modules/AWSPowerShell/Common/Internal/SSOUtils.cs (210 lines of code) (raw):

/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. * * AWS Tools for Windows (TM) PowerShell (TM) * */ using System; using System.Collections.Generic; using Amazon.Util.Internal; using Amazon.Runtime.Credentials.Internal; using Amazon.Runtime.CredentialManagement; using Amazon.Util; using System.Linq; using System.Management.Automation; using System.Threading; using Amazon.Runtime.Internal; using System.Threading.Tasks; using Amazon.Runtime; using Amazon.SSO; using Amazon.SSO.Model; namespace Amazon.PowerShell.Common.Internal { internal class SSOUtils { private const string clientName = "AWSPowerShell"; /// <summary> /// Initiates the SSO login flow for a given CredentialProfile and ssoVerificationCallback. /// The login flow is initiated by using .NET SDK's SSOTokenManager class by setting SSOTokenManagerGetTokenOptions' ssoVerificationCallback and supportsGettingNewToken to true. /// </summary> internal static async Task<SsoToken> LoginAsync(CredentialProfile profile, Action<SsoVerificationArguments> ssoVerificationCallback, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profile, supportsGettingNewToken: true, ssoVerificationCallback); return await GetTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Initiates the SSO login flow for a given CredentialProfileOptions and ssoVerificationCallback. /// The login flow is initiated by using .NET SDK's SSOTokenManager class by setting SSOTokenManagerGetTokenOptions' ssoVerificationCallback and supportsGettingNewToken to true. /// </summary> internal static async Task<SsoToken> LoginAsync(CredentialProfileOptions profileOptions, Action<SsoVerificationArguments> ssoVerificationCallback, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profileOptions, supportsGettingNewToken: true, ssoVerificationCallback); return await GetTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Initiates the SSO logout flow for a cached sso token associated with a CredentialProfile. /// </summary> internal static async Task LogoutAsync(CredentialProfile profile, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profile, supportsGettingNewToken: false, ssoVerificationCallback: null); var ssoTokenManager = new SSOTokenManager( null, new SSOTokenFileCache( CryptoUtilFactory.CryptoInstance, new FileRetriever(), new DirectoryRetriever()) ); await ssoTokenManager.LogoutAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Initiates the SSO logout flow for a cached sso token associated with a CredentialProfileOptions. /// </summary> internal static async Task LogoutAsync(CredentialProfileOptions profileOptions, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profileOptions, supportsGettingNewToken: false, ssoVerificationCallback: null); var ssoTokenManager = new SSOTokenManager( null, new SSOTokenFileCache( CryptoUtilFactory.CryptoInstance, new FileRetriever(), new DirectoryRetriever()) ); await ssoTokenManager.LogoutAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Initiates the SSO login flow for all cached sso tokens. /// </summary> internal static async Task LogoutAsync(CancellationToken cancellationToken = default) { var ssoTokenManager = new SSOTokenManager( null, new SSOTokenFileCache( CryptoUtilFactory.CryptoInstance, new FileRetriever(), new DirectoryRetriever()) ); await ssoTokenManager.LogoutAsync(ssoCacheDirectory: null, cancellationToken).ConfigureAwait(false); } /// <summary> /// Retrieves a cached token located at .aws/sso/cache for a given CredentialProfile. /// </summary> internal static async Task<SsoToken> GetCachedTokenAsync(CredentialProfile profile, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profile, supportsGettingNewToken: false, ssoVerificationCallback: null); return await GetCachedTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Retrieves a cached token located at .aws/sso/cache for a given CredentialProfileOptions. /// </summary> internal static async Task<SsoToken> GetCachedTokenAsync(CredentialProfileOptions profileOptions, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profileOptions, supportsGettingNewToken: false, ssoVerificationCallback: null); return await GetCachedTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Retrieves a cached token located at .aws/sso/cache for a given SSOTokenManagerGetTokenOptions. /// </summary> internal static async Task<SsoToken> GetCachedTokenAsync(SSOTokenManagerGetTokenOptions ssoTokenManagerGetTokenOptions, CancellationToken cancellationToken = default) { SsoToken ssoToken = null; SSOTokenFileCache ssoTokenFileCache = new SSOTokenFileCache(CryptoUtilFactory.CryptoInstance, new FileRetriever(), new DirectoryRetriever()); var ssoTokenResponse = await ssoTokenFileCache.TryGetSsoTokenAsync(ssoTokenManagerGetTokenOptions, ssoCacheDirectory: null, cancellationToken).ConfigureAwait(false); if (ssoTokenResponse.Success) { ssoToken = ssoTokenResponse.Value; } return ssoToken; } /// <summary> /// Retrieves an SSO token using .NET SDK's SSOTokenManager based on SSOTokenManagerGetTokenOptions. /// </summary> internal static async Task<SsoToken> GetTokenAsync(SSOTokenManagerGetTokenOptions ssoTokenManagerGetTokenOptions, CancellationToken cancellationToken = default) { var ssoTokenManager = new SSOTokenManager( SSOServiceClientHelpers.BuildSSOIDCClient(RegionEndpoint.GetBySystemName(ssoTokenManagerGetTokenOptions.Region), null), new SSOTokenFileCache( CryptoUtilFactory.CryptoInstance, new FileRetriever(), new DirectoryRetriever()) ); return await ssoTokenManager.GetTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Returns true if SSO Token refresh succeeded given a CredentialProfile. /// There is no good way to determine when a session session has expired. /// The only way is to try and refresh the SSO Token and check for exception. /// </summary> internal static async Task<bool> TryRefreshTokenAsync(CredentialProfile profile, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profile, supportsGettingNewToken: false, ssoVerificationCallback: null); return await TryRefreshTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); } /// <summary> /// Returns true if SSO Token refresh succeeded given SSOTokenManagerGetTokenOptions. /// There is no good way to determine when a session session has expired. /// The only way is to try and refresh the SSO Token and check for exception. /// </summary> internal static async Task<bool> TryRefreshTokenAsync(SSOTokenManagerGetTokenOptions ssoTokenManagerGetTokenOptions, CancellationToken cancellationToken = default) { bool refreshSuccessful = false; try { var accessToken = await GetTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken).ConfigureAwait(false); refreshSuccessful = true; } catch (AmazonClientException ex) { // this is expected when the token has expired. } return refreshSuccessful; } /// <summary> /// Returns true if the SSO Token has expired and SSO Login is required. /// </summary> internal static async Task<bool> IsSsoLoginRequiredAsync(CredentialProfile profile, CancellationToken cancellationToken = default) { var ssoTokenManagerGetTokenOptions = SSOUtils.BuildSSOTokenManagerGetTokenOptions(profile, supportsGettingNewToken: false, ssoVerificationCallback: null); return await IsSsoLoginRequiredAsync(ssoTokenManagerGetTokenOptions).ConfigureAwait(false); } /// <summary> /// Returns true if the SSO Token has expired and SSO Login is required. /// </summary> internal static async Task<bool> IsSsoLoginRequiredAsync(SSOTokenManagerGetTokenOptions ssoTokenManagerGetTokenOptions, CancellationToken cancellationToken = default) { var cachedSsoToken = await GetCachedTokenAsync(ssoTokenManagerGetTokenOptions, cancellationToken); if (cachedSsoToken == null) return true; if (cachedSsoToken.RegisteredClientExpired()) return true; // non-refreshable token if (cachedSsoToken.IsExpired() && !cachedSsoToken.CanRefresh()) return true; // refreshable token but session expired. // the only way to check for this condition is to try to refresh token. if (cachedSsoToken.IsExpired() && cachedSsoToken.CanRefresh()) { bool refreshTokenSucceeded = await TryRefreshTokenAsync(ssoTokenManagerGetTokenOptions).ConfigureAwait(false); if (!refreshTokenSucceeded) { return true; } } return false; } /// <summary> /// Builds SSOTokenManagerGetTokenOptions using CredentialProfile. /// </summary> internal static SSOTokenManagerGetTokenOptions BuildSSOTokenManagerGetTokenOptions(CredentialProfile profile, bool supportsGettingNewToken, Action<SsoVerificationArguments> ssoVerificationCallback) { return BuildSSOTokenManagerGetTokenOptions(profile.Options, supportsGettingNewToken, ssoVerificationCallback); } /// <summary> /// Builds SSOTokenManagerGetTokenOptions using CredentialProfileOptions. /// </summary> internal static SSOTokenManagerGetTokenOptions BuildSSOTokenManagerGetTokenOptions(CredentialProfileOptions profileOptions, bool supportsGettingNewToken, Action<SsoVerificationArguments> ssoVerificationCallback) { return new SSOTokenManagerGetTokenOptions() { ClientName = clientName, Region = profileOptions.SsoRegion, StartUrl = profileOptions.SsoStartUrl, Session = profileOptions.SsoSession, Scopes = profileOptions.SsoRegistrationScopes?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToList(), SsoVerificationCallback = ssoVerificationCallback, SupportsGettingNewToken = supportsGettingNewToken }; } /// <summary> /// Builds SSOTokenManagerGetTokenOptions using SSOAWSCredentials. /// </summary> internal static SSOTokenManagerGetTokenOptions BuildSSOTokenManagerGetTokenOptions(SSOAWSCredentials ssoAwsCredentials, bool supportsGettingNewToken, Action<SsoVerificationArguments> ssoVerificationCallback) { return new SSOTokenManagerGetTokenOptions() { ClientName = clientName, Region = ssoAwsCredentials.Region, StartUrl = ssoAwsCredentials.StartUrl, Session = ssoAwsCredentials.Options.SessionName, Scopes = ssoAwsCredentials.Options.Scopes, SsoVerificationCallback = ssoVerificationCallback, SupportsGettingNewToken = supportsGettingNewToken }; } /// <summary> /// Gets list of required properties that are missing from an existing SSO profile options. /// </summary> internal static List<string> GetSSOMissingProperties(CredentialProfileOptions options) { var missingProperties = new List<string>(); if (string.IsNullOrEmpty(options?.SsoSession)) { missingProperties.Add("sso_session"); } if (string.IsNullOrEmpty(options?.SsoAccountId)) { missingProperties.Add("sso_account_id"); } if (string.IsNullOrEmpty(options?.SsoRoleName)) { missingProperties.Add("sso_role_name"); } if (string.IsNullOrEmpty(options?.SsoStartUrl)) { missingProperties.Add("sso_start_url"); } if (string.IsNullOrEmpty(options?.SsoRegion)) { missingProperties.Add("sso_region"); } if (string.IsNullOrEmpty(options?.SsoRegistrationScopes)) { missingProperties.Add("sso_registration_scopes"); } return missingProperties; } internal static async Task<List<string>> GetAccountIdsAsync(string accessToken, string ssoRegion, CancellationToken cancellationToken = default) { var ssoClient = new AmazonSSOClient(new AnonymousAWSCredentials(), RegionEndpoint.GetBySystemName(ssoRegion)); var listAccountsRequest = new ListAccountsRequest { AccessToken = accessToken }; var accounts = await ssoClient.ListAccountsAsync(listAccountsRequest, cancellationToken).ConfigureAwait(false); return accounts.AccountList.Select(account => account.AccountId).OrderBy(a => a).ToList(); } internal static async Task<List<string>> GetAccountRolesAsync(string accountId, string accessToken, string ssoRegion, CancellationToken cancellationToken = default) { var ssoClient = new AmazonSSOClient(new AnonymousAWSCredentials(), region: RegionEndpoint.GetBySystemName(ssoRegion)); var listAccountRolesRequest = new ListAccountRolesRequest { AccountId = accountId, AccessToken = accessToken }; var accountRoles = await ssoClient.ListAccountRolesAsync(listAccountRolesRequest, cancellationToken).ConfigureAwait(false); return accountRoles.RoleList.Select(accountRole => accountRole.RoleName).OrderBy(r => r).ToList(); } } }