Skip to content

Setup permissions: replace oauth2PermissionGrants POST with unified /v2.0/adminconsent URL#424

Open
sellakumaran wants to merge 10 commits into
mainfrom
users/sellak/setup-permissions-improvements
Open

Setup permissions: replace oauth2PermissionGrants POST with unified /v2.0/adminconsent URL#424
sellakumaran wants to merge 10 commits into
mainfrom
users/sellak/setup-permissions-improvements

Conversation

@sellakumaran
Copy link
Copy Markdown
Contributor

@sellakumaran sellakumaran commented May 21, 2026

Summary

a365 setup all / setup permissions no longer calls the Microsoft Graph
oauth2PermissionGrants POST API to grant delegated consent on the agent
blueprint. Instead, the CLI builds a single /v2.0/adminconsent URL that
covers every delegated scope across all configured resources (Observability,
Power Platform, Messaging Bot, custom) and routes all delegated consent
through it.

Why

  • oauth2PermissionGrants POST requires DelegatedPermissionGrant.ReadWrite.All
    on the caller's token — a high-privilege permission most setup operators
    don't have, leading to per-resource 403s and partial grants.
  • The previous orchestration emitted one consent URL per resource, forcing
    the admin to click through several screens for what is logically one
    decision.
  • The unified /v2.0/adminconsent URL works for both GA-and-non-GA callers,
    is the same flow the Entra portal uses, and lands all scopes atomically.

The legacy Phase 2b grant code path is removed entirely; ~280 lines of
orchestration and ~150 lines of associated tests are gone with it.

Also in this PR

  • Fix: retry the blueprint application PATCH (identifier URI +
    access_agent_as_user OBO scope) on transient Request_ResourceNotFound.
    New blueprint apps have a Graph write replica that lags the read replica
    by several seconds, so the existing GET propagation check could succeed
    while the immediately following PATCH 404'd, silently leaving the
    blueprint incomplete. Retries are exhausted → SetupValidationException
    with a Re-run: a365 setup blueprint --agent-name <name> mitigation.
  • UX: friendlier admin-consent polling when the caller's token lacks
    DelegatedPermissionGrant.Read.All. The CLI now polls Graph every 5s
    in case the read permission becomes available, prints a 30s progress
    update, and responds promptly to Enter or Ctrl+C. The jargon-heavy
    message about oauth2PermissionGrants is demoted to Debug.
  • Refactor: extract shared helpers from NonDwBlueprintSetupOrchestrator
    into SetupHelpers; add Helpers/ConsoleHelper.ReadLineCancellable.

Validation

  • Build clean (0 warn / 0 err)
  • Tests: 1521 passed / 12 skipped / 0 failed, all under 1s
  • Live a365 setup all --agent-name MCPPermissionsTest4:
    blueprint PATCH succeeded after replica catch-up, OBO scope added,
    unified consent URL surfaced, canary polling and Enter handling behaved
    as expected.

CHANGELOG.md [Unreleased] updated under ### Fixed and ### Changed.

Admin Consent and S2S Permission Flow — Verified End-to-End

What the CLI configures on the blueprint

An Agent 365 blueprint service principal carries two distinct layers of permissions, both verified in the live run below:

1. Delegated permissions — scopes the blueprint grants to end-user sessions via the Agent Identity inheritance mechanism. These cover what an agent instance can do on behalf of a signed-in user (read mail, send chat, write telemetry, etc.). Consent is tenant-wide and is granted by a GA or Application Administrator completing the browser consent screen that the CLI opens.

2. Application permissions (S2S) — app role assignments granted directly to the blueprint's service principal so it can call APIs as itself (no user in the loop). The Observability API Agent365.Observability.OtelWrite role is the only one configured in this profile. These require AppRoleAssignment.ReadWrite.All to assign via the Graph API.


Before this PR — what failed and why

Operation API called Required scope on CLI token Result
Grant delegated consent POST /v1.0/oauth2PermissionGrants DelegatedPermissionGrant.ReadWrite.All 403 for any non-GA caller; GA needed it explicitly granted on their token
Assign S2S app role POST /v1.0/servicePrincipals/{id}/appRoleAssignments AppRoleAssignment.ReadWrite.All 403 for all callers; operator had to copy-paste and run the PowerShell script manually

Both operations were being attempted programmatically, requiring high-privilege Graph API scopes that the CLI's minimal 7-permission client app does not have and should not have.


