/* * Copyright 2018 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. */ using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Threading; using Amazon.CognitoIdentityProvider; using Amazon.CognitoIdentityProvider.Model; using Amazon.Extensions.CognitoAuthentication.Util; namespace Amazon.Extensions.CognitoAuthentication { public partial class CognitoUser { /// /// The client secret for the associated client, if one is set /// private string ClientSecret { get; set; } /// /// The secret hash for the associated user, if a client secret is set /// internal string SecretHash { get; set; } /// /// The session for the associated user. /// public CognitoUserSession SessionTokens { get; set; } /// /// The CognitoDevice associated with this user, if exists /// public CognitoDevice Device { get; set; } /// /// The userID of the associated user. UserID can only be configured through the /// constructor, and once set it cannot be changed. /// public string UserID { get; private set; } /// /// The username of the associated user. Username can only be configured through the /// constructor, and once set it cannot be changed. /// public string Username { get; private set; } /// /// The user pool of the associated user. UserPool can only be configured through /// the constructor, and once set it cannot be changed. /// public CognitoUserPool UserPool { get; private set; } /// /// The clientID of the associated user. ClientID can only be configured through /// the constructor, and once set it cannot be changed. /// public string ClientID { get; private set; } /// /// The status of the associated user. /// public string Status { get; private set; } /// /// The IAmazonCognitoIdentityProvider client of the associated user. Provider can /// only be configured through the constructor, and once set it cannot be changed. /// internal IAmazonCognitoIdentityProvider Provider { get; private set; } /// /// The attributes of the associated user. /// public Dictionary Attributes { get; private set; } = new Dictionary(); /// /// The settings of the associated user. /// public Dictionary Settings { get; set; } /// /// Private property to get and set the pool name of the user pool the user /// is associated with. /// private string PoolName { get; set; } /// /// Creates an instance of CognitoUser /// /// UserID of the specified user /// ClientID associated with the user pool /// CognitoUserPool this user is associated with /// IAmazonCognitoIdentityProvider for the specified user pool /// Client secret for the specified client, if exists /// Username for user, if different from userID public CognitoUser(string userID, string clientID, CognitoUserPool pool, IAmazonCognitoIdentityProvider provider, string clientSecret = null, string status = null, string username = null, Dictionary attributes = null) { if(pool.PoolID.Contains("_")) { this.PoolName = pool.PoolID.Split('_')[1]; } else { throw new ArgumentException("Pool's poolID malformed, should be of the form _."); } this.ClientSecret = clientSecret; if (!string.IsNullOrEmpty(clientSecret)) { this.SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(userID, clientID, clientSecret); } this.UserID = userID; this.Username = userID; if (!string.IsNullOrEmpty(username)) { this.Username = username; } else { this.Username = userID; } this.Status = status; this.UserPool = pool; this.ClientID = clientID; this.SessionTokens = null; if (attributes != null) { this.Attributes = attributes; } this.Provider = provider; if (this.Provider is AmazonCognitoIdentityProviderClient eventProvider) { eventProvider.BeforeRequestEvent += CognitoAuthHelper.ServiceClientBeforeRequestEvent; } } /// /// Confirms the sign up of the associated user using the provided confirmation code /// using an asynchronous call /// /// Confirmation code sent to user via email or SMS /// Boolean specifying whether forced alias creation is desired public virtual Task ConfirmSignUpAsync(string confirmationCode, bool forcedAliasCreation) { return ConfirmSignUpAsync(confirmationCode, forcedAliasCreation, default); } /// /// Confirms the sign up of the associated user using the provided confirmation code /// using an asynchronous call /// /// Confirmation code sent to user via email or SMS /// Boolean specifying whether forced alias creation is desired /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task ConfirmSignUpAsync(string confirmationCode, bool forcedAliasCreation, CancellationToken cancellationToken) { ConfirmSignUpRequest confirmRequest = CreateConfirmSignUpRequest(confirmationCode, forcedAliasCreation); return Provider.ConfirmSignUpAsync(confirmRequest, cancellationToken); } /// /// Request to resend registration confirmation code for a user using an asynchronous call /// /// Returns the delivery details for the confirmation code request public virtual Task ResendConfirmationCodeAsync() { return ResendConfirmationCodeAsync(default); } /// /// Request to resend registration confirmation code for a user using an asynchronous call /// /// Returns the delivery details for the confirmation code request /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task ResendConfirmationCodeAsync(CancellationToken cancellationToken) { ResendConfirmationCodeRequest resendRequest = CreateResendConfirmationCodeRequest(); return Provider.ResendConfirmationCodeAsync(resendRequest, cancellationToken); } /// /// Allows the user to reset their password using an asynchronous call. Should be used in /// conjunction with the ConfirmPasswordAsync method /// public virtual Task ForgotPasswordAsync() { return ForgotPasswordAsync(default); } /// /// Allows the user to reset their password using an asynchronous call. Should be used in /// conjunction with the ConfirmPasswordAsync method /// /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task ForgotPasswordAsync(CancellationToken cancellationToken) { ForgotPasswordRequest forgotPassRequest = CreateForgotPasswordRequest(); return Provider.ForgotPasswordAsync(forgotPassRequest, cancellationToken); } /// /// Confirms the user's new password using the confirmation code sent to them using /// an asynchronous call /// /// The confirmation code sent to the user /// The new desired password for the user public virtual Task ConfirmForgotPasswordAsync(string confirmationCode, string newPassword) { return ConfirmForgotPasswordAsync(confirmationCode, newPassword, default); } /// /// Confirms the user's new password using the confirmation code sent to them using /// an asynchronous call /// /// The confirmation code sent to the user /// The new desired password for the user /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task ConfirmForgotPasswordAsync(string confirmationCode, string newPassword, CancellationToken cancellationToken) { ConfirmForgotPasswordRequest confirmResetPassRequest = CreateConfirmPasswordRequest(confirmationCode, newPassword); return Provider.ConfirmForgotPasswordAsync(confirmResetPassRequest, cancellationToken); } /// /// Allows an authenticated user to change their password using an /// asynchronous call /// /// The user's old password /// The desired new password public virtual Task ChangePasswordAsync(string oldPass, string newPass) { return ChangePasswordAsync(oldPass, newPass, default); } /// /// Allows an authenticated user to change their password using an /// asynchronous call /// /// The user's old password /// The desired new password /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task ChangePasswordAsync(string oldPass, string newPass, CancellationToken cancellationToken) { ChangePasswordRequest changePassRequest = CreateChangePasswordRequest(oldPass, newPass); return Provider.ChangePasswordAsync(changePassRequest, cancellationToken); } /// /// Gets the details for the current user using an asynchronous call /// /// Returns a tuple containing the user attributes and settings, in that order public virtual Task GetUserDetailsAsync() { return GetUserDetailsAsync(default); } /// /// Gets the details for the current user using an asynchronous call /// /// Returns a tuple containing the user attributes and settings, in that order /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task GetUserDetailsAsync(CancellationToken cancellationToken) { EnsureUserAuthenticated(); GetUserRequest getUserRequest = new GetUserRequest() { AccessToken = SessionTokens.AccessToken }; return Provider.GetUserAsync(getUserRequest, cancellationToken); } /// /// Gets the attribute verification code for the specified attribute using /// an asynchronous call /// /// Name of the attribute the verification code is being sent to. /// Should be either email or phone_number. /// Returns the delivery details for the attribute verification code request public virtual Task GetAttributeVerificationCodeAsync(string medium) { return GetAttributeVerificationCodeAsync(medium, default); } /// /// Gets the attribute verification code for the specified attribute using /// an asynchronous call /// /// Name of the attribute the verification code is being sent to. /// Should be either email or phone_number. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation /// Returns the delivery details for the attribute verification code request public virtual Task GetAttributeVerificationCodeAsync(string medium, CancellationToken cancellationToken) { GetUserAttributeVerificationCodeRequest getAttributeCodeRequest = CreateGetUserAttributeVerificationCodeRequest(medium); return Provider.GetUserAttributeVerificationCodeAsync(getAttributeCodeRequest, cancellationToken); } /// /// Sign-out from all devices associated with this user using an asynchronous call /// public virtual Task GlobalSignOutAsync() { return GlobalSignOutAsync(default); } /// /// Sign-out from all devices associated with this user using an asynchronous call /// /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task GlobalSignOutAsync(CancellationToken cancellationToken) { EnsureUserAuthenticated(); GlobalSignOutRequest globalSignOutRequest = new GlobalSignOutRequest() { AccessToken = SessionTokens.AccessToken }; SessionTokens = null; return Provider.GlobalSignOutAsync(globalSignOutRequest, cancellationToken); } /// /// Deletes the current user using an asynchronous call /// public virtual Task DeleteUserAsync() { return DeleteUserAsync(default); } /// /// Deletes the current user using an asynchronous call /// /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task DeleteUserAsync(CancellationToken cancellationToken) { EnsureUserAuthenticated(); DeleteUserRequest deleteUserRequest = new DeleteUserRequest() { AccessToken = SessionTokens.AccessToken }; return Provider.DeleteUserAsync(deleteUserRequest, cancellationToken); } /// /// Verifies the given attribute using an asynchronous call /// /// Attribute to be verified. Should either be email or phone_number /// The verification code for the attribute being verified public virtual Task VerifyAttributeAsync(string attributeName, string verificationCode) { return VerifyAttributeAsync(attributeName, verificationCode, default); } /// /// Verifies the given attribute using an asynchronous call /// /// Attribute to be verified. Should either be email or phone_number /// The verification code for the attribute being verified /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual Task VerifyAttributeAsync(string attributeName, string verificationCode, CancellationToken cancellationToken) { VerifyUserAttributeRequest verifyUserAttributeRequest = CreateVerifyUserAttributeRequest(attributeName, verificationCode); return Provider.VerifyUserAttributeAsync(verifyUserAttributeRequest, cancellationToken); } /// /// Updates the user's attributes defined in the attributes parameter (one at a time) /// using an asynchronous call /// /// The attributes to be updated public virtual async Task UpdateAttributesAsync(IDictionary attributes) { await UpdateAttributesAsync(attributes, default); } /// /// Updates the user's attributes defined in the attributes parameter (one at a time) /// using an asynchronous call /// /// The attributes to be updated /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual async Task UpdateAttributesAsync(IDictionary attributes, CancellationToken cancellationToken) { UpdateUserAttributesRequest updateUserAttributesRequest = CreateUpdateUserAttributesRequest(attributes); await Provider.UpdateUserAttributesAsync(updateUserAttributesRequest, cancellationToken).ConfigureAwait(false); //Update the local Attributes property foreach (KeyValuePair entry in attributes) { Attributes[entry.Key] = entry.Value; } } /// /// Deletes the attributes specified in the attributeNamesToDelete list using /// an asynchronous call /// /// List of attributes to delete public virtual async Task DeleteAttributesAsync(IList attributeNamesToDelete) { await DeleteAttributesAsync(attributeNamesToDelete, default); } /// /// Deletes the attributes specified in the attributeNamesToDelete list using /// an asynchronous call /// /// List of attributes to delete /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual async Task DeleteAttributesAsync(IList attributeNamesToDelete, CancellationToken cancellationToken) { DeleteUserAttributesRequest deleteUserAttributesRequest = CreateDeleteUserAttributesRequest(attributeNamesToDelete); await Provider.DeleteUserAttributesAsync(deleteUserAttributesRequest, cancellationToken).ConfigureAwait(false); //Update the local Attributes property foreach (string attribute in attributeNamesToDelete) { if (Attributes.ContainsKey(attribute)) { Attributes.Remove(attribute); } } } /// /// Sets the MFAOptions to be the settings desibed in the userSettings dictionary /// using an asynchronous call /// /// Dictionary for the user MFA settings of the form [attribute, delivery medium] public virtual async Task SetUserSettingsAsync(IDictionary userSettings) { await SetUserSettingsAsync(userSettings, default); } /// /// Sets the MFAOptions to be the settings desibed in the userSettings dictionary /// using an asynchronous call /// /// Dictionary for the user MFA settings of the form [attribute, delivery medium] /// A cancellation token that can be used by other objects or threads to receive notice of cancellation public virtual async Task SetUserSettingsAsync(IDictionary userSettings, CancellationToken cancellationToken) { SetUserSettingsRequest setUserSettingsRequest = CreateSetUserSettingsRequest(userSettings); await Provider.SetUserSettingsAsync(setUserSettingsRequest, cancellationToken).ConfigureAwait(false); //Update the local Settings property foreach (KeyValuePair entry in userSettings) { Settings[entry.Key] = entry.Value; } } /// /// Lists the CognitoDevices associated with this user using an asynchronous call /// /// Maxmimum number of devices to be returned in this call /// Token to continue earlier search /// Returns a list of CognitoDevices associated with this user [Obsolete("This method is deprecated since it only lists the first page of devices. The method ListDevicesV2Async should be used instead which allows listing additional pages of devices.")] public virtual async Task> ListDevicesAsync(int limit, string paginationToken) { return await ListDevicesAsync(limit, paginationToken, default); } /// /// Lists the CognitoDevices associated with this user using an asynchronous call /// /// Maxmimum number of devices to be returned in this call /// Token to continue earlier search /// A cancellation token that can be used by other objects or threads to receive notice of cancellation /// Returns a list of CognitoDevices associated with this user [Obsolete("This method is deprecated since it only lists the first page of devices. The method ListDevicesV2Async should be used instead which allows listing additional pages of devices.")] public virtual async Task> ListDevicesAsync(int limit, string paginationToken, CancellationToken cancellationToken) { ListDevicesRequest listDevicesRequest = CreateListDevicesRequest(limit, paginationToken); ListDevicesResponse listDevicesReponse = await Provider.ListDevicesAsync(listDevicesRequest, cancellationToken).ConfigureAwait(false); List devicesList = new List(); foreach (DeviceType device in listDevicesReponse.Devices) { devicesList.Add(new CognitoDevice(device, this)); } return devicesList; } /// /// Executes the ListDevicesAsync service call to access device types associated with this user using an asynchronous call. /// The response returned contains DeviceType objects which could be used to construct list of CognitoDevice type objects and /// a PaginationToken which could be used to access remaining device types (if any). /// /// Maxmimum number of devices to be returned in this call /// Token to continue earlier search /// Returns underlying ListDevicesResponse that contains list of DeviceType objects along with PaginationToken. public virtual async Task ListDevicesV2Async(int limit, string paginationToken) { return await ListDevicesV2Async(limit, paginationToken, default); } /// /// Executes the ListDevicesAsync service call to access device types associated with this user using an asynchronous call. /// The response returned contains DeviceType objects which could be used to construct list of CognitoDevice type objects and /// a PaginationToken which could be used to access remaining device types (if any). /// /// Maxmimum number of devices to be returned in this call /// Token to continue earlier search /// A cancellation token that can be used by other objects or threads to receive notice of cancellation /// Returns underlying ListDevicesResponse that contains list of DeviceType objects along with PaginationToken. public virtual async Task ListDevicesV2Async(int limit, string paginationToken, CancellationToken cancellationToken) { ListDevicesRequest listDevicesRequest = CreateListDevicesRequest(limit, paginationToken); return await Provider.ListDevicesAsync(listDevicesRequest, cancellationToken).ConfigureAwait(false); } /// /// Request code for authenticator app. /// /// A cancellation token that can be used by other objects or threads to receive notice of cancellation /// with secret code. public virtual async Task AssociateSoftwareTokenAsync(CancellationToken cancellationToken) { EnsureUserAuthenticated(); AssociateSoftwareTokenRequest request = new AssociateSoftwareTokenRequest { AccessToken = SessionTokens.AccessToken }; return await Provider.AssociateSoftwareTokenAsync(request, cancellationToken).ConfigureAwait(false); } /// /// Verify code from authenticator app. /// /// Code from authenticator app. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation /// which contains token verification status. public virtual async Task VerifySoftwareTokenAsync(string code, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(code)) throw new ArgumentNullException(nameof(code)); EnsureUserAuthenticated(); VerifySoftwareTokenRequest request = new VerifySoftwareTokenRequest { AccessToken = SessionTokens.AccessToken, FriendlyDeviceName = Device?.GetDeviceName(), UserCode = code }; return await Provider.VerifySoftwareTokenAsync(request, cancellationToken).ConfigureAwait(false); } /// /// Update settings for software MFA settings. /// /// Software MFA preferred at sign in. /// Enable or disable software MFA. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation /// public async Task UpdateSoftwareMfaSettingsAsync(bool isPreferred, bool isEnabled, CancellationToken cancellationToken) { EnsureUserAuthenticated(); SetUserMFAPreferenceRequest request = new SetUserMFAPreferenceRequest { AccessToken = SessionTokens.AccessToken, SoftwareTokenMfaSettings = new SoftwareTokenMfaSettingsType() { PreferredMfa = isPreferred, Enabled = isEnabled } }; await Provider.SetUserMFAPreferenceAsync(request, cancellationToken).ConfigureAwait(false); } private ConfirmSignUpRequest CreateConfirmSignUpRequest(string confirmationCode, bool forcedAliasCreation) { ConfirmSignUpRequest confirmRequest = new ConfirmSignUpRequest() { ClientId = ClientID, Username = Username, ForceAliasCreation = forcedAliasCreation, ConfirmationCode = confirmationCode }; if (!string.IsNullOrEmpty(SecretHash)) { confirmRequest.SecretHash = SecretHash; } return confirmRequest; } private ResendConfirmationCodeRequest CreateResendConfirmationCodeRequest() { ResendConfirmationCodeRequest resendRequest = new ResendConfirmationCodeRequest() { Username = Username, ClientId = ClientID }; if (!string.IsNullOrEmpty(SecretHash)) { resendRequest.SecretHash = SecretHash; } return resendRequest; } private ForgotPasswordRequest CreateForgotPasswordRequest() { ForgotPasswordRequest forgotPassRequest = new ForgotPasswordRequest() { ClientId = ClientID, Username = Username }; if (!string.IsNullOrEmpty(SecretHash)) { forgotPassRequest.SecretHash = SecretHash; } return forgotPassRequest; } private ConfirmForgotPasswordRequest CreateConfirmPasswordRequest(string confirmationCode, string newPassword) { ConfirmForgotPasswordRequest confirmResetPassRequest = new ConfirmForgotPasswordRequest() { Username = Username, ClientId = ClientID, Password = newPassword, ConfirmationCode = confirmationCode }; if (!string.IsNullOrEmpty(SecretHash)) { confirmResetPassRequest.SecretHash = SecretHash; } return confirmResetPassRequest; } private ChangePasswordRequest CreateChangePasswordRequest(string oldPass, string newPass) { EnsureUserAuthenticated(); ChangePasswordRequest changePassRequest = new ChangePasswordRequest() { PreviousPassword = oldPass, ProposedPassword = newPass, AccessToken = SessionTokens.AccessToken }; return changePassRequest; } private GetUserAttributeVerificationCodeRequest CreateGetUserAttributeVerificationCodeRequest(string attributeName) { EnsureUserAuthenticated(); GetUserAttributeVerificationCodeRequest getAttributeCodeRequest = new GetUserAttributeVerificationCodeRequest() { AccessToken = SessionTokens.AccessToken, AttributeName = attributeName }; return getAttributeCodeRequest; } /// /// Internal function that creates a CognitoUserSession based on the authentication result /// /// An authentication result during authentication flow /// Optional variable to override the refreshToken manually /// Returns a CognitoUserSession based on the authentication result private CognitoUserSession GetCognitoUserSession(AuthenticationResultType authResult, string refreshTokenOverride = null) { string idToken = authResult.IdToken; string accessToken = authResult.AccessToken; string refreshToken; DateTime currentTime = DateTime.UtcNow; if (!string.IsNullOrEmpty(refreshTokenOverride)) { refreshToken = refreshTokenOverride; } else { refreshToken = authResult.RefreshToken; } return new CognitoUserSession(idToken, accessToken, refreshToken, currentTime, currentTime.AddSeconds(authResult.ExpiresIn)); } /// /// Sign-out by making the invalidating user session /// public void SignOut() { this.SessionTokens = null; } private VerifyUserAttributeRequest CreateVerifyUserAttributeRequest(string attributeName, string verificationCode) { EnsureUserAuthenticated(); VerifyUserAttributeRequest verifyUserAttributeRequest = new VerifyUserAttributeRequest() { AttributeName = attributeName, AccessToken = SessionTokens.AccessToken, Code = verificationCode }; return verifyUserAttributeRequest; } private UpdateUserAttributesRequest CreateUpdateUserAttributesRequest(IDictionary attributes) { EnsureUserAuthenticated(); UpdateUserAttributesRequest updateUserAttributesRequest = new UpdateUserAttributesRequest() { AccessToken = SessionTokens.AccessToken, UserAttributes = CognitoAuthHelper.CreateAttributeList(attributes) }; return updateUserAttributesRequest; } private DeleteUserAttributesRequest CreateDeleteUserAttributesRequest(IList attributeNamesToDelete) { if (attributeNamesToDelete == null || attributeNamesToDelete.Count < 1) { throw new ArgumentNullException("attributeNamesToDelete cannot be null or empty.", "attributeNamesToDelete"); } EnsureUserAuthenticated(); DeleteUserAttributesRequest deleteUserAttributesRequest = new DeleteUserAttributesRequest() { AccessToken = SessionTokens.AccessToken, UserAttributeNames = new List(attributeNamesToDelete) }; return deleteUserAttributesRequest; } private SetUserSettingsRequest CreateSetUserSettingsRequest(IDictionary userSettings) { if (userSettings == null || userSettings.Count < 1) { throw new ArgumentNullException("userSettings cannot be null or empty.", "userSettings"); } EnsureUserAuthenticated(); List settingsList = new List(); foreach (KeyValuePair setting in userSettings) { settingsList.Add(new MFAOptionType() { AttributeName = setting.Key, DeliveryMedium = setting.Value }); } SetUserSettingsRequest setUserSettingsRequest = new SetUserSettingsRequest() { AccessToken = SessionTokens.AccessToken, MFAOptions = settingsList }; return setUserSettingsRequest; } private ListDevicesRequest CreateListDevicesRequest(int limit, string paginationToken) { EnsureUserAuthenticated(); ListDevicesRequest listDevicesRequest = new ListDevicesRequest() { AccessToken = SessionTokens.AccessToken, Limit = limit, PaginationToken = paginationToken }; return listDevicesRequest; } private void EnsureUserAuthenticated() { if (SessionTokens == null || !SessionTokens.IsValid()) { throw new NotAuthorizedException("User is not authenticated."); } } } }