internal static PnPConnection InstantiateDeviceLoginConnection(string url, bool launchBrowser, string tenantAdminUrl, PSHost host, bool disableTelemetry, AzureEnvironment azureEnvironment) { var connectionUri = new Uri(url); var scopes = new[] { $"{connectionUri.Scheme}://{connectionUri.Authority}//.default" }; // the second double slash is not a typo. var context = new ClientContext(url); GenericToken tokenResult = null; try { tokenResult = GraphToken.AcquireApplicationTokenDeviceLogin(PnPConnection.PnPManagementShellClientId, scopes, PnPConnection.DeviceLoginCallback(host, launchBrowser), azureEnvironment); } catch (MsalUiRequiredException ex) { if (ex.Classification == UiRequiredExceptionClassification.ConsentRequired) { host.UI.WriteLine("You need to provide consent to the PnP Management Shell application for your tenant. The easiest way to do this is by issueing: 'Connect-PnPOnline -Url [yoursiteur] -PnPManagementShell -LaunchBrowser'. Make sure to authenticate as a Azure administrator allowing to provide consent to the application. Follow the steps provided."); throw ex; } } var spoConnection = new PnPConnection(context, tokenResult, ConnectionType.O365, null, url.ToString(), tenantAdminUrl, PnPPSVersionTag, disableTelemetry, InitializationType.DeviceLogin) { //var spoConnection = new PnPConnection(context, ConnectionType.O365, url.ToString(), tenantAdminUrl, PnPPSVersionTag, host, disableTelemetry, InitializationType.DeviceLogin); Scopes = scopes, AzureEnvironment = azureEnvironment }; if (spoConnection != null) { spoConnection.ConnectionMethod = ConnectionMethod.DeviceLogin; } return(spoConnection); }
internal static PnPConnection InstantiateGraphDeviceLoginConnection(bool launchBrowser, PSHost host, bool disableTelemetry, AzureEnvironment azureEnvironment) { var tokenResult = GraphToken.AcquireApplicationTokenDeviceLogin(PnPConnection.PnPManagementShellClientId, new[] { "Group.Read.All", "openid", "email", "profile", "Group.ReadWrite.All", "User.Read.All", "Directory.ReadWrite.All" }, PnPConnection.DeviceLoginCallback(host, launchBrowser), azureEnvironment); var spoConnection = new PnPConnection(tokenResult, ConnectionMethod.GraphDeviceLogin, ConnectionType.O365, PnPPSVersionTag, disableTelemetry, InitializationType.GraphDeviceLogin) { Scopes = new[] { "Group.Read.All", "openid", "email", "profile", "Group.ReadWrite.All", "User.Read.All", "Directory.ReadWrite.All" }, AzureEnvironment = azureEnvironment }; return(spoConnection); }
internal static bool DisconnectProvidedService(PnPConnection connection) { //connection.ClearTokens(); Environment.SetEnvironmentVariable("PNPPSHOST", string.Empty); Environment.SetEnvironmentVariable("PNPPSSITE", string.Empty); if (connection == null) { return(false); } GraphToken.ClearCaches(); connection.Context = null; connection = null; return(true); }
/// <summary> /// Connect using the parameter set AADWITHSCOPE /// </summary> /// <param name="credentials">Credentials to authenticate with for delegated access or NULL for application permissions</param> /// <returns>PnPConnection based on the parameters provided in the parameter set</returns> private PnPConnection ConnectAadWithScope(PSCredential credentials, AzureEnvironment azureEnvironment) { // Filter out the scopes for the Microsoft Office 365 Management API var officeManagementApiScopes = Enum.GetNames(typeof(OfficeManagementApiPermission)).Select(s => s.Replace("_", ".")).Intersect(Scopes).ToArray(); // Take the remaining scopes and try requesting them from the Microsoft Graph API var graphScopes = Scopes.Except(officeManagementApiScopes).ToArray(); PnPConnection connection = null; // If we have Office 365 scopes, get a token for those first if (officeManagementApiScopes.Length > 0) { var officeManagementApiToken = credentials == null?OfficeManagementApiToken.AcquireApplicationTokenDeviceLogin(PnPConnection.PnPManagementShellClientId, officeManagementApiScopes, PnPConnection.DeviceLoginCallback(this.Host, true), azureEnvironment) : OfficeManagementApiToken.AcquireDelegatedTokenWithCredentials(PnPConnection.PnPManagementShellClientId, officeManagementApiScopes, credentials.UserName, credentials.Password); connection = PnPConnection.GetConnectionWithToken(officeManagementApiToken, TokenAudience.OfficeManagementApi, InitializationType.InteractiveLogin, credentials, disableTelemetry: NoTelemetry.ToBool()); } // If we have Graph scopes, get a token for it if (graphScopes.Length > 0) { var graphToken = credentials == null?GraphToken.AcquireApplicationTokenDeviceLogin(PnPConnection.PnPManagementShellClientId, graphScopes, PnPConnection.DeviceLoginCallback(this.Host, true), azureEnvironment) : GraphToken.AcquireDelegatedTokenWithCredentials(PnPConnection.PnPManagementShellClientId, graphScopes, credentials.UserName, credentials.Password, AzureEnvironment); // 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); } else { connection = PnPConnection.GetConnectionWithToken(graphToken, TokenAudience.MicrosoftGraph, InitializationType.InteractiveLogin, credentials, disableTelemetry: NoTelemetry.ToBool()); } } connection.Scopes = Scopes; return(connection); }
/// <summary> /// Tries to get a token for the provided audience /// </summary> /// <param name="tokenAudience">Audience to try to get a token for</param> /// <param name="orRoles">The specific roles to request access to (i.e. Group.ReadWrite.All). Optional, will use default groups assigned to clientId if not specified.</param> /// <returns><see cref="GenericToken"/> for the audience or NULL if unable to retrieve a token for the audience on the current connection</returns> internal GenericToken TryGetToken(TokenAudience tokenAudience, AzureEnvironment azureEnvironment, string[] orRoles = null, string[] andRoles = null, TokenType tokenType = TokenType.All) { GenericToken token = null; switch (tokenAudience) { case TokenAudience.MicrosoftGraph: if (ConnectionMethod == ConnectionMethod.DeviceLogin || ConnectionMethod == ConnectionMethod.GraphDeviceLogin) { var officeManagementApiScopes = Enum.GetNames(typeof(OfficeManagementApiPermission)).Select(s => s.Replace("_", ".")).Intersect(Scopes).ToArray(); // Take the remaining scopes and try requesting them from the Microsoft Graph API var scopes = Scopes.Except(officeManagementApiScopes).ToArray(); token = GraphToken.AcquireApplicationTokenDeviceLogin(PnPConnection.PnPManagementShellClientId, scopes, DeviceLoginCallback(null, false), AzureEnvironment); } else { if (!string.IsNullOrEmpty(Tenant)) { if (Certificate != null) { token = GraphToken.AcquireApplicationToken(Tenant, ClientId, Certificate, AzureEnvironment); } else if (ClientSecret != null) { token = GraphToken.AcquireApplicationToken(Tenant, ClientId, ClientSecret, AzureEnvironment); } else if (Scopes != null) { var officeManagementApiScopes = Enum.GetNames(typeof(OfficeManagementApiPermission)).Select(s => s.Replace("_", ".")).Intersect(Scopes).ToArray(); // Take the remaining scopes and try requesting them from the Microsoft Graph API var scopes = Scopes.Except(officeManagementApiScopes).ToArray(); if (scopes.Length > 0) { token = PSCredential == null?GraphToken.AcquireApplicationTokenInteractive(PnPManagementShellClientId, scopes, azureEnvironment) : GraphToken.AcquireDelegatedTokenWithCredentials(PnPManagementShellClientId, scopes, PSCredential.UserName, PSCredential.Password, azureEnvironment); } else { throw new PSSecurityException($"Access to {tokenAudience} failed because you did not connect with any permission scopes related to this service (for instance 'Group.Read.All')."); } } } } break; case TokenAudience.OfficeManagementApi: if (!string.IsNullOrEmpty(Tenant)) { if (Certificate != null) { token = OfficeManagementApiToken.AcquireApplicationToken(Tenant, ClientId, Certificate, AzureEnvironment); } else if (ClientSecret != null) { token = OfficeManagementApiToken.AcquireApplicationToken(Tenant, ClientId, ClientSecret, AzureEnvironment); } else if (Scopes != null) { var scopes = Enum.GetNames(typeof(OfficeManagementApiPermission)).Select(s => s.Replace("_", ".")).Intersect(Scopes).ToArray(); // Take the remaining scopes and try requesting them from the Microsoft Graph API if (scopes.Length > 0) { token = PSCredential == null?OfficeManagementApiToken.AcquireApplicationTokenInteractive(PnPManagementShellClientId, scopes, azureEnvironment) : OfficeManagementApiToken.AcquireDelegatedTokenWithCredentials(PnPManagementShellClientId, scopes, PSCredential.UserName, PSCredential.Password, azureEnvironment); } else { throw new PSSecurityException($"Access to {tokenAudience} failed because you did not connect with any permission scopes related to this service (for instance 'ServiceHealth.Read')."); } } } break; case TokenAudience.SharePointOnline: // This is not a token type we can request on demand return(null); } if (token != null) { var validationResults = ValidateTokenForPermissions(token, tokenAudience, orRoles, andRoles, tokenType); if (!validationResults.valid) { throw new PSSecurityException($"Access to {tokenAudience} failed because the app registration {ClientId} in tenant {Tenant} is not granted {validationResults.message}"); } return(token); } // Didn't have a token yet and unable to retrieve one return(null); }
// GET api/values public async Task <HttpResponseMessage> Get() { Dictionary <string, string> errorObj = new Dictionary <string, string>(); // OWIN middleware validated the audience and issuer, but the scope must also be validated; must contain "access_as_user". string[] addinScopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value.Split(' '); if (addinScopes.Contains("access_as_user")) { // Get the raw token that the add-in page received from the Office host. var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext; // Get the access token for MS Graph. string[] graphScopes = { "Files.Read.All" }; GraphToken result = null; try { // The AcquireTokenOnBehalfOfAsync method will initiate the "on behalf of" flow // with the Azure AD V2 endpoint. result = await GraphTokenHelper.AcquireTokenOnBehalfOfAsync(bootstrapContext.Token, graphScopes); } catch (GraphTokenException e) { // Even we have the access token from Office, it's possible the Azure AD fails the request. // Potential reasons could be: 1) Require MFA, 2) Missing consent // Why missing consent could happen? // To get an access token from Office client, user only needs to grant the "access_as_user" scope. // It doesnt mean user has granted all the required scopes. // If the required scopes have updated (for example, the developer change the scopes in http://apps.dev.microsoft.com/, // user has to consent again for those new scopes. // Therefore, the server should send the error to the client and let the client to handle it. errorObj["claims"] = e.Claims; errorObj["message"] = e.Message; errorObj["errorCode"] = e.ErrorCode; errorObj["suberror"] = e.SubError; return(SendErrorToClient(HttpStatusCode.Unauthorized, errorObj)); } catch (Exception e) { errorObj["errorCode"] = "unknown_error"; errorObj["message"] = e.Message; return(SendErrorToClient(HttpStatusCode.InternalServerError, errorObj)); } // Get the names of files and folders in OneDrive for Business by using the Microsoft Graph API. Select only properties needed. var fullOneDriveItemsUrl = GraphApiHelper.GetOneDriveItemNamesUrl("?$select=name&$top=3"); IEnumerable <OneDriveItem> filesResult; try { filesResult = await ODataHelper.GetItems <OneDriveItem>(fullOneDriveItemsUrl, result.AccessToken); } // If the token is invalid, MS Graph sends a "401 Unauthorized" error with the code // "InvalidAuthenticationToken". ASP.NET then throws a RuntimeBinderException. This // is also what happens when the token is expired, although MSAL should prevent that // from ever happening. In either case, the client should start the process over by // re-calling getAccessTokenAsync. catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e) { errorObj["errorCode"] = "invalid_graph_token"; errorObj["message"] = e.Message; return(SendErrorToClient(HttpStatusCode.Unauthorized, errorObj)); } // The returned JSON includes OData metadata and eTags that the add-in does not use. // Return to the client-side only the filenames. List <string> itemNames = new List <string>(); foreach (OneDriveItem item in filesResult) { itemNames.Add(item.Name); } var requestMessage = new HttpRequestMessage(); requestMessage.SetConfiguration(new HttpConfiguration()); var response = requestMessage.CreateResponse <List <string> >(HttpStatusCode.OK, itemNames); return(response); } else { // The token from the client does not have "access_as_user" permission. errorObj["errorCode"] = "invalid_access_token"; errorObj["message"] = "Missing access_as_user. Microsoft Office does not have permission to get Microsoft Graph data on behalf of the current user."; return(SendErrorToClient(HttpStatusCode.Unauthorized, errorObj)); } }
/// <summary> /// Tries to get a token for the provided audience /// </summary> /// <param name="tokenAudience">Audience to try to get a token for</param> /// <param name="orRoles">The specific roles to request access to (i.e. Group.ReadWrite.All). Optional, will use default groups assigned to clientId if not specified.</param> /// <returns><see cref="GenericToken"/> for the audience or NULL if unable to retrieve a token for the audience on the current connection</returns> internal GenericToken TryGetToken(TokenAudience tokenAudience, string[] orRoles = null, string[] andRoles = null, TokenType tokenType = TokenType.All) { GenericToken token = null; //Validate if we have a token already //if (AccessTokens.ContainsKey(tokenAudience)) //{ // // We have a token already, ensure it is still valid // token = AccessTokens[tokenAudience]; // if (token.ExpiresOn > DateTime.Now) // { // var validationResults = ValidateTokenForPermissions(token, tokenAudience, orRoles, andRoles, tokenType); // if (validationResults.valid) // { // return token; // } // throw new PSSecurityException($"Access to {tokenAudience} failed because the app registration {ClientId} in tenant {Tenant} is not granted {validationResults.message}"); // } // // Token was no longer valid, proceed with trying to create a new token //} // We do not have a token for the requested audience yet or it was no longer valid, try to create (a new) one switch (tokenAudience) { case TokenAudience.MicrosoftGraph: if (ConnectionMethod == ConnectionMethod.DeviceLogin || ConnectionMethod == ConnectionMethod.GraphDeviceLogin) { token = GraphToken.AcquireApplicationTokenDeviceLogin(PnPConnection.DeviceLoginClientId, Scopes, DeviceLoginCallback(null, false)); } else { if (!string.IsNullOrEmpty(Tenant)) { if (Certificate != null) { token = GraphToken.AcquireApplicationToken(Tenant, ClientId, Certificate); } else if (ClientSecret != null) { token = GraphToken.AcquireApplicationToken(Tenant, ClientId, ClientSecret); } else if (Scopes != null) { token = PSCredential == null?GraphToken.AcquireApplicationTokenInteractive(DeviceLoginClientId, Scopes) : GraphToken.AcquireDelegatedTokenWithCredentials(DeviceLoginClientId, Scopes, PSCredential.UserName, PSCredential.Password); } } } break; case TokenAudience.OfficeManagementApi: if (!string.IsNullOrEmpty(Tenant)) { if (Certificate != null) { token = OfficeManagementApiToken.AcquireApplicationToken(Tenant, ClientId, Certificate); } else if (ClientSecret != null) { token = OfficeManagementApiToken.AcquireApplicationToken(Tenant, ClientId, ClientSecret); } } break; case TokenAudience.SharePointOnline: // This is not a token type we can request on demand return(null); } if (token != null) { var validationResults = ValidateTokenForPermissions(token, tokenAudience, orRoles, andRoles, tokenType); if (!validationResults.valid) { throw new PSSecurityException($"Access to {tokenAudience} failed because the app registration {ClientId} in tenant {Tenant} is not granted {validationResults.message}"); } // Managed to create a token for the requested audience, add it to our collection with tokens //AccessTokens[tokenAudience] = token; return(token); } // Didn't have a token yet and unable to retrieve one return(null); }
/// <summary> /// Tries to get a token for the provided audience /// </summary> /// <param name="tokenAudience">Audience to try to get a token for</param> /// <param name="roles">The specific roles to request access to (i.e. Group.ReadWrite.All). Optional, will use default groups assigned to clientId if not specified.</param> /// <returns><see cref="GenericToken"/> for the audience or NULL if unable to retrieve a token for the audience on the current connection</returns> internal GenericToken TryGetToken(TokenAudience tokenAudience, string[] roles = null) { GenericToken token = null; // Validate if we have a token already if (AccessTokens.ContainsKey(tokenAudience)) { // We have a token already, ensure it is still valid token = AccessTokens[tokenAudience]; if (token.ExpiresOn > DateTime.Now) { // Token is still valid, ensure we dont have specific roles to check for or the requested roles to execute the command are present in the token if (roles == null || roles.Length == 0 || roles.Any(r => token.Roles.Contains(r))) { return(token); } if (roles != null) { // Requested role was not part of the access token, throw an exception explaining which application registration is missing which role throw new PSSecurityException($"Access to {tokenAudience} failed because the app registration {ClientId} in tenant {Tenant} is not granted {(roles.Length != 1 ? "any of " : string.Empty)}the permission{(roles.Length != 1 ? "s" : string.Empty)} {string.Join(", ", roles).TrimEnd(new[] { ',', ' ' })}"); } } // Token was no longer valid, proceed with trying to create a new token } // We do not have a token for the requested audience yet or it was no longer valid, try to create (a new) one switch (tokenAudience) { case TokenAudience.MicrosoftGraph: if (!string.IsNullOrEmpty(Tenant)) { if (Certificate != null) { token = GraphToken.AcquireToken(Tenant, ClientId, Certificate); } else if (ClientSecret != null) { token = GraphToken.AcquireToken(Tenant, ClientId, ClientSecret); } } break; case TokenAudience.OfficeManagementApi: if (!string.IsNullOrEmpty(Tenant)) { if (Certificate != null) { token = OfficeManagementApiToken.AcquireToken(Tenant, ClientId, Certificate); } else if (ClientSecret != null) { token = OfficeManagementApiToken.AcquireToken(Tenant, ClientId, ClientSecret); } } break; case TokenAudience.SharePointOnline: // This is not a token type we can request on demand return(null); } if (token != null) { // Managed to create a token for the requested audience, add it to our collection with tokens AccessTokens[tokenAudience] = token; return(token); } // Didn't have a token yet and unable to retrieve one return(null); }