After this PR — what happens now

Delegated consent: The CLI builds a single /v2.0/adminconsent?client_id=…&scope=… URL covering all required delegated scopes for all resources atomically and opens the browser. A GA or Application Administrator completes the standard Entra consent screen — the same flow the Entra portal uses. The CLI no longer needs DelegatedPermissionGrant.ReadWrite.All on its own token; the privilege is exercised by the human admin in the browser.

Note: Because the CLI client app does not have DelegatedPermissionGrant.Read.All, it cannot programmatically verify that consent was applied after the admin completes the screen. The CLI waits for the operator to press Enter, then continues. Post-setup verification is done via a365 query-entra blueprint-scopes (see output below).

S2S app role assignment: The CLI first tries the Graph API (fast path). On 403, it automatically runs the assignment script via the Microsoft Graph PowerShell SDK (Connect-MgGraph -Scopes 'AppRoleAssignment.ReadWrite.All','Directory.Read.All'). The SDK authenticates via its own well-known app registration ("Microsoft Graph Command Line Tools") and — if those scopes have not already been consented to in the tenant — prompts the operator via device code flow at that point. No manual script copy-paste required.


Verified run — 2026-05-21

Command: a365 setup all --agent-name MCPPermissionsTest4
Operator: sellak@agent365002.onmicrosoft.com (Global Administrator)
CLI version: 1.1.188+781b997
Tenant: bc51ddf2-9c8b-45e7-8e08-c7ac238f6520 (agent365002.onmicrosoft.com)

Token scopes used by the CLI

From the scp claim logged at DEBUG level during the run:

AgentIdentity.DeleteRestore.All  AgentIdentity.Read.All
AgentIdentityBlueprint.ReadWrite.All  AgentIdentityBlueprintPrincipal.Create
AgentRegistration.ReadWrite.All  Application.Read.All  User.Read

DelegatedPermissionGrant.ReadWrite.All — absent.
AppRoleAssignment.ReadWrite.All — absent.
The CLI operated entirely within its minimal 7-permission set.

Key log events

Time Event
12:58:17–19 Blueprint created — MCPPermissionsTest4 Blueprint (ID: 1f6607fc-c27b-4d85-9ea1-f45b5b203b7f)
12:58:25–34 Identifier URI PATCH — attempts 1 and 2 returned 404 (write-replica lag); succeeded on attempt 3
12:58:37–40 Inheritable permissions configured on 4 resources (Microsoft Graph, Work IQ Tools, Observability API, Power Platform API)
12:58:40–55 S2S app role assignment via Graph API — 3 retries on 404 (SP propagation), then 403 (expected — AppRoleAssignment.ReadWrite.All not on CLI token)
12:58:55 PowerShell fallback triggered automatically — device code issued (AYPJDCWVU)
12:59:52 pwsh exited 0 — A365-S2S-OK confirmed — Observability API app role assigned
12:59:52 Browser opened for unified delegated consent URL (all 14 scopes in one request)
13:00:21 Operator confirmed browser consent; CLI continued (consent not auto-verifiable — CLI lacks DelegatedPermissionGrant.Read.All)
13:00:24 Agent identity created, agent registered
13:00:24 Setup completed successfully — all 7 steps

Blueprint permissions confirmed via a365 query-entra blueprint-scopes

Run post-setup to confirm consent was applied and app role assignment is in place:

Agent Blueprint ID: 1f6607fc-c27b-4d85-9ea1-f45b5b203b7f

Resource: Microsoft Graph (00000003-0000-0000-c000-000000000000)
  Delegated permissions (8): ChannelMessage.Read.All, ChannelMessage.Send, Chat.ReadWrite,
                              Files.ReadWrite.All, Mail.ReadWrite, Mail.Send,
                              Sites.Read.All, User.Read.All
  Application permissions (0): (none)

Resource: Work IQ Tools (ea9ffc3e-8a23-4a7d-836d-234d7c7565c1)
  Delegated permissions (4): McpServers.Calendar.All, McpServers.Mail.All,
                              McpServers.Teams.All, McpServersMetadata.Read.All
  Application permissions (0): (none)

Resource: Observability API (9b975845-388f-4429-889e-eab1ef63949c)
  Delegated permissions (1): Agent365.Observability.OtelWrite
  Application permissions (1): Agent365.Observability.OtelWrite   <-- assigned via PowerShell fallback

