/// <summary>
        /// Creates or updates an Azure AD / Azure AD B2C application, and updates the code, using
        /// your developer credentials (from Visual Studio, Azure CLI, Azure RM PowerShell, VS Code).
        /// Use this tool in folders containing applications created with the following command:
        ///
        ///   <c>dotnet new &lt;template&gt; --auth &lt;authOption&gt; [--calls-graph] [--called-api-url &lt;URI&gt; --called-api-scopes &lt;scopes&gt;]</c>
        ///
        /// where the &lt;template&gt; is a <c>webapp</c>, <c>mvc</c>, <c>webapi</c>, <c>blazorserver</c>, <c>blazorwasm</c>.
        /// See https://aka.ms/msidentity-app-sync.
        /// </summary>
        /// <param name="tenantId">Azure AD or Azure AD B2C tenant in which to create/update the app.
        /// - If specified, the tool will create the application in the specified tenant.
        /// - Otherwise it will create the app in your home tenant.</param>
        /// <param name="username">Username to use to connect to the Azure AD or Azure AD B2C tenant.
        /// It's only needed when you are signed-in in Visual Studio, or Azure CLI with
        /// several identities. In that case, the username param is used to disambiguate
        /// which  identity to use to create the app in the tenant.</param>
        /// <param name="clientId">Client ID of an existing application from which to update the code. This is
        /// used when you don't want to register a new app, but want to configure the code
        /// from an existing application (which can also be updated by the tool if needed).
        /// You might want to also pass-in the <paramref name="clientSecret"/> if you know it.</param>
        /// <param name="folder">When specified, will analyze the application code in the specified folder.
        /// Otherwise analyzes the code in the current directory.</param>
        /// <param name="clientSecret">Client secret to use as a client credential.</param>
        /// <param name="susiPolicyId">Sign-up/Sign-in policy required for configurating
        /// a B2C application from code that was created for AAD.</param>
        /// <param name="apiClientId">Client ID of the blazorwasm hosted web API.
        /// This is only used on the case of a blazorwasm hosted application where you only
        /// want to configure the code (named after the --api-client-id blazorwasm
        /// template parameter).</param>
        /// <param name="appIdUri">The App ID Uri for the blazorwasm hosted API. It's only used
        /// on the case of a blazorwasm hosted application (named after the --app-id-uri
        /// blazorwasm template parameter).</param>
        /// <param name="unregister">Unregister the application, instead of registering it.</param>
        /// <returns></returns>
        static public async Task Main(
            string?tenantId,
            string?username,
            string?clientId,
            string?folder,
            string?clientSecret,
            string?susiPolicyId,
            string?apiClientId,
            string?appIdUri,
            bool?unregister)
        {
            // Read options
            ProvisioningToolOptions provisioningToolOptions = new ProvisioningToolOptions
            {
                Username       = username,
                ClientId       = clientId,
                ClientSecret   = clientSecret,
                TenantId       = tenantId,
                SusiPolicyId   = susiPolicyId,
                WebApiClientId = apiClientId,
                AppIdUri       = appIdUri,
                Unregister     = unregister ?? false
            };

            if (folder != null)
            {
                provisioningToolOptions.CodeFolder = folder;
            }

            AppProvisioningTool appProvisionningTool = new AppProvisioningTool(provisioningToolOptions);
            await appProvisionningTool.Run();
        }
        private TokenCredential GetTokenCredential(ProvisioningToolOptions provisioningToolOptions, string?currentApplicationTenantId)
        {
            DeveloperCredentialsReader developerCredentialsReader = new DeveloperCredentialsReader();

            return(developerCredentialsReader.GetDeveloperCredentials(
                       provisioningToolOptions.Username,
                       currentApplicationTenantId ?? provisioningToolOptions.TenantId));
        }
        private ProjectAuthenticationSettings InferApplicationParameters(
            ProvisioningToolOptions provisioningToolOptions,
            ProjectDescription projectDescription,
            IEnumerable <ProjectDescription> projectDescriptions)
        {
            CodeReader reader = new CodeReader();
            ProjectAuthenticationSettings projectSettings = reader.ReadFromFiles(provisioningToolOptions.CodeFolder, projectDescription, projectDescriptions);

            // Override with the tools options
            projectSettings.ApplicationParameters.ApplicationDisplayName ??= Path.GetFileName(provisioningToolOptions.CodeFolder);
            projectSettings.ApplicationParameters.ClientId ??= provisioningToolOptions.ClientId;
            projectSettings.ApplicationParameters.TenantId ??= provisioningToolOptions.TenantId;
            projectSettings.ApplicationParameters.CalledApiScopes ??= provisioningToolOptions.CalledApiScopes;
            if (!string.IsNullOrEmpty(provisioningToolOptions.AppIdUri))
            {
                projectSettings.ApplicationParameters.AppIdUri = provisioningToolOptions.AppIdUri;
            }
            return(projectSettings);
        }
Exemple #4
0
 /// <summary>
 /// Identifier of a project type. This is the concatenation of the framework
 /// and the project type. This is the identifier of the extension describing
 /// the authentication pieces of the project
 /// </summary>
 public static string GetProjectTypeIdentifier(this ProvisioningToolOptions provisioningToolOptions)
 {
     return($"{provisioningToolOptions.LanguageOrFramework}-{provisioningToolOptions.ProjectType}");
 }
        public async Task <ApplicationParameters?> Run()
        {
            // If needed, infer project type from code
            ProjectDescription?projectDescription = ProjectDescriptionReader.GetProjectDescription(
                ProvisioningToolOptions.ProjectTypeIdentifier,
                ProvisioningToolOptions.CodeFolder);

            if (projectDescription == null)
            {
                Console.WriteLine($"The code in {ProvisioningToolOptions.CodeFolder} wasn't recognized as supported by the tool. Rerun with --help for details.");
                return(null);
            }
            else
            {
                Console.WriteLine($"Detected project type {projectDescription.Identifier}. ");
            }

            ProjectAuthenticationSettings projectSettings = InferApplicationParameters(
                ProvisioningToolOptions,
                projectDescription,
                ProjectDescriptionReader.projectDescriptions);

            // Case of a blazorwasm hosted application. We need to create two applications:
            // - the hosted web API
            // - the SPA.
            if (projectSettings.ApplicationParameters.IsBlazorWasm && projectSettings.ApplicationParameters.IsWebApi)
            {
                // Processes the hosted web API
                ProvisioningToolOptions provisioningToolOptionsBlazorServer = ProvisioningToolOptions.Clone();
                provisioningToolOptionsBlazorServer.CodeFolder     = Path.Combine(ProvisioningToolOptions.CodeFolder, "Server");
                provisioningToolOptionsBlazorServer.ClientId       = ProvisioningToolOptions.WebApiClientId;
                provisioningToolOptionsBlazorServer.WebApiClientId = null;
                AppProvisioningTool   appProvisioningToolBlazorServer = new AppProvisioningTool(provisioningToolOptionsBlazorServer);
                ApplicationParameters?applicationParametersServer     = await appProvisioningToolBlazorServer.Run();

                /// Processes the Blazorwasm client
                ProvisioningToolOptions provisioningToolOptionsBlazorClient = ProvisioningToolOptions.Clone();
                provisioningToolOptionsBlazorClient.CodeFolder      = Path.Combine(ProvisioningToolOptions.CodeFolder, "Client");
                provisioningToolOptionsBlazorClient.WebApiClientId  = applicationParametersServer?.ClientId;
                provisioningToolOptionsBlazorClient.AppIdUri        = applicationParametersServer?.AppIdUri;
                provisioningToolOptionsBlazorClient.CalledApiScopes = $"{applicationParametersServer?.AppIdUri}/access_as_user";
                AppProvisioningTool appProvisioningToolBlazorClient = new AppProvisioningTool(provisioningToolOptionsBlazorClient);
                return(await appProvisioningToolBlazorClient.Run());
            }

            // Case where the developer wants to have a B2C application, but the created application is an AAD one. The
            // tool needs to convert it
            if (!projectSettings.ApplicationParameters.IsB2C && !string.IsNullOrEmpty(ProvisioningToolOptions.SusiPolicyId))
            {
                projectSettings = ConvertAadApplicationToB2CApplication(projectDescription, projectSettings);
            }

            // Case where there is no code for the authentication
            if (!projectSettings.ApplicationParameters.HasAuthentication)
            {
                Console.WriteLine($"Authentication is not enabled yet in this project. An app registration will " +
                                  $"be created, but the tool does not add the code yet (work in progress). ");
            }

            // Get developer credentials
            TokenCredential tokenCredential = GetTokenCredential(
                ProvisioningToolOptions,
                projectSettings.ApplicationParameters.EffectiveTenantId ?? projectSettings.ApplicationParameters.EffectiveDomain);

            // Unregister the app
            if (ProvisioningToolOptions.Unregister)
            {
                await UnregisterApplication(tokenCredential, projectSettings.ApplicationParameters);

                return(null);
            }

            // Read or provision Microsoft identity platform application
            ApplicationParameters?effectiveApplicationParameters = await ReadOrProvisionMicrosoftIdentityApplication(
                tokenCredential,
                projectSettings.ApplicationParameters);

            Summary summary = new Summary();

            // Reconciliate code configuration and app registration
            if (effectiveApplicationParameters != null)
            {
                bool appNeedsUpdate = Reconciliate(
                    projectSettings.ApplicationParameters,
                    effectiveApplicationParameters);

                // Update appp registration if needed
                if (appNeedsUpdate)
                {
                    await WriteApplicationRegistration(
                        summary,
                        effectiveApplicationParameters,
                        tokenCredential);
                }

                // Write code configuration if needed
                WriteProjectConfiguration(
                    summary,
                    projectSettings,
                    effectiveApplicationParameters);
            }

            // Summarizes what happened
            WriteSummary(summary);
            return(effectiveApplicationParameters);
        }
 public AppProvisioningTool(ProvisioningToolOptions provisioningToolOptions)
 {
     ProvisioningToolOptions = provisioningToolOptions;
 }