Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 138 additions & 57 deletions src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupCommand.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public static class ErrorCodes
public const string DeploymentAppCompileFailed = "DEPLOYMENT_APP_COMPILE_FAILED";
public const string DeploymentScopesFailed = "DEPLOYMENT_SCOPES_FAILED";
public const string DeploymentMcpFailed = "DEPLOYMENT_MCP_FAILED";
public const string HighPrivilegeScopeDetected = "HIGH_PRIVILEGE_SCOPE_DETECTED";
public const string SetupValidationFailed = "SETUP_VALIDATION_FAILED";
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Text;

namespace Microsoft.Agents.A365.DevTools.Cli.Exceptions;
Expand Down Expand Up @@ -80,7 +80,7 @@ private static string BuildMessage(string errorCode, string issueDescription, Li
sb.AppendLine();
foreach (var detail in errorDetails)
{
sb.AppendLine($" {detail}");
sb.AppendLine($" * {detail}");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Agents.A365.DevTools.Cli.Constants;

namespace Microsoft.Agents.A365.DevTools.Cli.Exceptions;

/// <summary>
/// Exception thrown when a Graph access token contains disallowed high-privilege scopes.
/// </summary>
public class GraphTokenScopeException : Agent365Exception
{
private const string IssueDescriptionText = "Graph token contains high-privilege scopes";

public GraphTokenScopeException(string scope)
: base(
errorCode: ErrorCodes.HighPrivilegeScopeDetected,
issueDescription: IssueDescriptionText,
errorDetails: new List<string> { $"Disallowed scope detected in token: {scope}" },
mitigationSteps: new List<string>
{
"Check Microsoft Graph Command Line Tools app permissions: Azure portal ? App registrations ? 'Microsoft Graph Command Line Tools' (App ID: 14d82eec-204b-4c2f-b7e8-296a70dab67e) ? API permissions.",
"Look for 'Directory.AccessAsUser.All' and remove it or replace it with a least-privilege alternative (for example 'Directory.Read.All') if appropriate.",
"Re-run the CLI and, when the browser consent prompt appears, approve only the scopes requested by the CLI.",
"Note: Removing tenant-wide admin consent for this permission may impact other tools or automation that rely on it. Verify impact before removal."
})
{
}

public override int ExitCode => 2; // Configuration / permission error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Agents.A365.DevTools.Cli.Constants;

namespace Microsoft.Agents.A365.DevTools.Cli.Exceptions;

/// <summary>
/// Validation errors that occur during `a365 setup` (user-fixable issues).
/// </summary>
public sealed class SetupValidationException : Agent365Exception
{
public override int ExitCode => 2;

public SetupValidationException(
string issueDescription,
List<string>? errorDetails = null,
List<string>? mitigationSteps = null,
Dictionary<string, string>? context = null,
Exception? innerException = null)
: base(
errorCode: ErrorCodes.SetupValidationFailed,
issueDescription: issueDescription,
errorDetails: errorDetails,
mitigationSteps: mitigationSteps,
context: context,
innerException: innerException)
{
}
}
66 changes: 57 additions & 9 deletions src/Microsoft.Agents.A365.DevTools.Cli/Models/Agent365Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,27 @@ public class Agent365Config
public List<string> Validate()
{
var errors = new List<string>();

if (string.IsNullOrWhiteSpace(TenantId)) errors.Add("tenantId is required.");
if (string.IsNullOrWhiteSpace(SubscriptionId)) errors.Add("subscriptionId is required.");
if (string.IsNullOrWhiteSpace(ResourceGroup)) errors.Add("resourceGroup is required.");
if (string.IsNullOrWhiteSpace(Location)) errors.Add("location is required.");
if (string.IsNullOrWhiteSpace(AppServicePlanName)) errors.Add("appServicePlanName is required.");
if (string.IsNullOrWhiteSpace(WebAppName)) errors.Add("webAppName is required.");

if (NeedDeployment)
{
if (string.IsNullOrWhiteSpace(SubscriptionId)) errors.Add("subscriptionId is required.");
if (string.IsNullOrWhiteSpace(ResourceGroup)) errors.Add("resourceGroup is required.");
if (string.IsNullOrWhiteSpace(Location)) errors.Add("location is required.");
if (string.IsNullOrWhiteSpace(AppServicePlanName)) errors.Add("appServicePlanName is required.");
if (string.IsNullOrWhiteSpace(WebAppName)) errors.Add("webAppName is required.");
}
else
{
// Non-Azure hosting
if (string.IsNullOrWhiteSpace(MessagingEndpoint))
errors.Add("messagingEndpoint is required when needDeployment is 'no'.");
}

if (string.IsNullOrWhiteSpace(AgentIdentityDisplayName)) errors.Add("agentIdentityDisplayName is required.");
if (string.IsNullOrWhiteSpace(DeploymentProjectPath)) errors.Add("deploymentProjectPath is required.");

// agentIdentityScopes and agentApplicationScopes are now hardcoded defaults
// botName and botDisplayName are now derived, not required in config
// Add more validation as needed (e.g., GUID format, allowed values, etc.)
Expand Down Expand Up @@ -76,6 +89,22 @@ public List<string> Validate()
[JsonPropertyName("environment")]
public string Environment { get; init; } = "preprod";

/// <summary>
/// For External hosting, this is the HTTPS messaging endpoint that Bot Framework will call.
/// For AzureAppService, this is optional; the CLI derives the endpoint from webAppName.
/// </summary>
[JsonPropertyName("messagingEndpoint")]
public string? MessagingEndpoint { get; init; }

/// <summary>
/// Whether the CLI should create and deploy an Azure Web App for this agent.
/// Backed by the 'needDeployment' config value:
/// - true (default) => CLI provisions App Service + MSI, a365 deploy app is active.
/// - false => CLI does NOT create a web app; a365 deploy app is a no-op and MessagingEndpoint must be provided.
/// </summary>
[JsonPropertyName("needDeployment")]
public bool NeedDeployment { get; init; } = true;

#endregion

#region App Service Configuration
Expand Down Expand Up @@ -163,13 +192,32 @@ public List<string> Validate()

// BotName and BotDisplayName are now derived properties
/// <summary>
/// Gets the internal name for the endpoint registration, derived from WebAppName.
/// Gets the internal name for the endpoint registration.
/// - For AzureAppService, derived from WebAppName.
/// - For non-Azure hosting, derived from MessagingEndpoint host if possible.
/// </summary>
[JsonIgnore]
public string BotName => string.IsNullOrWhiteSpace(WebAppName) ? string.Empty : $"{WebAppName}-endpoint";
public string BotName
{
get
{
if (!string.IsNullOrWhiteSpace(WebAppName))
{
return $"{WebAppName}-endpoint";
}

if (!string.IsNullOrWhiteSpace(MessagingEndpoint) &&
Uri.TryCreate(MessagingEndpoint, UriKind.Absolute, out var uri))
{
return $"{uri.Host.Replace('.', '-')}-endpoint";
}

return string.Empty;
}
}

/// <summary>
/// Gets the display name for the bot, derived from AgentBlueprintDisplayName.
/// Gets the display name for the bot, derived from AgentBlueprintDisplayName or WebAppName.
/// </summary>
[JsonIgnore]
public string BotDisplayName => !string.IsNullOrWhiteSpace(AgentBlueprintDisplayName) ? AgentBlueprintDisplayName! : WebAppName;
Expand Down Expand Up @@ -530,4 +578,4 @@ public class AtgConfiguration
public List<McpServerConfig> McpServers { get; set; } = new();
public List<string> ToolsServers { get; set; } = new();
public string Agent365ToolsEndpoint { get; set; } = string.Empty;
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.Agents.A365.DevTools.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ static async Task<int> Main(string[] args)
rootCommand.AddCommand(DevelopCommand.CreateCommand(developLogger, configService, executor, authService));
rootCommand.AddCommand(DevelopMcpCommand.CreateCommand(developLogger, toolingService));
rootCommand.AddCommand(SetupCommand.CreateCommand(setupLogger, configService, executor,
deploymentService, botConfigurator, azureValidator, webAppCreator, platformDetector));
deploymentService, botConfigurator, azureValidator, webAppCreator, platformDetector, graphApiService));
rootCommand.AddCommand(CreateInstanceCommand.CreateCommand(createInstanceLogger, configService, executor,
botConfigurator, graphApiService, azureValidator));
rootCommand.AddCommand(DeployCommand.CreateCommand(deployLogger, configService, executor,
Expand Down
Loading