Resource: Power Platform API (8578e004-a5c6-46e7-913e-12f58912df43)
  Delegated permissions (1): Connectivity.Connections.Read
  Application permissions (0): (none)

Summary: 4 of 4 resource(s) have at least one granted permission on the blueprint SP.
Total delegated: 14.  Total application: 1.

Observability API has both delegated and application (S2S) permissions — confirming the PowerShell fallback completed the app role assignment that the Graph API path could not.


CLI client app permission set (for reference)

The 7 delegated permissions required on the custom client app registration — no high-privilege admin scopes:

Permission Purpose
AgentIdentityBlueprint.ReadWrite.All Blueprint CRUD, client secret, identifier URI, FIC ops
AgentIdentityBlueprintPrincipal.Create Create the blueprint service principal
AgentIdentity.Read.All Idempotency check, identity lookup
AgentIdentity.DeleteRestore.All Cleanup
AgentRegistration.ReadWrite.All Agent registration
Application.Read.All Service principal lookup by appId
User.Read Current user identity for owner/sponsor assignment

…d scopes; drop oauth2PermissionGrants POST

- BatchPermissionsOrchestrator: remove Phase 2b oauth2 grant calls; admin path now opens a single
  /v2.0/adminconsent URL covering Graph + non-Graph resources (api://{appId}/{scope}) and polls
  Graph for completion. Non-admin path returns the same URL via LogPermissionsActionRequired.
- SetupHelpers.LogPermissionsActionRequired: replace per-resource Invoke-MgGraphRequest snippets
  with the unified consent URL; retain New-MgServicePrincipalAppRoleAssignment for S2S grants.
- PermissionsSubcommand: thread localResults + specs into LogAdminConsentNextSteps so standalone
  runs of permissions mcp|bot|custom emit the same Action Required block as setup all.
- SetupCommand / PermissionsSubcommand help: clarify role separation (Agent ID Developer vs
  Global Administrator) and surface all granular subcommands.
- BrowserHelper.OpenUrlOverrideForTests + AdminConsentHelper.BypassConsentChecksForTests:
  AsyncLocal-scoped test overrides so admin-path unit tests do not launch real browsers or poll
  Graph for 180s; wired into PermissionsSubcommandTests and BatchPermissionsOrchestratorTests.
- Tests: rewrite BatchPermissionsOrchestratorTests + LogPermissionsActionRequired tests for the
  unified flow. Full suite: 1525 passed, 12 skipped, 0 failed.
…elper extractions

Blueprint create path: retry the initial application PATCH (identifier URI + access_agent_as_user OBO scope) and the EnsureOboScope safety-net PATCH on transient Request_ResourceNotFound. Newly created blueprint apps frequently have a write replica that lags the read replica for several seconds, so the existing GET propagation check could succeed while the immediately-following PATCH 404'd, silently leaving the blueprint without identifier URI or OBO scope.

AdminConsentHelper canary 403 path: replace the immediate Enter prompt with a poll loop that re-runs the canary every 5s, prints a 30s progress update, and responds promptly to Enter or Ctrl+C. Rewrite the jargon-heavy oauth2PermissionGrants / DelegatedPermissionGrant message in plain English; demote technical detail to Debug.

Refactors: extract shared helpers from NonDwBlueprintSetupOrchestrator into SetupHelpers; add ConsoleHelper.ReadLineCancellable for prompts that need cancellation; small adjustments to BatchPermissionsOrchestrator, AuthenticationService, GraphApiService. Test counts hold at 1521 passing / 12 skipped.
@sellakumaran sellakumaran requested a review from a team as a code owner May 21, 2026 01:07
Copilot AI review requested due to automatic review settings May 21, 2026 01:07
@sellakumaran sellakumaran requested a review from a team as a code owner May 21, 2026 01:07
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label May 21, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Agent365 CLI setup permission flows to stop using Graph POST /oauth2PermissionGrants for delegated consent and instead build a unified Entra /v2.0/adminconsent URL covering delegated scopes across all configured resources, while also improving reliability around newly-created blueprint app PATCH propagation.

Changes:

  • Replace per-resource delegated consent orchestration (and its PowerShell snippets) with a single combined /v2.0/adminconsent URL for Graph + non-Graph resources.
  • Add retry/backoff for blueprint application PATCH operations that can 404 due to write-replica lag, and improve cancellation handling in Graph/auth flows.
  • Introduce test hooks to suppress real browser launches/long consent polling, plus a cancellable console input helper used by setup prompts.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Helpers/SetupHelpersLogPermissionsActionRequiredTests.cs New unit tests for the Action Required block output for consent URL + S2S PowerShell.
src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/PermissionsSubcommandTests.cs Suppresses browser/polling in tests via overrides; adds IDisposable cleanup.
src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/NonDwBlueprintSetupOrchestratorExecuteTests.cs Removes tests for the retired per-identity oauth2PermissionGrants path.
src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/BatchPermissionsOrchestratorTests.cs Updates tests for unified consent URL behavior; suppresses browser/polling in tests.
src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/AdminConsentHelper.cs Adds canary-based polling behavior and a test bypass hook; improves UX around lacking grant-read permission.
src/Microsoft.Agents.A365.DevTools.Cli/Services/GraphApiService.cs Ensures OperationCanceledException is not swallowed during token acquisition.
src/Microsoft.Agents.A365.DevTools.Cli/Services/AuthenticationService.cs Ensures OperationCanceledException is not wrapped as authentication failure.
src/Microsoft.Agents.A365.DevTools.Cli/Helpers/ConsoleHelper.cs Adds cancellable Console.ReadLine helper with a test override hook.
src/Microsoft.Agents.A365.DevTools.Cli/Helpers/BrowserHelper.cs Adds AsyncLocal test override to avoid launching real browsers in tests.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/SetupHelpers.cs Uses cancellable console input; updates summary logic; adds unified resource identifier/scope helpers; adds LogPermissionsActionRequired helper.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/PermissionsSubcommand.cs Updates help text; ensures Action Required output includes tenant/blueprint context for standalone subcommands.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/NonDwBlueprintSetupOrchestrator.cs Removes per-agent-identity delegated grant POST path; relies on blueprint inheritance + admin consent.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/BlueprintSubcommand.cs Adds retry/backoff around blueprint PATCH operations; throws SetupValidationException after exhaustion.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/BatchPermissionsOrchestrator.cs Removes oauth2PermissionGrants POST orchestration; builds unified consent URL and uses browser+polling for admins.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupCommand.cs Refreshes CLI help text to emphasize setup all and clarify role requirements.
CHANGELOG.md Documents unified consent URL flow and blueprint PATCH retry behavior.
.gitignore Adds Other/ ignore entry.
Comments suppressed due to low confidence (1)

src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/PermissionsSubcommandTests.cs:70

  • Dispose() currently sets up NSubstitute return values on _mockGraphApiService. Stubbing in Dispose is unexpected and ineffective for the tests that just ran, and it makes cleanup code harder to reason about. Move these default Returns(...) calls into the constructor (or remove them if unused) and keep Dispose limited to resetting the test overrides.
    public void Dispose()
    {
        BrowserHelper.OpenUrlOverrideForTests = null;
        AdminConsentHelper.BypassConsentChecksForTests = false;
        GC.SuppressFinalize(this);
        _mockGraphApiService.IsCurrentUserAdminAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(Microsoft.Agents.A365.DevTools.Cli.Models.RoleCheckResult.Unknown);
        _mockGraphApiService.IsCurrentUserAgentIdAdminAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(Microsoft.Agents.A365.DevTools.Cli.Models.RoleCheckResult.Unknown);
    }

Comment thread src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/AdminConsentHelper.cs Outdated
1. (HIGH) PollAdminConsentAsync Graph overload returns tri-state ConsentPollResult (Verified / AssumedComplete / NotDetected) so callers can distinguish a directly observed grant from an unverified canary-403 fallback. BatchPermissionsOrchestrator now persists ResourceConsents only when Verified, and keeps the consent URL visible in the Action Required block on AssumedComplete. BlueprintSubcommand logs different messages per outcome.

2. (MED) Refresh stale comment in PermissionsSubcommand.LogAdminConsentNextSteps. The block does not surface PowerShell snippets for non-Graph resources anymore; the comment now matches the real behavior (unified /v2.0/adminconsent URL + optional S2S app-role guidance).

3. (MED) Narrow the identifier-URI / OBO-scope PATCH retry in BlueprintSubcommand to only retry on 404 Request_ResourceNotFound (the documented write-replica-lag race). Other failures abort the retry loop immediately so the SetupValidationException fires on the first attempt.

Tests: 1524 pass (12 skipped, 0 failed). Added 3 new tests in AdminConsentHelperTests with 'because:' clauses locking in the tri-state contract: canary 403 -> AssumedComplete, canary already shows grant -> Verified, missing SP id -> NotDetected.
When the Graph API path returns 403 for a Global Administrator (the
delegated token does not carry AppRoleAssignment.ReadWrite.All), setup
now automatically runs the assignment script via pwsh. On success,
BlueprintS2SOutcome is set to Granted and the "Action Required" block
is suppressed. Requires pwsh 7+ and Microsoft.Graph modules; run
'a365 setup requirements' to check prerequisites.

Demotes intermediate S2S failure log levels to Debug since the
PowerShell fallback now handles those cases, reducing console noise.
Adds a user-visible pre-launch message so agents driving the CLI can
detect and relay the interactive device code step. Removes dead
ExecuteWithStdinAsync method from CommandExecutor.
Copilot AI review requested due to automatic review settings May 21, 2026 20:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/PermissionsSubcommandTests.cs:70

  • The stubs for IsCurrentUserAdminAsync / IsCurrentUserAgentIdAdminAsync are currently inside Dispose(), so they run after each test instead of before. Because _mockGraphApiService is a partial substitute, leaving these unstubbed during the test can call the real implementation and trigger auth/Graph calls (or hang CI). Move these .Returns(...) setups into the constructor (or into each test) and keep Dispose() only for resetting AsyncLocal test overrides.
        _mockGraphApiService.IsCurrentUserAdminAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(Microsoft.Agents.A365.DevTools.Cli.Models.RoleCheckResult.Unknown);
        _mockGraphApiService.IsCurrentUserAgentIdAdminAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(Microsoft.Agents.A365.DevTools.Cli.Models.RoleCheckResult.Unknown);
    }

