From 04004756dcbc5949a65a2ebb2e1c6e8862f68705 Mon Sep 17 00:00:00 2001 From: Koen Zomers Date: Sat, 20 Jun 2020 03:52:03 +0200 Subject: [PATCH 1/4] Added support for delegation permission login using -Scope parameter --- Commands/Base/ConnectOnline.cs | 38 +++++++------ Commands/Base/PnPConnection.cs | 8 +-- Commands/Graph/SetUnifiedGroup.cs | 36 ++++++++----- .../GetManagementApiAccessToken.cs | 2 +- Commands/Model/GraphToken.cs | 54 ++++++++++++++++--- Commands/Model/OfficeManagementApiToken.cs | 50 ++++++++++++++--- 6 files changed, 139 insertions(+), 49 deletions(-) diff --git a/Commands/Base/ConnectOnline.cs b/Commands/Base/ConnectOnline.cs index cd7b56781..522db8081 100644 --- a/Commands/Base/ConnectOnline.cs +++ b/Commands/Base/ConnectOnline.cs @@ -105,29 +105,33 @@ namespace SharePointPnP.PowerShell.Commands.Base Code = "PS:> Connect-PnPOnline -Scopes \"Mail.Read\",\"Files.Read\",\"ActivityFeed.Read\"", Remarks = "Connects to Azure Active Directory interactively and gets an OAuth 2.0 Access Token to consume the resources of the declared permission scopes. It will utilize the Azure Active Directory enterprise application named PnP.PowerShell with application id bb0c5778-9d5c-41ea-a4a8-8cd417b3ab71 registered by the PnP PowerShell team. If you want to connect using your own Azure Active Directory application registration, use one of the Connect-PnPOnline cmdlets using a -ClientId attribute instead and pre-assign the required permissions/scopes/roles in your application registration in Azure Active Directory. The available permission scopes for Microsoft Graph are defined at the following URL: https://docs.microsoft.com/graph/permissions-reference . If the requested scope(s) have been used with this connect cmdlet before, they will not be asked for consent again. You can request scopes from different APIs in one Connect, i.e. from Microsoft Graph and the Microsoft Office Management API. It will ask you to authenticate for each of the APIs you have listed scopes for.", SortOrder = 15)] + [CmdletExample( + Code = "PS:> Connect-PnPOnline -Scopes \"Mail.Read\",\"Files.Read\",\"ActivityFeed.Read\" -Credentials (New-Object System.Management.Automation.PSCredential (\"johndoe@contoso.onmicrosoft.com\", (ConvertTo-SecureString \"password\" -AsPlainText -Force)))", + Remarks = "Connects to Azure Active Directory using delegated permissions and gets an OAuth 2.0 Access Token to consume the resources of the declared permission scopes. It will utilize the Azure Active Directory enterprise application named PnP.PowerShell with application id bb0c5778-9d5c-41ea-a4a8-8cd417b3ab71 registered by the PnP PowerShell team. If you want to connect using your own Azure Active Directory application registration, use one of the Connect-PnPOnline cmdlets using a -ClientId attribute instead and pre-assign the required permissions/scopes/roles in your application registration in Azure Active Directory. The available permission scopes for Microsoft Graph are defined at the following URL: https://docs.microsoft.com/graph/permissions-reference . If the requested scope(s) have been used with this connect cmdlet before, they will not be asked for consent again. You can request scopes from different APIs in one Connect, i.e. from Microsoft Graph and the Microsoft Office Management API. You must have logged on interactively with the same scopes at least once without using -Credentials to allow for the permission grant dialog to show and allow constent for the user account you would like to use.", + SortOrder = 16)] #endif #endif #if !ONPREMISES [CmdletExample( Code = "PS:> Connect-PnPOnline -ClientId '' -ClientSecret '' -AADDomain 'contoso.onmicrosoft.com'", Remarks = "Connects to the Microsoft Graph API using application permissions via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/Graph.ConnectUsingAppPermissions for a sample on how to get started.", - SortOrder = 16)] + SortOrder = 17)] [CmdletExample( Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -CertificatePath c:\\absolute-path\\to\\pnp.pfx -CertificatePassword ", Remarks = "Connects to SharePoint using app-only tokens via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/SharePoint.ConnectUsingAppPermissions for a sample on how to get started.", - SortOrder = 17)] + SortOrder = 18)] [CmdletExample( Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -Thumbprint 34CFAA860E5FB8C44335A38A097C1E41EEA206AA", Remarks = "Connects to SharePoint using app-only tokens via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/SharePoint.ConnectUsingAppPermissions for a sample on how to get started.", - SortOrder = 18)] + SortOrder = 19)] [CmdletExample( Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -PEMCertificate -PEMPrivateKey -CertificatePassword ", Remarks = "Connects to SharePoint using app-only tokens via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/SharePoint.ConnectUsingAppPermissions for a sample on how to get started.", - SortOrder = 19)] + SortOrder = 20)] [CmdletExample( Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -Certificate ", Remarks = "Connects to SharePoint using app-only auth in combination with a certificate. See https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread#using-this-principal-in-your-powershell-script-using-the-pnp-sites-core-library for a sample on how to get started.", - SortOrder = 20)] + SortOrder = 21)] #endif #if ONPREMISES [CmdletExample( @@ -174,7 +178,7 @@ public class ConnectOnline : BasePSCmdlet private const string ParameterSet_DEVICELOGIN = "PnP O365 Management Shell / DeviceLogin"; private const string ParameterSet_GRAPHDEVICELOGIN = "PnP Office 365 Management Shell to the Microsoft Graph"; #if !NETSTANDARD2_1 - private const string ParameterSet_GRAPHWITHSCOPE = "Microsoft Graph using Scopes"; + private const string ParameterSet_AADWITHSCOPE = "Azure Active Directory using Scopes"; #endif private const string ParameterSet_GRAPHWITHAAD = "Microsoft Graph using Azure Active Directory"; private const string SPOManagementClientId = "9bc3ab49-b65d-410a-85ad-de819febfddc"; @@ -239,6 +243,7 @@ public class ConnectOnline : BasePSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_MAIN, HelpMessage = "Credentials of the user to connect with. Either specify a PSCredential object or a string. In case of a string value a lookup will be done to the Generic Credentials section of the Windows Credentials in the Windows Credential Manager for the correct credentials.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ADFSCREDENTIALS, HelpMessage = "Credentials of the user to connect with. Either specify a PSCredential object or a string. In case of a string value a lookup will be done to the Generic Credentials section of the Windows Credentials in the Windows Credential Manager for the correct credentials.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_AADWITHSCOPE, HelpMessage = "Credentials of the user to connect with. Either specify a PSCredential object or a string. In case of a string value a lookup will be done to the Generic Credentials section of the Windows Credentials in the Windows Credential Manager for the correct credentials.")] public CredentialPipeBind Credentials; [Parameter(Mandatory = false, ParameterSetName = ParameterSet_MAIN, HelpMessage = "If you want to connect with the current user credentials")] @@ -518,8 +523,8 @@ public class ConnectOnline : BasePSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYCLIENTIDCLIENTSECRETURL, HelpMessage = "The Azure environment to use for authentication, the defaults to 'Production' which is the main Azure environment.")] public AzureEnvironment AzureEnvironment = AzureEnvironment.Production; -#if !NETSTANDARD2_1 - [Parameter(Mandatory = true, ParameterSetName = ParameterSet_GRAPHWITHSCOPE, HelpMessage = "The array of permission scopes for the Microsoft Graph API.")] +#if !NETSTANDARD2_1 + [Parameter(Mandatory = true, ParameterSetName = ParameterSet_AADWITHSCOPE, HelpMessage = "The array of permission scopes to request from Azure Active Directory")] public string[] Scopes; #endif @@ -585,7 +590,7 @@ public class ConnectOnline : BasePSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting to a SharePoint farm using self signed certificates or using a certificate authority not trusted by this machine.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_GRAPHWITHAAD, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting through a proxy to the Microsoft Graph API which has SSL interception enabled.")] #if !NETSTANDARD2_1 - [Parameter(Mandatory = false, ParameterSetName = ParameterSet_GRAPHWITHSCOPE, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting through a proxy to the Microsoft Graph API which has SSL interception enabled.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_AADWITHSCOPE, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting through a proxy to the Microsoft Graph API which has SSL interception enabled.")] #endif [Parameter(Mandatory = false, ParameterSetName = ParameterSet_GRAPHDEVICELOGIN, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting through a proxy to the Microsoft Graph API which has SSL interception enabled.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting to a SharePoint farm using self signed certificates or using a certificate authority not trusted by this machine.")] @@ -697,8 +702,8 @@ protected void Connect() break; #if !NETSTANDARD2_1 - case ParameterSet_GRAPHWITHSCOPE: - connection = ConnectGraphWithScope(); + case ParameterSet_AADWITHSCOPE: + connection = ConnectAadWithScope(credentials); break; #endif case ParameterSet_ACCESSTOKEN: @@ -1082,10 +1087,11 @@ private PnPConnection ConnectAppOnlyAadCer() } /// - /// Connect using the parameter set GRAPHWITHSCOPE + /// Connect using the parameter set AADWITHSCOPE /// + /// Credentials to authenticate with for delegated access or NULL for application permissions /// PnPConnection based on the parameters provided in the parameter set - private PnPConnection ConnectGraphWithScope() + private PnPConnection ConnectAadWithScope(PSCredential credentials) { #if !ONPREMISES #if !NETSTANDARD2_1 @@ -1100,16 +1106,16 @@ private PnPConnection ConnectGraphWithScope() // If we have Office 365 scopes, get a token for those first if (officeManagementApiScopes.Length > 0) { - var officeManagementApiToken = OfficeManagementApiToken.AcquireTokenInteractive(MSALPnPPowerShellClientId, officeManagementApiScopes); + var officeManagementApiToken = credentials == null ? OfficeManagementApiToken.AcquireApplicationTokenInteractive(MSALPnPPowerShellClientId, officeManagementApiScopes) : OfficeManagementApiToken.AcquireDelegatedTokenWithCredentials(MSALPnPPowerShellClientId, graphScopes, credentials.UserName, credentials.Password); connection = PnPConnection.GetConnectionWithToken(officeManagementApiToken, TokenAudience.OfficeManagementApi, Host, InitializationType.InteractiveLogin, disableTelemetry: NoTelemetry.ToBool()); } // If we have Graph scopes, get a token for it if (graphScopes.Length > 0) { - var graphToken = GraphToken.AcquireTokenInteractive(MSALPnPPowerShellClientId, graphScopes); + var graphToken = credentials == null ? GraphToken.AcquireApplicationTokenInteractive(MSALPnPPowerShellClientId, graphScopes) : GraphToken.AcquireDelegatedTokenWithCredentials(MSALPnPPowerShellClientId, graphScopes, credentials.UserName, credentials.Password); - // If there's a connection already, add the Graph token to it, otherwise set up a new connection with it + // If there's a connection already, add the AAD token to it, otherwise set up a new connection with it if (connection != null) { connection.AddToken(TokenAudience.MicrosoftGraph, graphToken); diff --git a/Commands/Base/PnPConnection.cs b/Commands/Base/PnPConnection.cs index 37f8a8dc9..d1f9ffdda 100644 --- a/Commands/Base/PnPConnection.cs +++ b/Commands/Base/PnPConnection.cs @@ -153,11 +153,11 @@ internal GenericToken TryGetToken(TokenAudience tokenAudience, string[] roles = { if (Certificate != null) { - token = GraphToken.AcquireToken(Tenant, ClientId, Certificate); + token = GraphToken.AcquireApplicationToken(Tenant, ClientId, Certificate); } else if (ClientSecret != null) { - token = GraphToken.AcquireToken(Tenant, ClientId, ClientSecret); + token = GraphToken.AcquireApplicationToken(Tenant, ClientId, ClientSecret); } } break; @@ -167,11 +167,11 @@ internal GenericToken TryGetToken(TokenAudience tokenAudience, string[] roles = { if (Certificate != null) { - token = OfficeManagementApiToken.AcquireToken(Tenant, ClientId, Certificate); + token = OfficeManagementApiToken.AcquireApplicationToken(Tenant, ClientId, Certificate); } else if (ClientSecret != null) { - token = OfficeManagementApiToken.AcquireToken(Tenant, ClientId, ClientSecret); + token = OfficeManagementApiToken.AcquireApplicationToken(Tenant, ClientId, ClientSecret); } } break; diff --git a/Commands/Graph/SetUnifiedGroup.cs b/Commands/Graph/SetUnifiedGroup.cs index 7730d2395..3051a3b46 100644 --- a/Commands/Graph/SetUnifiedGroup.cs +++ b/Commands/Graph/SetUnifiedGroup.cs @@ -25,15 +25,15 @@ namespace SharePointPnP.PowerShell.Commands.Graph SortOrder = 2)] [CmdletExample( Code = @"PS:> Set-PnPUnifiedGroup -Identity $group -GroupLogoPath "".\MyLogo.png""", - Remarks = "Sets a specific Microsoft 365 Group logo.", + Remarks = "Sets a specific Microsoft 365 Group logo", SortOrder = 3)] [CmdletExample( Code = @"PS:> Set-PnPUnifiedGroup -Identity $group -IsPrivate:$false", - Remarks = "Sets a group to be Public if previously Private.", + Remarks = "Sets a group to be Public if previously Private", SortOrder = 4)] [CmdletExample( Code = @"PS:> Set-PnPUnifiedGroup -Identity $group -Owners demo@contoso.com", - Remarks = "Sets demo@contoso.com as owner of the group.", + Remarks = "Sets demo@contoso.com as owner of the group", SortOrder = 5)] [CmdletMicrosoftGraphApiPermission(MicrosoftGraphApiPermission.Group_ReadWrite_All)] public class SetUnifiedGroup : PnPGraphCmdlet @@ -56,7 +56,7 @@ public class SetUnifiedGroup : PnPGraphCmdlet [Parameter(Mandatory = false, HelpMessage = "Makes the group private when selected")] public SwitchParameter IsPrivate; - [Parameter(Mandatory = false, HelpMessage = "The path to the logo file of to set")] + [Parameter(Mandatory = false, HelpMessage = "The path to the logo file of to set. Requires Site.ReadWrite.All permissions.")] public string GroupLogoPath; [Parameter(Mandatory = false, HelpMessage = "Creates a Microsoft Teams team associated with created group")] @@ -88,16 +88,24 @@ protected override void ExecuteCmdlet() { isPrivateGroup = IsPrivate.ToBool(); } - UnifiedGroupsUtility.UpdateUnifiedGroup( - groupId: group.GroupId, - accessToken: AccessToken, - displayName: DisplayName, - description: Description, - owners: Owners, - members: Members, - groupLogo: groupLogoStream, - isPrivate: isPrivateGroup, - createTeam: CreateTeam); + try + { + UnifiedGroupsUtility.UpdateUnifiedGroup( + groupId: group.GroupId, + accessToken: AccessToken, + displayName: DisplayName, + description: Description, + owners: Owners, + members: Members, + groupLogo: groupLogoStream, + isPrivate: isPrivateGroup, + createTeam: CreateTeam); + } + catch(Exception e) + { + while (e.InnerException != null) e = e.InnerException; + WriteError(new ErrorRecord(e, "GROUPUPDATEFAILED", ErrorCategory.InvalidOperation, this)); + } } else { diff --git a/Commands/ManagementApi/GetManagementApiAccessToken.cs b/Commands/ManagementApi/GetManagementApiAccessToken.cs index cc116ed20..fc5477f32 100644 --- a/Commands/ManagementApi/GetManagementApiAccessToken.cs +++ b/Commands/ManagementApi/GetManagementApiAccessToken.cs @@ -29,7 +29,7 @@ public class GetManagementApiAccessToken : BasePSCmdlet protected override void ExecuteCmdlet() { - var officeManagementApiToken = OfficeManagementApiToken.AcquireToken(TenantId, ClientId, ClientSecret); + var officeManagementApiToken = OfficeManagementApiToken.AcquireApplicationToken(TenantId, ClientId, ClientSecret); WriteObject(officeManagementApiToken.AccessToken); } } diff --git a/Commands/Model/GraphToken.cs b/Commands/Model/GraphToken.cs index ce8399249..8f629af0b 100644 --- a/Commands/Model/GraphToken.cs +++ b/Commands/Model/GraphToken.cs @@ -1,6 +1,7 @@ using Microsoft.Identity.Client; using System; using System.Linq; +using System.Security; using System.Security.Cryptography.X509Certificates; namespace SharePointPnP.PowerShell.Commands.Model @@ -35,13 +36,13 @@ public GraphToken(string accesstoken) : base(accesstoken) } /// - /// Tries to acquire a Microsoft Graph Access Token + /// Tries to acquire an application Microsoft Graph Access Token /// /// Name of the tenant to acquire the token for (i.e. contoso.onmicrosoft.com). Required. /// ClientId to use to acquire the token. Required. /// Certificate to use to acquire the token. Required. /// instance with the token - public static GenericToken AcquireToken(string tenant, string clientId, X509Certificate2 certificate) + public static GenericToken AcquireApplicationToken(string tenant, string clientId, X509Certificate2 certificate) { if (string.IsNullOrEmpty(tenant)) { @@ -63,13 +64,13 @@ public static GenericToken AcquireToken(string tenant, string clientId, X509Cert } /// - /// Tries to acquire a Microsoft Graph Access Token + /// Tries to acquire an application Microsoft Graph Access Token /// /// Name of the tenant to acquire the token for (i.e. contoso.onmicrosoft.com). Required. /// ClientId to use to acquire the token. Required. /// Client Secret to use to acquire the token. Required. /// instance with the token - public static GenericToken AcquireToken(string tenant, string clientId, string clientSecret) + public static GenericToken AcquireApplicationToken(string tenant, string clientId, string clientSecret) { if (string.IsNullOrEmpty(tenant)) { @@ -91,18 +92,18 @@ public static GenericToken AcquireToken(string tenant, string clientId, string c } /// - /// Tries to acquire a Microsoft Graph Access Token for the provided scopes interactively by allowing the user to log in + /// Tries to acquire an application Microsoft Graph Access Token for the provided scopes interactively by allowing the user to log in /// /// ClientId to use to acquire the token. Required. /// Array with scopes that should be requested access to. Required. /// instance with the token - public static GenericToken AcquireTokenInteractive(string clientId, string[] scopes) + public static GenericToken AcquireApplicationTokenInteractive(string clientId, string[] scopes) { - if(string.IsNullOrEmpty(clientId)) + if (string.IsNullOrEmpty(clientId)) { throw new ArgumentNullException(nameof(clientId)); } - if(scopes == null || scopes.Length == 0) + if (scopes == null || scopes.Length == 0) { throw new ArgumentNullException(nameof(scopes)); } @@ -112,5 +113,42 @@ public static GenericToken AcquireTokenInteractive(string clientId, string[] sco return new GraphToken(tokenResult.AccessToken); } + + /// + /// Tries to acquire a delegated Microsoft Graph Access Token for the provided scopes using the provided credentials + /// + /// ClientId to use to acquire the token. Required. + /// Array with scopes that should be requested access to. Required. + /// The username to authenticate with. Required. + /// The password to authenticate with. Required. + /// instance with the token + public static GenericToken AcquireDelegatedTokenWithCredentials(string clientId, string[] scopes, string username, SecureString securePassword) + { + if (string.IsNullOrEmpty(clientId)) + { + throw new ArgumentNullException(nameof(clientId)); + } + if (scopes == null || scopes.Length == 0) + { + throw new ArgumentNullException(nameof(scopes)); + } + if (string.IsNullOrEmpty(username)) + { + throw new ArgumentNullException(nameof(username)); + } + if (securePassword == null || securePassword.Length == 0) + { + throw new ArgumentNullException(nameof(securePassword)); + } + + var app = PublicClientApplicationBuilder.Create(clientId) + // Delegated Graph token using credentials is only possible against organizational tenants + .WithAuthority($"{OAuthBaseUrl}organizations/") + .Build(); + + var tokenResult = app.AcquireTokenByUsernamePassword(scopes.Select(s => $"{ResourceIdentifier}/{s}").ToArray(), username, securePassword).ExecuteAsync().GetAwaiter().GetResult(); + + return new GraphToken(tokenResult.AccessToken); + } } } diff --git a/Commands/Model/OfficeManagementApiToken.cs b/Commands/Model/OfficeManagementApiToken.cs index 556d23dbc..22a1d9a66 100644 --- a/Commands/Model/OfficeManagementApiToken.cs +++ b/Commands/Model/OfficeManagementApiToken.cs @@ -1,6 +1,7 @@ using Microsoft.Identity.Client; using System; using System.Linq; +using System.Security; using System.Security.Cryptography.X509Certificates; namespace SharePointPnP.PowerShell.Commands.Model @@ -35,13 +36,13 @@ public OfficeManagementApiToken(string accesstoken) : base(accesstoken) } /// - /// Tries to acquire an Office 365 Management API Access Token + /// Tries to acquire an application Office 365 Management API Access Token /// /// Name or id of the tenant to acquire the token for (i.e. contoso.onmicrosoft.com). Required. /// ClientId to use to acquire the token. Required. /// Certificate to use to acquire the token. Required. /// instance with the token - public static GenericToken AcquireToken(string tenant, string clientId, X509Certificate2 certificate) + public static GenericToken AcquireApplicationToken(string tenant, string clientId, X509Certificate2 certificate) { if (string.IsNullOrEmpty(tenant)) { @@ -63,13 +64,13 @@ public static GenericToken AcquireToken(string tenant, string clientId, X509Cert } /// - /// Tries to acquire an Office 365 Management API Access Token + /// Tries to acquire an application Office 365 Management API Access Token /// /// Name or id of the tenant to acquire the token for (i.e. contoso.onmicrosoft.com). Required. /// ClientId to use to acquire the token. Required. /// Client Secret to use to acquire the token. Required. /// instance with the token - public static GenericToken AcquireToken(string tenant, string clientId, string clientSecret) + public static GenericToken AcquireApplicationToken(string tenant, string clientId, string clientSecret) { if (string.IsNullOrEmpty(tenant)) { @@ -91,12 +92,12 @@ public static GenericToken AcquireToken(string tenant, string clientId, string c } /// - /// Tries to acquire an Office 365 Management API Access Token for the provided scopes interactively by allowing the user to log in + /// Tries to acquire an application Office 365 Management API Access Token for the provided scopes interactively by allowing the user to log in /// /// ClientId to use to acquire the token. Required. /// Array with scopes that should be requested access to. Required. /// instance with the token - public static GenericToken AcquireTokenInteractive(string clientId, string[] scopes) + public static GenericToken AcquireApplicationTokenInteractive(string clientId, string[] scopes) { if (string.IsNullOrEmpty(clientId)) { @@ -112,5 +113,42 @@ public static GenericToken AcquireTokenInteractive(string clientId, string[] sco return new OfficeManagementApiToken(tokenResult.AccessToken); } + + /// + /// Tries to acquire a delegated Office 365 Management API Access Token for the provided scopes using the provided credentials + /// + /// ClientId to use to acquire the token. Required. + /// Array with scopes that should be requested access to. Required. + /// The username to authenticate with. Required. + /// The password to authenticate with. Required. + /// instance with the token + public static GenericToken AcquireDelegatedTokenWithCredentials(string clientId, string[] scopes, string username, SecureString securePassword) + { + if (string.IsNullOrEmpty(clientId)) + { + throw new ArgumentNullException(nameof(clientId)); + } + if (scopes == null || scopes.Length == 0) + { + throw new ArgumentNullException(nameof(scopes)); + } + if (string.IsNullOrEmpty(username)) + { + throw new ArgumentNullException(nameof(username)); + } + if (securePassword == null || securePassword.Length == 0) + { + throw new ArgumentNullException(nameof(securePassword)); + } + + var app = PublicClientApplicationBuilder.Create(clientId) + // Delegated Graph token using credentials is only possible against organizational tenants + .WithAuthority($"{OAuthBaseUrl}organizations/") + .Build(); + + var tokenResult = app.AcquireTokenByUsernamePassword(scopes.Select(s => $"{ResourceIdentifier}/{s}").ToArray(), username, securePassword).ExecuteAsync().GetAwaiter().GetResult(); + + return new GraphToken(tokenResult.AccessToken); + } } } From c7a4ae3a3fef63bbce3bb46f0bf7e633a2963dd0 Mon Sep 17 00:00:00 2001 From: Koen Zomers Date: Sat, 20 Jun 2020 04:00:02 +0200 Subject: [PATCH 2/4] Added constraints for group logo --- Commands/Graph/SetUnifiedGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commands/Graph/SetUnifiedGroup.cs b/Commands/Graph/SetUnifiedGroup.cs index 3051a3b46..fe2d51091 100644 --- a/Commands/Graph/SetUnifiedGroup.cs +++ b/Commands/Graph/SetUnifiedGroup.cs @@ -56,7 +56,7 @@ public class SetUnifiedGroup : PnPGraphCmdlet [Parameter(Mandatory = false, HelpMessage = "Makes the group private when selected")] public SwitchParameter IsPrivate; - [Parameter(Mandatory = false, HelpMessage = "The path to the logo file of to set. Requires Site.ReadWrite.All permissions.")] + [Parameter(Mandatory = false, HelpMessage = "The path to the logo file of to set. Logo must be at least 48 pixels wide and may be at most 4 MB in size. Requires Site.ReadWrite.All permissions.")] public string GroupLogoPath; [Parameter(Mandatory = false, HelpMessage = "Creates a Microsoft Teams team associated with created group")] From d78fa35102dfde0340fc6c97ccd08d1f07d59e3a Mon Sep 17 00:00:00 2001 From: Koen Zomers Date: Sat, 20 Jun 2020 04:01:32 +0200 Subject: [PATCH 3/4] Documentation fix --- Commands/Model/OfficeManagementApiToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commands/Model/OfficeManagementApiToken.cs b/Commands/Model/OfficeManagementApiToken.cs index 22a1d9a66..77ecb74f8 100644 --- a/Commands/Model/OfficeManagementApiToken.cs +++ b/Commands/Model/OfficeManagementApiToken.cs @@ -121,7 +121,7 @@ public static GenericToken AcquireApplicationTokenInteractive(string clientId, s /// Array with scopes that should be requested access to. Required. /// The username to authenticate with. Required. /// The password to authenticate with. Required. - /// instance with the token + /// instance with the token public static GenericToken AcquireDelegatedTokenWithCredentials(string clientId, string[] scopes, string username, SecureString securePassword) { if (string.IsNullOrEmpty(clientId)) From 9f4210eab1f05ebfefa8c02c2b5f129952ac97d2 Mon Sep 17 00:00:00 2001 From: Koen Zomers Date: Sun, 21 Jun 2020 03:10:47 +0200 Subject: [PATCH 4/4] Added changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9bc823a3..05804b4f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [3.23.2007.0] (not yet released) ### Added +- Added connection option to `Connect-PnPOnline` taking `-Scopes` and `-Credentials` to allow setting up a delegated permission token for use with Microsoft Graph and the Office 365 Management API. See [this wiki page](https://github.com/pnp/PnP-PowerShell/wiki/Connect-options#connect-using-scopes-and-credentials) for more details. [PR #2746](https://github.com/pnp/PnP-PowerShell/pull/2746) ### Changed ### Contributors - Gautam Sheth [gautamdsheth] +- Koen Zomers [koenzomers] ## [3.22.2006.2]