Repository navigation
feat!: OAuth client credentials and the v1 API (1.0.0) - #48
Merged
Merged
Conversation
…PI keys Connect-Inforcer gains a ClientCredentials parameter set (-ClientId/-ClientSecret). The API version follows the login: API key -> /beta, client credentials -> /v1. v1 responses are mapped to the module's existing field names at the transport boundary, so API-key users see no change apart from a legacy-API warning. - Get-InforcerAuthHeader is the single place auth headers are built; v1 tokens are requested and renewed automatically - Invoke-InforcerApiRequest: version-neutral endpoints, v1 renames, problem+json errors, 429 Retry-After handling, 401 token renewal, collection unwrap - New Invoke-InforcerPagedRequest, Get-InforcerTenantList (session-bound cache) - Resolve-InforcerTenantId accepts Microsoft tenant IDs and names on v1 - Test-InforcerConnection no longer writes part of the API key to Verbose Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… audit events - Paging goes through Invoke-InforcerPagedRequest in both modes - Get-InforcerGroup -Group attaches v1 members from the paged members endpoint - Get-InforcerTenantPolicies warns when v1 returns no Intune policies - v1 audit search without -EventType no longer sends beta-only event types - -TenantId accepts the MsTenantId alias for pipeline binding - fix: Get-InforcerUser -OutputType JsonObject no longer throws - fix: Get-InforcerSupportedEventType returns the live list instead of the built-in fallback (a ?. expression parsed as a variable named $n?) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- All report and assessment requests, including the assessment runspace, the output download, the run probe and the TAB completer, take their auth header from Get-InforcerAuthHeader; long polls fetch a fresh header per call - v1 report runs post msTenantIds; outputs map msTenantId to tenantId - Permission and scope hints name v1 API client permissions on a v1 login - Invoke-InforcerAssessment -MultiTenant finds tenants on v1 - -TenantId accepts the MsTenantId alias - fix: the assessment-ID completer returned nothing when -ReportType was unbound Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eference Stop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ThrowTerminatingError is silenced when the caller passes -ErrorAction SilentlyContinue, so cmdlets wrapping Resolve-InforcerTenantId in try/catch returned nothing instead of their InvalidTenantId error. A plain throw of the ErrorRecord keeps the ErrorId and is always catchable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Get-InforcerTenant -Detailed fetches per-tenant details on v1 (no-op on beta, whose list is already rich); -TenantId returns the detailed tenant - Get-InforcerBaseline and Get-InforcerAlignmentDetails work on v1, including -Tag, baseline members and the scores table - Tenant lookups reuse the cached tenant list instead of refetching - Format views show the Microsoft tenant ID when there is no client tenant ID - -TenantId accepts the MsTenantId alias for pipeline binding Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The caught resolver error also lands in -ErrorVariable, as it already did for unknown tenant names, so the cmdlet's own record is not always first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It emitted the session and then the status object, so the documented $s = Connect-Inforcer -PassThru; Compare-InforcerEnvironments -SourceSession $s failed with 'Cannot convert System.Object[] to Hashtable'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…forcerEnvironments - Documentation data loads the detailed tenant on v1 and never falls back to another tenant when the record is missing - Baseline filtering on v1 joins on policy ids (v1 ids are Microsoft 365 ids) - Compare resolves each baseline owner in its own session, so mixed API-key and client-credentials sessions work - On v1 the HTML, Markdown, Excel and comparison reports carry a note when the API returned no Intune policies - Export -TenantId accepts the MsTenantId alias Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every caller now passes version-neutral endpoints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m API keys - README: quick start for both logins, legacy notice, migration guide - CMDLET-REFERENCE: new Connect parameter set, -Detailed, MsTenantId alias, v1 endpoints and API client permissions per cmdlet, Intune-gap notes - API-REFERENCE: authentication, v1 envelope, errors, paging, rate limits, every v1 path with its beta equivalent, permission tables for both models - docs/openapi-snapshot-v1.json: production v1 spec snapshot Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ixed it Verified live: every dynamic group carries its rule on the list. Removed the help, view and reference text that said it was always null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- API-key logins no longer retry on 429; only v1 honours Retry-After (0.8.0 behaviour) - cmdlets read their own error record, so network failures show their reason again - failed requests are stripped of auth headers and body, so no token, key or secret remains in $Error, -ErrorVariable or Get-Error - user and group lists stream again; Select-Object -First stops paging - a tenant missing from the cached list triggers one refresh - a failed v1 members call warns instead of returning an empty member list - Connect-Inforcer -BaseUrl must use https - help, docs and tests use synthetic ids; personal-data note added - dead fallbacks and stale comments removed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Get-InforcerReportRun -IncludeOutputs / -Wait embedded outputs without a runId, so the documented bulk-download pipeline failed with "RunId missing" in both login modes (also on 0.8.0). Outputs now carry runId, set where the outputs endpoint is read. README named the internal error id for numeric tenant ids on v1; callers see InvalidTenantId. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Get-InforcerTenantPolicies, Get-InforcerBaseline, Get-InforcerAlignmentDetails and Export-InforcerTenantDocumentation bind -TenantId from the pipeline but had no process block, so `Get-InforcerTenant | <cmdlet>` only handled the last tenant (also on 0.8.0). Invoke-InforcerAssessment collects piped tenants and runs them as one multi-tenant assessment, the same as an -TenantId array. A consistency test now fails for any cmdlet that takes pipeline input without a process block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eleven app ids in the -FetchGraphData name map were labelled with the wrong or an outdated name, e.g. 01cb2876 is Device Registration Service (not Global Secure Access), de8bc8b5 is Graph Explorer (not Microsoft Graph Command Line Tools), d4ebce55 is Microsoft Intune Enrollment and 4765445b is OfficeHome. Names now match Microsoft Learn; the two ids Learn does not list are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Report downloads reduce the server's file name to a safe leaf, never overwrite an existing file (name (n).ext) and -FileName writes directly. - HTML reports encode tenant- and API-supplied strings for their context: the assessment matrix no longer builds inline handlers from tenant names, the documentation search highlight builds text nodes, the comparison category filter builds DOM elements. - Excel export writes '=' values as text and no automatic hyperlinks; Markdown cells and headings encode & < >. - Requests that carry the API key, token or client secret never follow a redirect; a 3xx fails with a clear message. - The settings catalog freshness check decoded the release asset as bytes, so the cache never refreshed; it now decodes UTF-8, downloads when the remote date is unknown, and validates size and shape before replacing it. - Saved downloads are marked as internet files on Windows; -Open no longer launches .xls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Get-InforcerBaseline and Get-InforcerAlignmentDetails collect piped tenants and fetch baselines, scores and tenant details once per call; rows no longer repeat. - Results from piped tenants carry TenantId and -OutputType JsonObject returns one array; calls without pipeline input are unchanged. - v1 Role, Tenant and the alignment table return a JSON array for one item. - Baselines are cached per session (Get-InforcerBaselineList); a v1 -TenantId GUID lookup makes only the detail call. - Beta audit search sends one request instead of one per 10 event types (verified live: identical results). - The assessment run renews a v1 token once on 401, honours Retry-After on v1 429 and never follows a redirect; CSV export neutralises formulas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… type - Export builds Graph maps per tenant (no carry-over between piped tenants or calls), stops before overwriting one -OutputPath file with several tenants, and opens each HTML file once. - Graph sign-in requests Directory.Read.All, Policy.Read.All and DeviceManagementConfiguration.Read.All; 401/403 are not retried. - Conditional Access app names come from Graph (static map only as fallback), roles from roleDefinitions (custom roles too), groups via getByIds; Entra/SharePoint/M365 settings use inforcer's own names. - Compare fetches source and destination in parallel. - Beta no longer sends the policy type name; Export fills Profile type from policyTypeId (Get-InforcerPolicyTypeName, names taken from v1 responses) until inforcer restores the field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes 30 verified documentation errors (examples that did not bind, -OutputPath being a file for assessments, Test-InforcerConnection needing a baselines permission, swapped beta/v1 endpoints, wrong error ids and response shapes) and documents the behaviour from the security, pipeline and Graph fixes: piped TenantId, download rules, redirect refusal, catalog refresh, Graph scopes and parallel Compare. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… audit event fix -TenantId returns a fresh record per call, as in 0.8.0, so the cached-list note was wrong. The single audit search also returns event types the per-type search missed (apiKeyCreate, apiClientCreate). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved security and correctness issues can expose client secrets in error records, trigger account-wide assessments from empty pipelines, and select groups from incomplete search results.
6 open issues
Clear failed token request content containing the client secret · New Propagate paged search errors before resolving a group · New Validate every catalog entry before replacing the cache · New Check for repeated page tokens before emitting items · New Retry v1 report metadata requests on HTTP 429 · New Return early when the tenant pipeline has no input · New
What changed in this PR
Introduces the 1.0.0 dual-login model: OAuth client credentials use the v1 API, while API keys retain legacy beta access. Shared helpers normalize responses and authentication so existing cmdlets can support both APIs.
Changes:
- Adds v1 authentication, response mapping, tenant resolution, and shared pagination.
- Improves multi-tenant pipelines, report downloads, and parallel environment comparisons.
- Hardens credential handling and exports, refreshes settings-catalog handling, and documents migration.
| File | Description |
|---|---|
| Tests/GraphResolution.Tests.ps1 | Replaces named-location fixture GUIDs. |
| Tests/DocModel.Tests.ps1 | Standardizes GUID comparison fixtures. |
| Tests/Consistency.Tests.ps1 | Updates authentication, pipeline, and download assertions. |
| module/Public/Test-InforcerConnection.ps1 | Uses shared authenticated requests for connection checks. |
| module/Public/Save-InforcerReportOutput.ps1 | Supports both APIs and safe filename overrides. |
| module/Public/Invoke-InforcerAssessment.ps1 | Collects piped tenants and protects CSV values. |
| module/Public/Get-InforcerUser.ps1 | Adds shared pagination and tenant-labelled pipeline output. |
| module/Public/Get-InforcerTenantPolicies.ps1 | Supports v1 policies and warns about missing Intune data. |
| module/Public/Get-InforcerTenant.ps1 | Adds v1 tenant details and session-cached lookups. |
| module/Public/Get-InforcerSupportedEventType.ps1 | Corrects live event-type parsing. |
| module/Public/Get-InforcerSecureScore.ps1 | Supports both APIs and combined pipeline JSON. |
| module/Public/Get-InforcerRole.ps1 | Adds v1 mapping and tenant-labelled results. |
| module/Public/Get-InforcerReportType.ps1 | Uses version-neutral report catalog requests. |
| module/Public/Get-InforcerReportRun.ps1 | Maps v1 report runs to existing property names. |
| module/Public/Get-InforcerGroup.ps1 | Adds shared pagination and separate v1 member retrieval. |
| module/Public/Get-InforcerBaseline.ps1 | Fetches once and labels results for piped tenants. |
| module/Public/Get-InforcerAuditEvent.ps1 | Consolidates searches through shared pagination. |
| module/Public/Get-InforcerAssessment.ps1 | Uses version-neutral assessment requests. |
| module/Public/Disconnect-Inforcer.ps1 | Recognizes either login type when disconnecting. |
| module/Public/Compare-InforcerEnvironments.ps1 | Resolves baseline owners per session and propagates notes. |
| module/Private/Test-InforcerSession.ps1 | Validates API-key or client-credential sessions. |
| module/Private/Test-InforcerReportRunTerminal.ps1 | Supports v1 polling, token renewal, and output run IDs. |
| module/Private/Select-InforcerBaselinePolicies.ps1 | Supports v1 tenant and policy identifiers. |
| module/Private/Resolve-InforcerTenantId.ps1 | Resolves identifiers according to the active API. |
| module/Private/Resolve-InforcerReportTypeSchema.ps1 | Uses mapped catalog requests and broader credential redaction. |
| module/Private/Resolve-InforcerReportOutputFileName.ps1 | Prevents downloaded filenames from becoming hidden dotfiles. |
| module/Private/Resolve-InforcerGroupId.ps1 | Searches all group pages during name resolution. |
| module/Private/Resolve-InforcerBaselineId.ps1 | Uses version-neutral baseline lookup. |
| module/Private/Resolve-InforcerAssessmentId.ps1 | Uses version-neutral assessment lookup. |
| module/Private/Protect-InforcerApiKeyInText.ps1 | Extends redaction to secrets and bearer tokens. |
| module/Private/Invoke-InforcerRawDownload.ps1 | Adds dual authentication and non-overwriting downloads. |
| module/Private/Invoke-InforcerPagedRequest.ps1 | Centralizes pagination for both APIs. |
| module/Private/Invoke-InforcerGraphRequest.ps1 | Refines retries and permission warnings. |
| module/Private/Invoke-InforcerAssessmentRun.ps1 | Adds v1 retries and redirect-safe background requests. |
| module/Private/Get-InforcerTenantList.ps1 | Adds a session-specific tenant cache. |
| module/Private/Get-InforcerSettingsCatalogPath.ps1 | Fixes refresh handling and validates downloaded catalogs. |
| module/Private/Get-InforcerPolicyTypeName.ps1 | Supplies fallback profile-type names. |
| module/Private/Get-InforcerDocData.ps1 | Collects dual-API data and records coverage gaps. |
| module/Private/Get-InforcerComparisonData.ps1 | Collects destination data concurrently with the source. |
| module/Private/Get-InforcerBaseUrl.ps1 | Centralizes versioned URLs and endpoint renames. |
| module/Private/Get-InforcerBaselineList.ps1 | Adds a session-specific baseline cache. |
| module/Private/Get-InforcerAuthHeader.ps1 | Centralizes authentication and OAuth token renewal. |
| module/Private/Export-InforcerDocExcel.ps1 | Neutralizes formulas and adds report notes. |
| module/Private/ConvertTo-InforcerMarkdown.ps1 | Encodes HTML-sensitive text and includes notes. |
| module/Private/ConvertTo-InforcerHtml.ps1 | Safely highlights search text and displays notes. |
| module/Private/ConvertTo-InforcerDocModel.ps1 | Improves policy labels, profile types, and tenant fallbacks. |
| module/Private/ConvertTo-InforcerComparisonHtml.ps1 | Safely constructs filters and displays coverage notes. |
| module/Private/ConvertTo-InforcerAssessmentMatrixHtml.ps1 | Hardens embedded data and tenant controls. |
| module/Private/ConvertTo-InforcerAssessmentHtml.ps1 | Escapes status attribute values. |
| module/Private/ConvertFrom-InforcerV1Response.ps1 | Maps v1 objects to canonical property names. |
| module/Private/Connect-InforcerGraph.ps1 | Expands default Graph read scopes. |
| module/InforcerCommunity.psm1 | Warns when unloading either authenticated session type. |
| module/InforcerCommunity.psd1 | Sets version 1.0.0 and updates release notes. |
| module/InforcerCommunity.Format.ps1xml | Adapts displayed fields to API-specific properties. |
| CONTRIBUTING.md | Documents dual-API implementation and testing conventions. |
| CHANGELOG.md | Records release changes, limitations, and migration steps. |
Review effort: Balanced
…olicy type name inforcer released fixes for the v1 API (ENG-6987..6992), verified live on UK 2026-11-05. - The v1 tenant list now holds every field, so Get-InforcerTenant -Detailed (never released) is removed, and -Tag and -TenantId in Get-InforcerAlignmentDetails read tags and aligned tenants from the list: no per-tenant or /baselines call. v1 and beta share one path. - Invoke-InforcerAssessment shows inforcer's own score (v1 summary.score) when it is a number from 0 to 100, else it computes Pass / (Pass + Fail); the matrix report escapes the score. - Profile type names follow the names v1 now uses on every route (8 updated, 5 added). - A -Tag or piped tenant that matches nothing returns no rows instead of every row; -Tag warns only when no tenant has the tag (also a fix for API-key users). - Docs: v1 audit events carry relId/relType and list the apiClient* types; refreshed the v1 OpenAPI snapshot. Reviewed red/blue/green; mutations of every new behaviour are caught by the tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v1 policies endpoint now returns Intune and Defender settings-catalog policies (verified on UK 2026-11-05: v1 = beta, 244 policies, 81 Intune). Removed the zero-Intune warning in Get-InforcerTenantPolicies and Get-InforcerDocData, and the report note it fed (DocModel Notes in HTML, Markdown, Excel and the Compare report), which nothing else used. Skipped types are still named through the failedPolicyTypes warning. Docs no longer tell users to keep an API key for Intune. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Get-InforcerAlignmentDetails rounded the score to one decimal and returned it as text with a comma
("92,8"), or as a whole number, so sorting and filtering on it compared text. It now returns the
API's number (92.79) on both logins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce Stop GitHub Actions' pwsh shell sets $ErrorActionPreference = 'Stop', which made three Fix.Review mocks throw instead of emitting a non-terminating error like the real request helpers do. CI failed on them since 47f1311. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Keep the token and API key out of the debug stream: the download, run poll and assessment completer requests now pass -Debug:$false -Verbose:$false like every other request. - Redact credentials in the non-JSON response preview. - A piped object without a tenant id no longer lifts the tenant filter in Get-InforcerAlignmentDetails -Format Raw and Get-InforcerBaseline. - The assessment matrix keys results by tenant id, so two tenants with the same name keep their own result. - The Markdown export escapes baseline, product and category names. - Docs: event-type wording, stale comment, changelog test numbers and date. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blue-lane review before the 1.0.0 release: - Get-InforcerUser -UserId writes the API error for a non-404 failure. - Get-InforcerAlignmentDetails -BaselineId writes the API error instead of a warning, so -ErrorAction Stop throws. - An empty response body is an EmptyResponse error, not a null-method crash. - Get-InforcerUser, Get-InforcerGroup and Get-InforcerAuditEvent JSON output is withheld when a page fails, like Get-InforcerTenant and Get-InforcerRole. - Tests pin the Role and v1 Policies failure guards (mutations survived before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows the status-keying change: the tenant checkboxes, Select All and the column filter used the tenant name, so two same-named tenants hid together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Human assessment is needed to weigh unpublished moderate findings affecting aggregate output after failures, baseline completeness checks, and OAuth token refreshes during retries.
No open issues 🎉
1 resolved since last review
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What's New
inforcer now has a versioned API (v1) with API clients that log in with a client ID and secret. 1.0.0 supports both logins: an API key keeps using the legacy beta API, and client credentials use v1. Every cmdlet works with either, and v1 responses are mapped to the module's usual property names, so most scripts run unchanged.
Highlights
Connect-Inforcer -ClientId <id> -ClientSecret <secret> -Region uk, with automatic token renewal and429retries that honourRetry-After.-TenantIdtakes a Microsoft tenant ID (GUID) or a tenant name with both logins; numeric IDs keep working with an API key.Invoke-InforcerAssessmentshows inforcer's own compliance score with client credentials.Compare-InforcerEnvironmentscan compare across logins, and it collects both tenants at the same time.failedPolicyTypes).Fixes that API-key users get too
Get-InforcerAlignmentDetails -Tagwith a tag no tenant has returns nothing and warns; it used to return every row.Get-InforcerSupportedEventTypereturns the live list.Save-InforcerReportOutputworks.$Error.The full list and the migration steps are in CHANGELOG.md under 1.0.0.
Merge order
Verification
main(SettingsCatalog integration test against the live catalog data, unrelated), 3 skipped. CI green.Export -Tag, on both, because no policy in the test tenant carries a tag.Full Changelog: v0.8.0...feature/oauth-auth
-Debugoutput or in a non-JSON error; failed requests are never shown as an empty or complete result (Get-InforcerUser -UserId,-BaselineId, user/group/audit JSON); an empty API answer is a clear error; same-named tenants in the assessment matrix; Markdown escaping of baseline/product/category names. Each fix has a test that fails without it. Live matrix rerun on both logins after the fixes: no status changes, no leaks.🤖 Generated with Claude Code