/* * 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 Amazon.AspNetCore.Identity.Cognito.Exceptions; using Amazon.CognitoIdentityProvider; using Amazon.CognitoIdentityProvider.Model; using Amazon.Extensions.CognitoAuthentication; using Microsoft.AspNetCore.Identity; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Amazon.AspNetCore.Identity.Cognito { public partial class CognitoUserStore : IUserRoleStore where TUser : CognitoUser { #region IUserStore /// /// Finds and returns a user, if any, who has the specified . /// /// The user ID to search for. /// The used to propagate notifications that the operation should be canceled. /// /// The that represents the asynchronous operation, containing the user matching the specified if it exists. /// public virtual async Task FindByIdAsync(string userId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); try { // The userId can be the userName, the email or the phone number depending on the User Pool login policy var user = await _pool.FindByIdAsync(userId).ConfigureAwait(false); return user as TUser; } catch (AmazonCognitoIdentityProviderException e) { throw new CognitoServiceException("Failed to find the Cognito User by Id", e); } } /// /// Returns the userId associated with the . /// /// The user to retrieve the id for. /// The used to propagate notifications that the operation should be canceled. /// /// The that represents the asynchronous operation, containing the userId belonging to the matching the specified . /// public virtual Task GetUserIdAsync(TUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(user.UserID); } /// /// Returns the UserName associated with the . /// /// The user to retrieve the UserName for. /// The used to propagate notifications that the operation should be canceled. /// /// The that represents the asynchronous operation, containing the UserName belonging to the matching the specified . /// public virtual Task GetUserNameAsync(TUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(user.Username); } /// /// Registers the specified in Cognito, /// as an asynchronous operation. /// /// The user to create. /// /// The that represents the asynchronous operation, containing the /// of the operation. /// public virtual Task CreateAsync(TUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return CreateAsync(user, null, cancellationToken); } /// /// Registers the specified in Cognito, /// as an asynchronous operation. Also submits the validation data to the pre sign-up lambda trigger. /// /// The user to create. /// The validation data to be sent to the pre sign-up lambda triggers. /// /// The that represents the asynchronous operation, containing the /// of the operation. /// public virtual async Task CreateAsync(TUser user, IDictionary validationData, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); try { await _pool.AdminSignupAsync(user.UserID, user.Attributes, validationData).ConfigureAwait(false); return IdentityResult.Success; } catch (AmazonCognitoIdentityProviderException e) { return IdentityResult.Failed(_errorDescribers.CognitoServiceError("Failed to create the Cognito User", e)); } } /// /// Deletes the specified from the user store. /// /// The user to delete. /// The that represents the asynchronous operation, containing the of the update operation. public virtual async Task DeleteAsync(TUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); try { await _cognitoClient.AdminDeleteUserAsync(new AdminDeleteUserRequest { Username = user.Username, UserPoolId = _pool.PoolID }, cancellationToken).ConfigureAwait(false); return IdentityResult.Success; } catch (AmazonCognitoIdentityProviderException e) { return IdentityResult.Failed(_errorDescribers.CognitoServiceError("Failed to delete the Cognito User", e)); } } /// /// Finds and returns a user, if any, who has the specified normalized user name. /// /// The normalized user name to search for. /// The used to propagate notifications that the operation should be canceled. /// /// The that represents the asynchronous operation, containing the user matching the specified if it exists. /// public virtual Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) { return FindByIdAsync(normalizedUserName, cancellationToken); } public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken) { throw new NotSupportedException("Cognito is case-sensitive and does not support normalized user name"); } public Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken) { throw new NotSupportedException("Cognito is case-sensitive and does not support normalized user name"); } public Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken) { throw new NotSupportedException("Cognito does not allow changing username, but the preferred_username attribute is allowed to change"); } /// /// Updates the specified attributes in the user store. /// /// The user to update attributes for. /// The that represents the asynchronous operation, containing the of the update operation. public virtual async Task UpdateAsync(TUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (user == null) { throw new ArgumentNullException(nameof(user)); } try { // Only update user writable attributes. var clientConfig = await _pool.GetUserPoolClientConfiguration().ConfigureAwait(false); var newValues = clientConfig.WriteAttributes .Where(key => user.Attributes.ContainsKey(key)) .ToDictionary(key => key, key => user.Attributes[key]); await _cognitoClient.AdminUpdateUserAttributesAsync(new AdminUpdateUserAttributesRequest { UserAttributes = CreateAttributeList(newValues), Username = user.Username, UserPoolId = _pool.PoolID }, cancellationToken).ConfigureAwait(false); return IdentityResult.Success; } catch (AmazonCognitoIdentityProviderException e) { return IdentityResult.Failed(_errorDescribers.CognitoServiceError("Failed to update the Cognito User", e)); } } /// /// Internal method to convert a dictionary of user attributes to a list of AttributeType /// /// Dictionary containing attributes of type string /// Returns a List of AttributeType objects internal List CreateAttributeList(IDictionary attributeDict) { List attributeList = new List(); foreach (KeyValuePair data in attributeDict) { AttributeType attribute = new AttributeType() { Name = data.Key, Value = data.Value }; attributeList.Add(attribute); } return attributeList; } #endregion #region IUserRoleStore /// /// Add the specified to the named role. /// /// The user to add to the named role. /// The name of the role to add the user to. /// The that represents the asynchronous operation. public virtual Task AddToRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (user == null) { throw new ArgumentNullException(nameof(user)); } try { return _cognitoClient.AdminAddUserToGroupAsync(new AdminAddUserToGroupRequest { GroupName = roleName, Username = user.Username, UserPoolId = _pool.PoolID }, cancellationToken); } catch (AmazonCognitoIdentityProviderException e) { throw new CognitoServiceException("Failed to add the Cognito User to a role", e); } } /// /// Remove the specified from the named role. /// /// The user to remove the named role from. /// The name of the role to remove. /// The that represents the asynchronous operation. public virtual Task RemoveFromRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (user == null) { throw new ArgumentNullException(nameof(user)); } try { return _cognitoClient.AdminRemoveUserFromGroupAsync(new AdminRemoveUserFromGroupRequest { GroupName = roleName, Username = user.Username, UserPoolId = _pool.PoolID }, cancellationToken); } catch (AmazonCognitoIdentityProviderException e) { throw new CognitoServiceException("Failed to remove the Cognito User from a role", e); } } /// /// Gets a list of role names the specified belongs to. /// /// The user whose role names to retrieve. /// The that represents the asynchronous operation, containing a list of role names. public virtual async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (user == null) { throw new ArgumentNullException(nameof(user)); } try { // This calls retrieve ALL the groups var response = await _cognitoClient.AdminListGroupsForUserAsync(new AdminListGroupsForUserRequest { Username = user.Username, UserPoolId = _pool.PoolID }, cancellationToken).ConfigureAwait(false); return response.Groups.Select(group => group.GroupName).ToList(); } catch (AmazonCognitoIdentityProviderException e) { throw new CognitoServiceException("Failed to retrieve roles for the Cognito User", e); } } /// /// Returns a flag indicating whether the specified is a member of the given named role. /// /// The user whose role membership should be checked. /// The name of the role to be checked. /// /// The that represents the asynchronous operation, containing a flag indicating whether the specified is /// a member of the named role. /// public virtual async Task IsInRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (user == null) { throw new ArgumentNullException(nameof(user)); } var userRoles = await GetRolesAsync(user, cancellationToken).ConfigureAwait(false); return userRoles.Contains(roleName); } /// /// Returns a list of Users who are members of the named role. /// /// The name of the role whose membership should be returned. /// /// The that represents the asynchronous operation, containing a list of users who are in the named role. /// public virtual async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); try { // This calls retrieve ALL the user for a group var response = await _cognitoClient.ListUsersInGroupAsync(new ListUsersInGroupRequest { GroupName = roleName, UserPoolId = _pool.PoolID }, cancellationToken).ConfigureAwait(false); return response.Users.Select(user => _pool.GetUser(user.Username, user.UserStatus, user.Attributes.ToDictionary(att => att.Name, att => att.Value))).ToList() as IList; } catch (AmazonCognitoIdentityProviderException e) { throw new CognitoServiceException("Failed to get the Cognito Users in a role", e); } } #endregion } }