// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
{
///
/// Represents a new instance of a persistence store for users, using the default implementation
/// of with a string as a primary key.
///
public class UserStore : UserStore>
{
///
/// Constructs a new instance of .
///
/// The .
/// The .
public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
///
/// Creates a new instance of a persistence store for the specified user type.
///
/// The type representing a user.
public class UserStore : UserStore
where TUser : IdentityUser, new()
{
///
/// Constructs a new instance of .
///
/// The .
/// The .
public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
///
/// Represents a new instance of a persistence store for the specified user and role types.
///
/// The type representing a user.
/// The type representing a role.
/// The type of the data context class used to access the store.
public class UserStore : UserStore
where TUser : IdentityUser
where TRole : IdentityRole
where TContext : DbContext
{
///
/// Constructs a new instance of .
///
/// The .
/// The .
public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
///
/// Represents a new instance of a persistence store for the specified user and role types.
///
/// The type representing a user.
/// The type representing a role.
/// The type of the data context class used to access the store.
/// The type of the primary key for a role.
public class UserStore : UserStore, IdentityUserRole, IdentityUserLogin, IdentityUserToken, IdentityRoleClaim>
where TUser : IdentityUser
where TRole : IdentityRole
where TContext : DbContext
where TKey : IEquatable
{
///
/// Constructs a new instance of .
///
/// The .
/// The .
public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
///
/// Represents a new instance of a persistence store for the specified user and role types.
///
/// The type representing a user.
/// The type representing a role.
/// The type of the data context class used to access the store.
/// The type of the primary key for a role.
/// The type representing a claim.
/// The type representing a user role.
/// The type representing a user external login.
/// The type representing a user token.
/// The type representing a role claim.
public class UserStore :
UserStoreBase,
IProtectedUserStore
where TUser : IdentityUser
where TRole : IdentityRole
where TContext : DbContext
where TKey : IEquatable
where TUserClaim : IdentityUserClaim, new()
where TUserRole : IdentityUserRole, new()
where TUserLogin : IdentityUserLogin, new()
where TUserToken : IdentityUserToken, new()
where TRoleClaim : IdentityRoleClaim, new()
{
///
/// Creates a new instance of the store.
///
/// The context used to access the store.
/// The used to describe store errors.
public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(describer ?? new IdentityErrorDescriber())
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Context = context;
}
///
/// Gets the database context for this store.
///
public TContext Context { get; private set; }
private DbSet UsersSet { get { return Context.Set(); } }
private DbSet Roles { get { return Context.Set(); } }
private DbSet UserClaims { get { return Context.Set(); } }
private DbSet UserRoles { get { return Context.Set(); } }
private DbSet UserLogins { get { return Context.Set(); } }
private DbSet UserTokens { get { return Context.Set(); } }
///
/// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called.
///
///
/// True if changes should be automatically persisted, otherwise false.
///
public bool AutoSaveChanges { get; set; } = true;
/// Saves the current store.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
protected Task SaveChanges(CancellationToken cancellationToken)
{
return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.CompletedTask;
}
///
/// Creates the specified in the user store.
///
/// The user to create.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the of the creation operation.
public async override Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
Context.Add(user);
await SaveChanges(cancellationToken);
return IdentityResult.Success;
}
///
/// Updates the specified in the user store.
///
/// The user to update.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the of the update operation.
public async override Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
Context.Attach(user);
user.ConcurrencyStamp = Guid.NewGuid().ToString();
Context.Update(user);
try
{
await SaveChanges(cancellationToken);
}
catch (DbUpdateConcurrencyException)
{
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityResult.Success;
}
///
/// Deletes the specified from the user store.
///
/// The user to delete.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation, containing the of the update operation.
public async override Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
Context.Remove(user);
try
{
await SaveChanges(cancellationToken);
}
catch (DbUpdateConcurrencyException)
{
return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityResult.Success;
}
///
/// 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 override Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var id = ConvertIdFromString(userId);
return UsersSet.FindAsync(new object[] { id }, cancellationToken);
}
///
/// 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 override Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
return Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken);
}
///
/// A navigation property for the users the store contains.
///
public override IQueryable Users
{
get { return UsersSet; }
}
///
/// Return a role with the normalized name if it exists.
///
/// The normalized role name.
/// The used to propagate notifications that the operation should be canceled.
/// The role if it exists.
protected override Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken)
{
return Roles.SingleOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, cancellationToken);
}
///
/// Return a user role for the userId and roleId if it exists.
///
/// The user's id.
/// The role's id.
/// The used to propagate notifications that the operation should be canceled.
/// The user role if it exists.
protected override Task FindUserRoleAsync(TKey userId, TKey roleId, CancellationToken cancellationToken)
{
return UserRoles.FindAsync(new object[] { userId, roleId }, cancellationToken);
}
///
/// Return a user with the matching userId if it exists.
///
/// The user's id.
/// The used to propagate notifications that the operation should be canceled.
/// The user if it exists.
protected override Task FindUserAsync(TKey userId, CancellationToken cancellationToken)
{
return Users.SingleOrDefaultAsync(u => u.Id.Equals(userId), cancellationToken);
}
///
/// Return a user login with the matching userId, provider, providerKey if it exists.
///
/// The user's id.
/// The login provider name.
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
/// The user login if it exists.
protected override Task FindUserLoginAsync(TKey userId, string loginProvider, string providerKey, CancellationToken cancellationToken)
{
return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.UserId.Equals(userId) && userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken);
}
///
/// Return a user login with provider, providerKey if it exists.
///
/// The login provider name.
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
/// The user login if it exists.
protected override Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)
{
return UserLogins.SingleOrDefaultAsync(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey, cancellationToken);
}
///
/// Adds the given to the specified .
///
/// The user to add the role to.
/// The role to add.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public async override Task AddToRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (string.IsNullOrWhiteSpace(normalizedRoleName))
{
throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName));
}
var roleEntity = await FindRoleAsync(normalizedRoleName, cancellationToken);
if (roleEntity == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.RoleNotFound, normalizedRoleName));
}
UserRoles.Add(CreateUserRole(user, roleEntity));
}
///
/// Removes the given from the specified .
///
/// The user to remove the role from.
/// The role to remove.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public async override Task RemoveFromRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (string.IsNullOrWhiteSpace(normalizedRoleName))
{
throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName));
}
var roleEntity = await FindRoleAsync(normalizedRoleName, cancellationToken);
if (roleEntity != null)
{
var userRole = await FindUserRoleAsync(user.Id, roleEntity.Id, cancellationToken);
if (userRole != null)
{
UserRoles.Remove(userRole);
}
}
}
///
/// Retrieves the roles the specified is a member of.
///
/// The user whose roles should be retrieved.
/// The used to propagate notifications that the operation should be canceled.
/// A that contains the roles the user is a member of.
public override async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = user.Id;
var query = from userRole in UserRoles
join role in Roles on userRole.RoleId equals role.Id
where userRole.UserId.Equals(userId)
select role.Name;
return await query.ToListAsync(cancellationToken);
}
///
/// Returns a flag indicating if the specified user is a member of the give .
///
/// The user whose role membership should be checked.
/// The role to check membership of
/// The used to propagate notifications that the operation should be canceled.
/// A containing a flag indicating if the specified user is a member of the given group. If the
/// user is a member of the group the returned value with be true, otherwise it will be false.
public override async Task IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (string.IsNullOrWhiteSpace(normalizedRoleName))
{
throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, nameof(normalizedRoleName));
}
var role = await FindRoleAsync(normalizedRoleName, cancellationToken);
if (role != null)
{
var userRole = await FindUserRoleAsync(user.Id, role.Id, cancellationToken);
return userRole != null;
}
return false;
}
///
/// Get the claims associated with the specified as an asynchronous operation.
///
/// The user whose claims should be retrieved.
/// The used to propagate notifications that the operation should be canceled.
/// A that contains the claims granted to a user.
public async override Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => c.ToClaim()).ToListAsync(cancellationToken);
}
///
/// Adds the given to the specified .
///
/// The user to add the claim to.
/// The claim to add to the user.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claims == null)
{
throw new ArgumentNullException(nameof(claims));
}
foreach (var claim in claims)
{
UserClaims.Add(CreateUserClaim(user, claim));
}
return Task.FromResult(false);
}
///
/// Replaces the on the specified , with the .
///
/// The user to replace the claim on.
/// The claim replace.
/// The new claim replacing the .
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public async override Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
if (newClaim == null)
{
throw new ArgumentNullException(nameof(newClaim));
}
var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken);
foreach (var matchedClaim in matchedClaims)
{
matchedClaim.ClaimValue = newClaim.Value;
matchedClaim.ClaimType = newClaim.Type;
}
}
///
/// Removes the given from the specified .
///
/// The user to remove the claims from.
/// The claim to remove.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public async override Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (claims == null)
{
throw new ArgumentNullException(nameof(claims));
}
foreach (var claim in claims)
{
var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken);
foreach (var c in matchedClaims)
{
UserClaims.Remove(c);
}
}
}
///
/// Adds the given to the specified .
///
/// The user to add the login to.
/// The login to add to the user.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override Task AddLoginAsync(TUser user, UserLoginInfo login,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (login == null)
{
throw new ArgumentNullException(nameof(login));
}
UserLogins.Add(CreateUserLogin(user, login));
return Task.FromResult(false);
}
///
/// Removes the given from the specified .
///
/// The user to remove the login from.
/// The login to remove from the user.
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
/// The that represents the asynchronous operation.
public override async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var entry = await FindUserLoginAsync(user.Id, loginProvider, providerKey, cancellationToken);
if (entry != null)
{
UserLogins.Remove(entry);
}
}
///
/// Retrieves the associated logins for the specified .
///
/// The user whose associated logins to retrieve.
/// The used to propagate notifications that the operation should be canceled.
///
/// The for the asynchronous operation, containing a list of for the specified , if any.
///
public async override Task> GetLoginsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = user.Id;
return await UserLogins.Where(l => l.UserId.Equals(userId))
.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(cancellationToken);
}
///
/// Retrieves the user associated with the specified login provider and login provider key.
///
/// The login provider who provided the .
/// The key provided by the to identify a user.
/// The used to propagate notifications that the operation should be canceled.
///
/// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key.
///
public async override Task FindByLoginAsync(string loginProvider, string providerKey,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var userLogin = await FindUserLoginAsync(loginProvider, providerKey, cancellationToken);
if (userLogin != null)
{
return await FindUserAsync(userLogin.UserId, cancellationToken);
}
return null;
}
///
/// Gets the user, if any, associated with the specified, normalized email address.
///
/// The normalized email address to return the user for.
/// The used to propagate notifications that the operation should be canceled.
///
/// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address.
///
public override Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken);
}
///
/// Retrieves all users with the specified claim.
///
/// The claim whose users should be retrieved.
/// The used to propagate notifications that the operation should be canceled.
///
/// The contains a list of users, if any, that contain the specified claim.
///
public async override Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (claim == null)
{
throw new ArgumentNullException(nameof(claim));
}
var query = from userclaims in UserClaims
join user in Users on userclaims.UserId equals user.Id
where userclaims.ClaimValue == claim.Value
&& userclaims.ClaimType == claim.Type
select user;
return await query.ToListAsync(cancellationToken);
}
///
/// Retrieves all users in the specified role.
///
/// The role whose users should be retrieved.
/// The used to propagate notifications that the operation should be canceled.
///
/// The contains a list of users, if any, that are in the specified role.
///
public async override Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (string.IsNullOrEmpty(normalizedRoleName))
{
throw new ArgumentNullException(nameof(normalizedRoleName));
}
var role = await FindRoleAsync(normalizedRoleName, cancellationToken);
if (role != null)
{
var query = from userrole in UserRoles
join user in Users on userrole.UserId equals user.Id
where userrole.RoleId.Equals(role.Id)
select user;
return await query.ToListAsync(cancellationToken);
}
return new List();
}
///
/// Find a user token if it exists.
///
/// The token owner.
/// The login provider for the token.
/// The name of the token.
/// The used to propagate notifications that the operation should be canceled.
/// The user token if it exists.
protected override Task FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
=> UserTokens.FindAsync(new object[] { user.Id, loginProvider, name }, cancellationToken);
///
/// Add a new user token.
///
/// The token to be added.
///
protected override Task AddUserTokenAsync(TUserToken token)
{
UserTokens.Add(token);
return Task.CompletedTask;
}
///
/// Remove a new user token.
///
/// The token to be removed.
///
protected override Task RemoveUserTokenAsync(TUserToken token)
{
UserTokens.Remove(token);
return Task.CompletedTask;
}
}
}