Comment thread src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/SetupHelpers.cs Outdated
Comment thread src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/SetupHelpers.cs Outdated
…ary logic

- Fix CI test crash: mock EnsureServicePrincipalForAppIdAsync and
  SetInheritablePermissionsAsync in NonAdmin_BuildsUnifiedConsentUrl test
  so ForPartsOf substitutes do not reach real Azure endpoints in CI

- Fix hasS2SPowerShell to check BlueprintS2SOutcome != Granted so the
  Action Required S2S PowerShell block is suppressed when the automatic
  PowerShell fallback already completed the assignment

- Fix s2sOk in non-DW flow to require both agentIdS2sGranted and
  !blueprintS2sFailed, preventing an inconsistent summary where the
  grants row shows OK while the Action Required block fires for blueprint S2S

- Fix Connect-MgGraph snippet to omit -TenantId when tenantId is null
  rather than emitting an unusable empty placeholder
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated 4 comments.

Comment thread src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/AdminConsentHelper.cs Outdated
Comment thread src/Microsoft.Agents.A365.DevTools.Cli/Services/Helpers/AdminConsentHelper.cs Outdated
…in S2S tests

- BatchPermissionsOrchestrator: set BlueprintS2SOutcome=Failed for non-admin
  callers that have S2S specs, so DisplaySetupSummary surfaces the PowerShell
  hand-off block in the Action Required section alongside the consent URL.
  Previously the block was silently suppressed for non-GA users.

