From 21958e0cc1f3a8250814187f8061e9e6777d864a Mon Sep 17 00:00:00 2001 From: Gautam Sheth Date: Sat, 31 Aug 2024 20:26:52 +0300 Subject: [PATCH] Fix #3717: improvements to EntraID app creation and Connect for custom Azure env (#4219) * Fix #3717: improvements to EntraID app creation and Connect for custom Azure env * Fix internals --------- Co-authored-by: Gautam Sheth --- documentation/Connect-PnPOnline.md | 2 +- documentation/Register-PnPAzureADApp.md | 28 +++++++++++++++++ ...gister-PnPEntraIDAppForInteractiveLogin.md | 30 ++++++++++++++++++- src/Commands/AzureAD/RegisterAzureADApp.cs | 24 +++++++++------ .../RegisterEntraIDAppForInteractiveLogin.cs | 22 +++++++++----- src/Commands/Base/ConnectOnline.cs | 4 +++ src/Commands/Utilities/AzureAuthHelper.cs | 12 ++++---- 7 files changed, 97 insertions(+), 25 deletions(-) diff --git a/documentation/Connect-PnPOnline.md b/documentation/Connect-PnPOnline.md index e91991742..af58501a4 100644 --- a/documentation/Connect-PnPOnline.md +++ b/documentation/Connect-PnPOnline.md @@ -307,7 +307,7 @@ The Azure environment to use for authentication, the defaults to 'Production' wh ```yaml Type: AzureEnvironment -Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, PnP Management Shell / DeviceLogin, Interactive, Access Token, Environment Variable +Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, PnP Management Shell / DeviceLogin, Interactive, Access Token, Environment Variable, Managed Identity Aliases: Accepted values: Production, PPE, China, Germany, USGovernment, USGovernmentHigh, USGovernmentDoD, Custom diff --git a/documentation/Register-PnPAzureADApp.md b/documentation/Register-PnPAzureADApp.md index c8bddbe3b..7078dc8d0 100644 --- a/documentation/Register-PnPAzureADApp.md +++ b/documentation/Register-PnPAzureADApp.md @@ -38,6 +38,8 @@ Register-PnPAzureADApp -ApplicationName [-CertificatePassword ] [-NoPopup] [-LogoFilePath ] + [-MicrosoftGraphEndPoint ] + [-EntraIDLoginEndPoint ] ``` ### Existing Certificate @@ -394,6 +396,32 @@ Position: Named Accept pipeline input: False ``` +### -EntraIDLoginEndPoint + +Sets the EntraID login endpoint to be used for creation of the app. This only works if Azure Environment parameter is set to `Custom` + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Accept pipeline input: False +``` + +### -MicrosoftGraphEndPoint + +Sets the Microsoft Graph endpoint to be used for creation of the app. This only works if Azure Environment parameter is set to `Custom` + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Accept pipeline input: False +``` + ## RELATED LINKS [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/documentation/Register-PnPEntraIDAppForInteractiveLogin.md b/documentation/Register-PnPEntraIDAppForInteractiveLogin.md index 6e8f20ddf..76db31468 100644 --- a/documentation/Register-PnPEntraIDAppForInteractiveLogin.md +++ b/documentation/Register-PnPEntraIDAppForInteractiveLogin.md @@ -4,7 +4,7 @@ schema: 2.0.0 applicable: SharePoint Online online version: https://pnp.github.io/powershell/cmdlets/Register-PnPEntraIDAppForInteractiveLogin.html external help file: PnP.PowerShell.dll-Help.xml -title: Register-PnPAzureADApp +title: Register-PnPEntraIDAppForInteractiveLogin --- # Register-PnPEntraIDAppForInteractiveLogin @@ -25,6 +25,8 @@ Register-PnPEntraIDAppForInteractiveLogin -ApplicationName [-SharePointDelegatePermissions ] [-NoPopup] [-LogoFilePath ] + [-MicrosoftGraphEndPoint ] + [-EntraIDLoginEndPoint ] ``` ### Generate App using Device Login @@ -184,6 +186,32 @@ Position: Named Accept pipeline input: False ``` +### -EntraIDLoginEndPoint + +Sets the EntraID login endpoint to be used for creation of the app. This only works if Azure Environment parameter is set to `Custom` + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Accept pipeline input: False +``` + +### -MicrosoftGraphEndPoint + +Sets the Microsoft Graph endpoint to be used for creation of the app. This only works if Azure Environment parameter is set to `Custom` + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Accept pipeline input: False +``` + ## RELATED LINKS [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/src/Commands/AzureAD/RegisterAzureADApp.cs b/src/Commands/AzureAD/RegisterAzureADApp.cs index ac0431a90..08b2ce6d3 100644 --- a/src/Commands/AzureAD/RegisterAzureADApp.cs +++ b/src/Commands/AzureAD/RegisterAzureADApp.cs @@ -94,6 +94,12 @@ public class RegisterAzureADApp : BasePSCmdlet, IDynamicParameters [Parameter(Mandatory = false)] public SwitchParameter SkipCertCreation; + [Parameter(Mandatory = false)] + public string MicrosoftGraphEndPoint; + + [Parameter(Mandatory = false)] + public string EntraIDLoginEndPoint; + protected override void ProcessRecord() { if (ParameterSpecified(nameof(Store)) && !OperatingSystem.IsWindows()) @@ -128,7 +134,7 @@ protected override void ProcessRecord() using (var authenticationManager = new AuthenticationManager()) { - loginEndPoint = authenticationManager.GetAzureADLoginEndPoint(AzureEnvironment); + loginEndPoint = authenticationManager.GetAzureADLoginEndPoint(AzureEnvironment) ?? EntraIDLoginEndPoint; } var permissionScopes = new PermissionScopes(); @@ -423,7 +429,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter) { Task.Factory.StartNew(() => { - token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment); + token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, MicrosoftGraphEndPoint); if (token == null) { messageWriter.WriteWarning("Operation cancelled or no token retrieved."); @@ -436,7 +442,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter) { Task.Factory.StartNew(() => { - token = AzureAuthHelper.AuthenticateInteractive(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, Tenant); + token = AzureAuthHelper.AuthenticateInteractive(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, Tenant, MicrosoftGraphEndPoint); if (token == null) { messageWriter.WriteWarning("Operation cancelled or no token retrieved."); @@ -460,7 +466,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter) { throw new PSArgumentException("Password is required or use -DeviceLogin or -Interactive"); } - token = AzureAuthHelper.AuthenticateAsync(Tenant, Username, Password, AzureEnvironment).GetAwaiter().GetResult(); + token = AzureAuthHelper.AuthenticateAsync(Tenant, Username, Password, AzureEnvironment, MicrosoftGraphEndPoint).GetAwaiter().GetResult(); } return token; @@ -542,7 +548,7 @@ private bool AppExists(string appName, HttpClient httpClient, string token) var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var azureApps = RestHelper.Get>(httpClient, $"{graphEndpoint}/v1.0/applications?$filter=displayName eq '{appName}'&$select=Id", token); @@ -591,7 +597,7 @@ private AzureADApp CreateApp(string loginEndPoint, HttpClient httpClient, string var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var azureApp = RestHelper.Post(httpClient, $"{graphEndpoint}/v1.0/applications", token, payload); @@ -636,7 +642,7 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var resource = scopes.FirstOrDefault(s => s.resourceAppId == PermissionScopes.ResourceAppId_Graph) != null ? $"{graphEndpoint}/.default" : "https://microsoft.sharepoint-df.com/.default"; @@ -712,12 +718,12 @@ private void SetLogo(AzureADApp azureApp, string token) { try { - WriteVerbose("Setting the logo for the En AD app"); + WriteVerbose("Setting the logo for the EntraID app"); var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var endpoint = $"{graphEndpoint}/v1.0/applications/{azureApp.Id}/logo"; diff --git a/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs b/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs index 58c7cd0ce..bb7307016 100644 --- a/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs +++ b/src/Commands/AzureAD/RegisterEntraIDAppForInteractiveLogin.cs @@ -43,6 +43,12 @@ public class RegisterEntraIDAppForInteractiveLogin : BasePSCmdlet, IDynamicParam [Parameter(Mandatory = false)] public string LogoFilePath; + [Parameter(Mandatory = false)] + public string MicrosoftGraphEndPoint; + + [Parameter(Mandatory = false)] + public string EntraIDLoginEndPoint; + protected override void ProcessRecord() { var redirectUri = "http://localhost"; @@ -60,7 +66,7 @@ protected override void ProcessRecord() using (var authenticationManager = new AuthenticationManager()) { - loginEndPoint = authenticationManager.GetAzureADLoginEndPoint(AzureEnvironment); + loginEndPoint = authenticationManager.GetAzureADLoginEndPoint(AzureEnvironment) ?? EntraIDLoginEndPoint; } var permissionScopes = new PermissionScopes(); @@ -344,7 +350,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter) { Task.Factory.StartNew(() => { - token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment); + token = AzureAuthHelper.AuthenticateDeviceLogin(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, MicrosoftGraphEndPoint); if (token == null) { messageWriter.WriteWarning("Operation cancelled or no token retrieved."); @@ -357,7 +363,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter) { Task.Factory.StartNew(() => { - token = AzureAuthHelper.AuthenticateInteractive(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, Tenant); + token = AzureAuthHelper.AuthenticateInteractive(cancellationTokenSource, messageWriter, NoPopup, AzureEnvironment, Tenant, MicrosoftGraphEndPoint); if (token == null) { messageWriter.WriteWarning("Operation cancelled or no token retrieved."); @@ -377,7 +383,7 @@ private bool AppExists(string appName, HttpClient httpClient, string token) var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var azureApps = RestHelper.Get>(httpClient, $"{graphEndpoint}/v1.0/applications?$filter=displayName eq '{appName}'&$select=Id", token); @@ -408,7 +414,7 @@ private AzureADApp CreateApp(string loginEndPoint, HttpClient httpClient, string var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var azureApp = RestHelper.Post(httpClient, $"{graphEndpoint}/v1.0/applications", token, payload); @@ -453,7 +459,7 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var resource = scopes.FirstOrDefault(s => s.resourceAppId == PermissionScopes.ResourceAppId_Graph) != null ? $"{graphEndpoint}/.default" : "https://microsoft.sharepoint-df.com/.default"; @@ -532,12 +538,12 @@ private void SetLogo(AzureADApp azureApp, string token) { try { - WriteVerbose("Setting the logo for the En AD app"); + WriteVerbose("Setting the logo for the EntraID app"); var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(AzureEnvironment)}"; if (AzureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? MicrosoftGraphEndPoint; } var endpoint = $"{graphEndpoint}/v1.0/applications/{azureApp.Id}/logo"; diff --git a/src/Commands/Base/ConnectOnline.cs b/src/Commands/Base/ConnectOnline.cs index 708c9985f..47cbeb38e 100644 --- a/src/Commands/Base/ConnectOnline.cs +++ b/src/Commands/Base/ConnectOnline.cs @@ -202,6 +202,10 @@ public class ConnectOnline : BasePSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ENVIRONMENTVARIABLE)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_OSLOGIN)] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SYSTEMASSIGNEDMANAGEDIDENTITY)] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_USERASSIGNEDMANAGEDIDENTITYBYCLIENTID)] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_USERASSIGNEDMANAGEDIDENTITYBYPRINCIPALID)] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_USERASSIGNEDMANAGEDIDENTITYBYAZURERESOURCEID)] public AzureEnvironment AzureEnvironment = AzureEnvironment.Production; // [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYCLIENTIDCLIENTSECRETAADDOMAIN)] diff --git a/src/Commands/Utilities/AzureAuthHelper.cs b/src/Commands/Utilities/AzureAuthHelper.cs index 14ddc9218..87a39ddc5 100644 --- a/src/Commands/Utilities/AzureAuthHelper.cs +++ b/src/Commands/Utilities/AzureAuthHelper.cs @@ -10,7 +10,7 @@ namespace PnP.PowerShell.Commands.Utilities public static class AzureAuthHelper { private static string CLIENTID = "1950a258-227b-4e31-a9cf-717495945fc2"; // Well-known Azure Management App Id - internal static async Task AuthenticateAsync(string tenantId, string username, SecureString password, AzureEnvironment azureEnvironment) + internal static async Task AuthenticateAsync(string tenantId, string username, SecureString password, AzureEnvironment azureEnvironment, string customGraphEndpoint = "") { if (string.IsNullOrEmpty(tenantId)) { @@ -22,13 +22,13 @@ internal static async Task AuthenticateAsync(string tenantId, string use var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}"; if (azureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? customGraphEndpoint; } return await authManager.GetAccessTokenAsync(new[] { $"{graphEndpoint}/.default" }); } } - internal static string AuthenticateDeviceLogin(CancellationTokenSource cancellationTokenSource, CmdletMessageWriter messageWriter, bool noPopup, AzureEnvironment azureEnvironment, string clientId = "1950a258-227b-4e31-a9cf-717495945fc2") + internal static string AuthenticateDeviceLogin(CancellationTokenSource cancellationTokenSource, CmdletMessageWriter messageWriter, bool noPopup, AzureEnvironment azureEnvironment, string clientId = "1950a258-227b-4e31-a9cf-717495945fc2", string customGraphEndpoint = "") { try { @@ -54,7 +54,7 @@ internal static string AuthenticateDeviceLogin(CancellationTokenSource cancellat var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}"; if (azureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? customGraphEndpoint; } return authManager.GetAccessTokenAsync(new string[] { $"{graphEndpoint}/.default" }, cancellationTokenSource.Token).GetAwaiter().GetResult(); } @@ -71,7 +71,7 @@ internal static string AuthenticateDeviceLogin(CancellationTokenSource cancellat return null; } - internal static string AuthenticateInteractive(CancellationTokenSource cancellationTokenSource, CmdletMessageWriter messageWriter, bool noPopup, AzureEnvironment azureEnvironment, string tenantId) + internal static string AuthenticateInteractive(CancellationTokenSource cancellationTokenSource, CmdletMessageWriter messageWriter, bool noPopup, AzureEnvironment azureEnvironment, string tenantId, string customGraphEndpoint = "") { try { @@ -91,7 +91,7 @@ internal static string AuthenticateInteractive(CancellationTokenSource cancellat var graphEndpoint = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}"; if (azureEnvironment == AzureEnvironment.Custom) { - graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process); + graphEndpoint = Environment.GetEnvironmentVariable("MicrosoftGraphEndPoint", EnvironmentVariableTarget.Process) ?? customGraphEndpoint; } return authManager.GetAccessTokenAsync(new string[] { $"{graphEndpoint}/.default" }, cancellationTokenSource.Token).GetAwaiter().GetResult(); }