Skip to content

Commit 495c8ea

Browse files
sellakumaranclaude
andauthored
feat: add custom blueprint permissions configuration and management (#298)
* feat: add custom blueprint permissions with auto-lookup and code quality fixes - Add support for custom API permissions in agent blueprints - Auto-resolve resource display names from Azure (eliminates manual "Resource Name" prompt) - New `a365 setup permissions custom` command - New `a365 config init --custom-blueprint-permissions` management commands - Comprehensive validation with GUID format checks and duplicate scope detection - Integration with `a365 setup all` workflow - Fix "Agent Blueprints are not supported on the API version used" error - Change addToRequiredResourceAccess from true to false (matches CopilotStudio/MCP pattern) - Inheritable permissions now configure correctly without Graph API errors Security & Reliability: - Add HttpResponseMessage disposal with using statements - Add GUID validation to prevent OData injection in service principal lookups - Add safe substring operations with null/length checks in fallback name generation - Fix duplicate error logging when re-throwing exceptions Maintainability: - Add WithCustomBlueprintPermissions() helper to eliminate config reconstruction anti-pattern - Add --force flag for non-interactive permission updates - Add early validation for empty/whitespace scope inputs - Fix inconsistent null handling in Scopes property with setter null protection - Extract magic strings to constants in fallback resource names Documentation: - Add complete XML documentation with 10 parameter descriptions - Remove redundant test comments - Add trailing commas for consistency - 7 new/modified documentation files - 12 source files (commands, models, services) - 4 test files with 6 new unit tests - ✅ 992 tests passing (6 new tests for custom permissions) - ✅ Build: 0 warnings, 0 errors - ✅ All critical/high priority issues resolved Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Expose scopes as public property with null protection Changed the _scopes field in CustomResourcePermission to a public Scopes property with getter and setter. The setter ensures null values are replaced with an empty list, allowing safe external access and modification of scopes. * Update custom permissions: resource name now auto-resolved Docs clarify that resource name is not prompted or required during `a365 config init --custom-blueprint-permissions`; it is now set to null and auto-resolved during setup. Updated sample config and validation requirements to reflect this. Minor code refactor in ConfigCommand.cs to adjust validation order. * Refactor custom permissions: new 'config permissions' cmd Major overhaul of custom blueprint permissions management: - Adds `a365 config permissions` subcommand for add/update/list/reset - Removes old permission flags from `config init` - Integrates improved permission step into interactive wizard - Updates all docs and tests to use new command/flags - Improves validation, error messages, and config file discovery - Refactors logic into PermissionsSubcommand.cs and adds helper methods - Adds comprehensive unit tests for CLI and wizard flows - Enhances UX: wizard re-prompts only for invalid scopes - CLI suggests next steps after permission changes This modernizes and simplifies custom API permission management for agent blueprints. * Update docs for new 'a365 config permissions' command Replaced deprecated 'init --custom-blueprint-permissions' usage with 'config permissions' in Readme-Usage.md, including Copilot Studio setup instructions. Added [Collection("ConfigTests")] to ConfigurationWizardServicePermissionsTests.cs for improved test grouping. * chore: ignore diagnostics/, .codereviews/, and nul artifacts Prevents accidental commit of local diagnostic files, code review artifacts, and the Windows NUL device pseudo-file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: auto-apply custom blueprint permissions in 'setup blueprint' When customBlueprintPermissions are configured in a365.config.json, 'setup blueprint' now automatically calls ConfigureCustomPermissionsAsync instead of showing a hint to run a separate command. This matches the behavior of 'setup all' (Step 5) and ensures developers who run config init → add custom permissions → setup blueprint get a complete setup without extra manual steps. Note: guarded by !isSetupAll to avoid double-applying when called from 'setup all' (which handles custom permissions at its own Step 5). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Reconcile custom blueprint permissions in setup command Implements full reconciliation for custom blueprint permissions in the CLI. When running `a365 setup permissions custom` (or as part of `setup all`), the CLI now removes any custom permissions from Azure AD that are no longer present in the config file, including both inheritable permissions and OAuth2 grants (excluding standard/required permissions). Reconciliation runs even if the config is empty, ensuring stale permissions are cleaned up. Documentation and CLI output are updated to clarify that resource display names are resolved in-memory for logging only and not persisted. Adds new methods to list and remove inheritable permissions. Switches test mocking to NSubstitute and improves test resource cleanup. Also normalizes scope strings and enhances summary/error reporting. --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 422b513 commit 495c8ea

25 files changed

+3399
-19
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,8 @@ htmlcov/
9292
*.egg-info/
9393
dist/
9494
build/
95+
96+
# Local diagnostics and code review artifacts (never commit)
97+
diagnostics/
98+
.codereviews/
99+
nul

Readme-Usage.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ a365 config init -c path/to/config.json
5353
a365 config init --global
5454
```
5555

56+
**Configure custom blueprint permissions:**
57+
```bash
58+
# Add custom API permissions for your agent
59+
a365 config permissions --resource-app-id 00000003-0000-0000-c000-000000000000 \
60+
--scopes Presence.ReadWrite,Files.Read.All
61+
62+
# View configured permissions
63+
a365 config permissions
64+
65+
# Clear all custom permissions
66+
a365 config permissions --reset
67+
```
68+
5669
**Minimum required configuration:**
5770
```json
5871
{
@@ -122,9 +135,27 @@ a365 setup infrastructure
122135
a365 setup blueprint
123136
a365 setup permissions mcp
124137
a365 setup permissions bot
138+
a365 setup permissions custom # Configure custom blueprint permissions (if configured)
125139
a365 setup permissions copilotstudio # Configure Copilot Studio permissions
126140
```
127141

142+
**Custom Blueprint Permissions:**
143+
If your agent needs additional API permissions beyond the standard set (e.g., Presence, Files, Chat, or custom APIs), configure them before running setup:
144+
145+
```bash
146+
# Add custom permissions to config
147+
a365 config permissions --resource-app-id 00000003-0000-0000-c000-000000000000 \
148+
--scopes Presence.ReadWrite,Files.Read.All
149+
150+
# Then run setup (custom permissions applied automatically)
151+
a365 setup all
152+
153+
# Or apply custom permissions separately
154+
a365 setup permissions custom
155+
```
156+
157+
See [Custom Permissions Guide](docs/commands/setup-permissions-custom.md) for detailed examples.
158+
128159
### Publish & Deploy
129160
```bash
130161
a365 publish # Publish manifest to MOS

docs/ai-workflows/integration-test-workflow.md

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,35 @@ a365 config init --global
154154
# Record: Global config created (Yes/No)
155155
```
156156

157-
**Section 2 Status**: ✅ Pass | ❌ Fail
157+
#### Test 2.5: Configure Custom Blueprint Permissions
158+
```bash
159+
# Add Microsoft Graph extended permissions
160+
a365 config permissions \
161+
--resource-app-id 00000003-0000-0000-c000-000000000000 \
162+
--scopes Presence.ReadWrite,Files.Read.All
163+
164+
# Expected: NO PROMPTS - permission added directly to a365.config.json
165+
# Resource name will be auto-resolved during 'a365 setup permissions custom'
166+
# Verify customBlueprintPermissions array exists in config file
167+
# Record: Custom permission added (Yes/No)
168+
169+
# View configured permissions
170+
a365 config permissions
171+
172+
# Expected: Lists all configured custom permissions (may show appId only until setup runs)
173+
# Record: Permissions displayed correctly (Yes/No)
174+
175+
# Add second custom resource
176+
a365 config permissions \
177+
--resource-app-id 12345678-1234-1234-1234-123456789012 \
178+
--scopes CustomScope.Read,CustomScope.Write
179+
180+
# Expected: NO PROMPTS - second permission added directly
181+
# Resource names will be auto-resolved during setup
182+
# Record: Second permission added (Yes/No)
183+
```
184+
185+
**Section 2 Status**: ✅ Pass | ❌ Fail
158186
**Notes**:
159187

160188
---
@@ -284,7 +312,84 @@ a365 setup permissions bot
284312
# Record: Bot permissions set (Yes/No)
285313
```
286314

287-
**Section 4 Status**: ✅ Pass | ❌ Fail
315+
#### Test 4.5: Blueprint Permissions - Custom Resources (with Auto-Lookup)
316+
```bash
317+
# Configure custom permissions (requires Test 2.5 completed)
318+
a365 setup permissions custom
319+
320+
# Expected:
321+
# - AUTO-LOOKUP: CLI queries Azure to resolve resource display names
322+
# - Output shows: "Resource name not provided, attempting auto-lookup for {appId}..."
323+
# - Output shows: "Auto-resolved resource name: Microsoft Graph" (or similar)
324+
# - OAuth2 grants created for each custom resource
325+
# - Inheritable permissions configured
326+
# - Permissions visible in Azure Portal under API permissions
327+
# - Success messages for each configured resource
328+
# - Note: ResourceName is resolved in-memory for logging only; it is NOT persisted to any config file
329+
330+
# IMPORTANT: Verify auto-lookup messages appear in output
331+
# If resource not found in Azure, should show fallback: "Custom-{first 8 chars}"
332+
333+
# Record: Custom permissions configured (Yes/No)
334+
# Record: Number of custom resources configured
335+
# Record: Auto-lookup succeeded (Yes/No)
336+
```
337+
338+
#### Test 4.6: Verify Custom Permissions in Azure Portal
339+
```bash
340+
# Query blueprint application to verify custom permissions
341+
az ad app show --id <blueprint-app-id> --query "requiredResourceAccess[].{ResourceAppId:resourceAppId, Scopes:resourceAccess[].id}"
342+
343+
# Expected: Shows custom resource permissions configured
344+
# - Microsoft Graph (00000003-0000-0000-c000-000000000000) with extended scopes
345+
# - Custom API resource (if configured)
346+
347+
# Alternatively, verify in Azure Portal:
348+
# Navigate to: Entra ID → Applications → [Blueprint App] → API permissions
349+
# Verify custom permissions are listed with "Granted" status
350+
351+
# Record: Custom permissions visible in portal (Yes/No)
352+
```
353+
354+
#### Test 4.7: Verify Inheritable Permissions via Graph API
355+
```powershell
356+
# Get blueprint object ID from config
357+
$blueprintObjectId = (Get-Content a365.generated.config.json | ConvertFrom-Json).agentBlueprintObjectId
358+
359+
# Get access token
360+
$token = az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv
361+
362+
# Query inheritable permissions (this is what the CLI verifies internally)
363+
$headers = @{ Authorization = "Bearer $token" }
364+
$uri = "https://graph.microsoft.com/beta/applications/microsoft.graph.agentIdentityBlueprint/$blueprintObjectId/inheritablePermissions"
365+
$response = Invoke-RestMethod -Uri $uri -Headers $headers
366+
$response | ConvertTo-Json -Depth 10
367+
368+
# Expected response format:
369+
# {
370+
# "value": [
371+
# {
372+
# "resourceAppId": "00000003-0000-0000-c000-000000000000",
373+
# "resourceName": "Microsoft Graph",
374+
# "scopes": ["Presence.ReadWrite", "Files.Read.All"]
375+
# }
376+
# ]
377+
# }
378+
379+
# Verify:
380+
# - Each custom resource appears in the "value" array
381+
# - resourceAppId matches configured permissions
382+
# - resourceName is populated (auto-resolved during setup)
383+
# - All requested scopes are present
384+
385+
# Note: This is the SAME endpoint the CLI uses to verify permissions were set correctly
386+
# If this query succeeds, inheritable permissions are working properly
387+
388+
# Record: Inheritable permissions verified via Graph API (Yes/No)
389+
# Record: Number of resources found in response
390+
```
391+
392+
**Section 4 Status**: ✅ Pass | ❌ Fail
288393
**Notes**:
289394

290395
---
@@ -306,10 +411,18 @@ a365 setup all
306411
# Expected:
307412
# - Infrastructure created
308413
# - Blueprint created
309-
# - Permissions configured
414+
# - MCP permissions configured
415+
# - Bot API permissions configured
416+
# - Custom blueprint permissions configured (if present in config)
417+
# - Messaging endpoint registered
310418
# - All steps completed successfully
311419

420+
# Verify custom permissions were configured (if Test 2.5 was completed):
421+
# - Check output for "Configuring custom blueprint permissions..."
422+
# - Verify each custom resource shows "configured successfully"
423+
312424
# Record: Setup all completed (Yes/No)
425+
# Record: Custom permissions included (Yes/No/N/A)
313426
# Record: Time taken (approximate)
314427
```
315428

docs/commands/config-init.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,31 @@ Azure location [westus]:
190190

191191
**Smart Defaults**: Uses location from existing config or Azure account
192192

193-
### Step 9: Configuration Summary
193+
### Step 9: Custom Blueprint Permissions (Optional)
194+
195+
Optionally configure custom resource permissions for your agent:
196+
197+
```
198+
=== Optional: Custom Blueprint Permissions ===
199+
If your agent needs access to additional external resources
200+
(e.g. Teams presence, OneDrive files, custom APIs) beyond
201+
standard permissions, you can configure them here.
202+
Most agents do not require this.
203+
204+
Configure custom blueprint permissions? (y/N): y
205+
206+
Resource App ID (GUID) - press Enter when done: 00000003-0000-0000-c000-000000000000
207+
Scopes (comma-separated, e.g. Presence.ReadWrite,Files.Read.All): Presence.ReadWrite,Files.Read.All
208+
Permission added.
209+
210+
Resource App ID (GUID) - press Enter when done:
211+
```
212+
213+
Press **Enter** with no input to finish and proceed.
214+
215+
> **Tip**: You can also add or update permissions after initial setup using `a365 config permissions`.
216+
217+
### Step 10: Configuration Summary
194218

195219
Review all settings before saving:
196220

@@ -212,11 +236,12 @@ App Service Plan : a365agent-app-plan
212236
Location : westus
213237
Subscription : My Subscription (e09e22f2-9193-4f54-a335-01f59575eefd)
214238
Tenant : adfa4542-3e1e-46f5-9c70-3df0b15b3f6c
239+
Custom Permissions : 1 configured
215240
216241
Do you want to customize any derived names? (y/N):
217242
```
218243

219-
### Step 10: Name Customization (Optional)
244+
### Step 11: Name Customization (Optional)
220245

221246
Optionally customize generated names:
222247

@@ -230,7 +255,7 @@ Agent User Principal Name [agent.myagent.11140916@yourdomain.onmicrosoft.com]:
230255
Agent User Display Name [myagent Agent User]:
231256
```
232257

233-
### Step 11: Confirmation
258+
### Step 12: Confirmation
234259

235260
Final confirmation to save:
236261

0 commit comments

Comments
 (0)