- SetupHelpers: add blank line before the S2S Action Required item so it is
  visually separated from the preceding consent URL block.

- BatchPermissionsOrchestratorTests: mock EnsureServicePrincipalForAppIdAsync
  and SetInheritablePermissionsAsync in ArrangeS2SPhase1AndAdminCheck to prevent
  real Azure HTTP calls in CI (ForPartsOf<> calls real implementations for
  unmocked methods — caused a 5-minute hang and crash on Linux CI). The three
  existing GA S2S tests are unaffected: null resource SP still yields
  BlueprintS2SOutcome=Failed, which is the precondition for the PowerShell
  fallback under test. Also adds a new test covering the non-admin S2S outcome.
…splay path tests

- Admin consent polling now uses the az-cli (az rest) overload unconditionally when
  commandExecutor is available, fixing the 403 that caused every poll to time out even
  when consent was already granted (MSAL delegated token lacks DelegatedPermissionGrant.Read.All
  since PR #409; az-cli token carries GA-level access)
- adminConsentUrl was being unconditionally nulled for all consentGranted=true cases,
  making it impossible to distinguish Verified from AssumedComplete; now passed through
  unchanged from GrantAdminConsentAsync
- GrantOutcome.AssumedGranted renamed to GrantOutcome.Unverified; setup summary row
  shows "unverified — run 'a365 query-entra inheritance' to confirm" and an Action
  Required block surfaces the re-grant URL
- Add three display path tests for Unverified outcome in SetupHelpersDisplaySetupSummaryTests
- Fix PollAdminConsentAsync_Graph_ReturnsVerified test to use factory lambda so each
  mock call gets a fresh JsonDocument (avoids ObjectDisposedException on reuse)
- Fix CheckConsentExistsAsync test stubs to use 4-arg GraphGetAsync overload
- Update stale ConsentPollResult.AssumedComplete and TryConsumeEnterKey doc comments
  after canary-403 path removal
Copilot AI review requested due to automatic review settings May 22, 2026 00:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 7 comments.

…auth instead

Device code requires the operator to open a browser, navigate to login.microsoft.com/device,
and type a code — more steps than necessary. Without the flag, Connect-MgGraph uses
WAM/browser auth on Windows (potentially silent if a cached token exists) and browser
auth on macOS/Linux desktop. Headless scenarios are not a concern for this flow since
it requires an interactive Global Administrator session regardless.
Copilot AI review requested due to automatic review settings May 22, 2026 01:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/BatchPermissionsOrchestrator.cs:88

  • ConfigureAllPermissionsAsync filters out any spec with Scopes.Length == 0 and then uses the filtered list for all phases. That made sense when Phase 2b created OAuth2 grants, but now Phase 2b is S2S app-role assignment; S2S-only specs (empty delegated scopes but non-empty AppRoleScopes) would be dropped and never assigned (or surfaced as Action Required). The filter should keep specs that have delegated scopes OR app-role scopes (and possibly keep inheritable-only specs if those are valid).
        if (specs.Count == 0)
        {
            logger.LogInformation("No permission specs provided — skipping batch permissions configuration.");
            return (true, true, true, null);
        }

        // Filter out specs with no scopes — they would produce empty OAuth2 grants (HTTP 400).
        // This can happen when the MCP manifest is missing or contains no required scopes.
        var effectiveSpecs = specs.Where(s => s.Scopes.Length > 0).ToList();
        if (effectiveSpecs.Count < specs.Count)

src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/PermissionsSubcommandTests.cs:70

  • Dispose() sets up new Returns(...) stubs on _mockGraphApiService (IsCurrentUserAdminAsync / IsCurrentUserAgentIdAdminAsync). This has no practical effect (the substitute instance is about to be discarded) and makes it harder to see what the test setup actually is. These stubs should either be removed or moved into the constructor if they are required for the tests.
    public void Dispose()
    {
        BrowserHelper.OpenUrlOverrideForTests = null;
        AdminConsentHelper.BypassConsentChecksForTests = false;
        GC.SuppressFinalize(this);
        _mockGraphApiService.IsCurrentUserAdminAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(Microsoft.Agents.A365.DevTools.Cli.Models.RoleCheckResult.Unknown);
        _mockGraphApiService.IsCurrentUserAgentIdAdminAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(Microsoft.Agents.A365.DevTools.Cli.Models.RoleCheckResult.Unknown);
    }

/// AsyncLocal scoping prevents leaks across parallel xUnit test classes; tests that set this
/// must still reset it in a finally/Dispose block. Not intended for production code.
/// </summary>
public static bool BypassConsentChecksForTests
Comment on lines +61 to +62
"Non-admin flow: any step that needs Global Administrator action prints\n" +
"copy-paste PowerShell that an admin can run out-of-band.");
- GrantAdminConsentAsync: return (true, null) when allConsented so ResourceConsents
  is persisted (verified signal requires null consentUrl)
- GrantAdminConsentAsync: return (true, null) when no delegated scopes exist
  (nothing to consent = success, not failure)
- BlueprintSubcommand: use <name> placeholder in mitigation step instead of
  interpolating displayName which may contain spaces or shell-special characters
- EnsureOboScopeAsync: reduce maxRetries 5->2 for non-fatal OBO PATCH to avoid
  ~45s wait on deterministic 400/403 failures
- PowerShellS2SRunner: replace Unicode em dashes with ASCII hyphens in all
  user-facing log messages and embedded PowerShell error strings
- AdminConsentHelper: fix progress report interval 60s->30s in both overloads
  to match CHANGELOG
- AdminConsentHelper: rethrow OperationCanceledException in Graph polling overload
  so Ctrl+C during consent polling terminates setup cleanly
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants