modules/AWSPowerShell/Common/Internal/SSOProfileMethods.cs (140 lines of code) (raw):

using Amazon.Runtime.CredentialManagement; using Amazon.Runtime.Internal.Util; using System.IO; using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Amazon.PowerShell.Utils { /// <summary> /// Utility methods to use with Credentials Profile names /// </summary> public static class SSOProfileMethods { private const string _configFileName = "config"; private const string _ssoSessionSectionName = "sso-session"; private const string _ssoRegionPropertyName = "sso_region"; private const string _ssoRegistrationScopesName = "sso_registration_scopes"; private const string _ssoStartUrlPropertyName = "sso_start_url"; private const string _ssoRegistrationScopes = "sso_registration_scopes"; private const string _regionPropertyName = "region"; private const string _ssoAccountIdPropertyName = "sso_account_id"; private const string _ssoRoleNamePropertyName = "sso_role_name"; private const string _ssoSessionPropertyName = "sso_session"; private const string ProfilePrefix = "profile"; private const string SsoSessionProfilePrefix = "sso-session"; /// <summary> /// Regex: /// - Starts with "sso-session" /// - followed by one or more whitespaces /// - followed by one or more non-whitespaces, in a group called "name" /// </summary> private static readonly Regex SsoSessionProfileName = new Regex($"^{SsoSessionProfilePrefix}\\s+(?<name>\\S+)", RegexOptions.Compiled, TimeSpan.FromSeconds(1)); /// <summary> /// Whether or not the profile name represents an SSO Session (prefixed with "sso-session") /// </summary> private static bool IsSsoSession(string profileName) => SsoSessionProfileName.IsMatch(profileName); /// <summary> /// Transforms "sso-session foo" to "foo" /// </summary> private static string GetSsoSessionFromProfileName(string profileName) { var match = SsoSessionProfileName.Match(profileName); if (!match.Success) { throw new ArgumentException($"Not a SSO Session based profile name: {profileName}", nameof(profileName)); } return match.Groups["name"].Value; } /// <summary> /// Transforms "foo" to "sso-session foo" /// </summary> private static string CreateSsoSessionProfileName(string ssoSessionName) => $"{SsoSessionProfilePrefix} {ssoSessionName}"; /// <summary> /// Transforms "foo" to "profile foo" /// </summary> private static string CreateProfileName(string profileName) => $"{ProfilePrefix} {profileName}"; private static string GetSharedConfigFilePath(this SharedCredentialsFile @this) { // Re-check if they set an explicit config file path, use that if it's set var awsConfigEnvironmentPath = Environment.GetEnvironmentVariable(SharedCredentialsFile.SharedConfigFileEnvVar); if (!string.IsNullOrEmpty(awsConfigEnvironmentPath)) { return awsConfigEnvironmentPath; } // config file will be in the same location as the credentials file and no env vars are set. // Just return the path, if the caller cares, they can figure out if it exists or not return Path.Combine(Path.GetDirectoryName(@this.FilePath), _configFileName); } /// <summary> /// Returns a ProfileIniFile of the shared config file. /// </summary> /// <param name="this">The SharedCredentialsFile to retrieve the config file for.</param> /// <returns>A ProfileIniFile instance of the config file.</returns> private static ProfileIniFile GetSharedConfigFile(this SharedCredentialsFile @this) { // Second parameter profileMarkerRequired is required to be true for config files, but not for general ini files. return new ProfileIniFile(@this.GetSharedConfigFilePath(), true); } private static void ThrowOnNullOrWhiteSpace(string name, string value) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException($"{name} must have value set.", name); } } /// <summary> /// Add the session info given. If the sso-session section already exists, update it. /// </summary> public static void RegisterSsoSession(this CredentialProfileOptions profileOptions) { SharedCredentialsFile sharedCredentialsFile = new SharedCredentialsFile(); if (profileOptions == null) { throw new ArgumentNullException(nameof(profileOptions)); } ThrowOnNullOrWhiteSpace(nameof(profileOptions.SsoSession), profileOptions.SsoSession); ThrowOnNullOrWhiteSpace(nameof(profileOptions.SsoRegion), profileOptions.SsoRegion); ThrowOnNullOrWhiteSpace(nameof(profileOptions.SsoStartUrl), profileOptions.SsoStartUrl); // Only sso_start_url and sso_region supported in sso-session sections for IAM Identity Center // Legacy profiles don't support sso_session sections, all Sso* keys defined directly in profile. var ssoSessionProperties = new SortedDictionary<string, string>() { { _ssoRegionPropertyName, profileOptions.SsoRegion }, { _ssoStartUrlPropertyName, profileOptions.SsoStartUrl }, }; if (!string.IsNullOrWhiteSpace(profileOptions.SsoRegistrationScopes)) { ssoSessionProperties.Add(_ssoRegistrationScopes, profileOptions.SsoRegistrationScopes); } var configFile = sharedCredentialsFile.GetSharedConfigFile(); configFile.EnsureSectionExists(SSOProfileMethods.CreateSsoSessionProfileName(profileOptions.SsoSession)); configFile.EditSection(profileOptions.SsoSession, true, ssoSessionProperties); // Section must already exist to edit sso-session configFile.Persist(); } /// <summary> /// Add the profile and session info given. If the profile already exists, update it. /// </summary> public static void RegisterSsoProfileAndSession(this CredentialProfile profile) { SharedCredentialsFile sharedCredentialsFile = new SharedCredentialsFile(); if (profile == null) { throw new ArgumentNullException(nameof(profile)); } var options = profile.Options; ThrowOnNullOrWhiteSpace(nameof(options.SsoSession), options.SsoSession); ThrowOnNullOrWhiteSpace(nameof(options.SsoRegion), options.SsoRegion); ThrowOnNullOrWhiteSpace(nameof(options.SsoStartUrl), options.SsoStartUrl); ThrowOnNullOrWhiteSpace(nameof(options.SsoAccountId), options.SsoAccountId); ThrowOnNullOrWhiteSpace(nameof(options.SsoRoleName), options.SsoRoleName); // Only sso_start_url and sso_region supported in sso-session sections for IAM Identity Center // Legacy profiles don't support sso_session sections, all Sso* keys defined directly in profile. var ssoSessionProperties = new SortedDictionary<string, string>() { { _ssoRegionPropertyName, options.SsoRegion }, { _ssoStartUrlPropertyName, options.SsoStartUrl }, }; var profileProperties = new SortedDictionary<string, string>() { { _ssoAccountIdPropertyName, options.SsoAccountId }, { _ssoRoleNamePropertyName, options.SsoRoleName }, { _ssoSessionPropertyName, options.SsoSession }, }; if (!string.IsNullOrWhiteSpace(options.SsoRegistrationScopes)) { ssoSessionProperties.Add(_ssoRegistrationScopes, options.SsoRegistrationScopes); } if (!string.IsNullOrWhiteSpace(profile.Region?.SystemName)) { profileProperties.Add(_regionPropertyName, profile.Region.SystemName); } var configFile = sharedCredentialsFile.GetSharedConfigFile(); configFile.EnsureSectionExists(SSOProfileMethods.CreateProfileName(profile.Name)); configFile.EnsureSectionExists(SSOProfileMethods.CreateSsoSessionProfileName(options.SsoSession)); configFile.EditSection(profile.Name, false, profileProperties); // Section must already exist to edit sso-session configFile.EditSection(options.SsoSession, true, ssoSessionProperties); configFile.Persist(); } /// <summary> /// Retrieves sso-session from the SharedCredentialsFile '~/.aws/config'. /// </summary> public static CredentialProfileOptions GetSsoSessionSection(string sessionName) { CredentialProfileOptions profileOptions = null; var sharedCredentialsFile = new SharedCredentialsFile(); var configFile = sharedCredentialsFile.GetSharedConfigFile(); if (configFile.TryGetSection(sessionName, isSsoSession: true, isServicesSection: false, out var properties, nestedProperties: out var _)) { profileOptions = new CredentialProfileOptions { SsoSession = sessionName, SsoStartUrl = properties["sso_start_url"], SsoRegistrationScopes = properties["sso_registration_scopes"], SsoRegion = properties["sso_region"] }; } return profileOptions; } } }