503 lines
16 KiB
C#
503 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Linq;
|
|
using System.Security.Claims;
|
|
using System.Threading.Tasks;
|
|
using IdentityModel;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using xCommons.Constants;
|
|
using xCommons.Extensions;
|
|
using xExceptions.Constants;
|
|
using xIdentityModels;
|
|
using xIdentityModels.Constants;
|
|
using xIdentityModels.Dtos;
|
|
using xIdentityModels.Extensions;
|
|
using xIdentityModels.Navigations;
|
|
|
|
namespace xIds.Providers
|
|
{
|
|
public partial class XIdentityManager
|
|
{
|
|
//
|
|
#region User Handlers ...
|
|
/// <summary>
|
|
/// Check a User Exists or not
|
|
/// </summary>
|
|
/// <param name="userSelectByParam">specified user's identifier</param>
|
|
/// <returns>a boolean value</returns>
|
|
public async Task<bool> IsUserExistsAsync(
|
|
string userSelectByParam
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
if (userSelectByParam.IsNullOrEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
var selectType = GetUserSelectByType(userSelectByParam);
|
|
|
|
//
|
|
var result = false;
|
|
XUser user = null;
|
|
switch (selectType)
|
|
{
|
|
case XUserSelectBy.MobileNumber:
|
|
user = await GetUsersDbSet()
|
|
.Where(ur => ur.PhoneNumber == userSelectByParam)
|
|
.FirstOrDefaultAsync();
|
|
break;
|
|
|
|
case XUserSelectBy.Email:
|
|
user = await UserManager
|
|
.FindByEmailAsync(userSelectByParam);
|
|
break;
|
|
|
|
case XUserSelectBy.Username:
|
|
user = await UserManager
|
|
.FindByNameAsync(userSelectByParam);
|
|
break;
|
|
|
|
case XUserSelectBy.ID:
|
|
default:
|
|
user = await UserManager
|
|
.FindByIdAsync(userSelectByParam);
|
|
break;
|
|
}
|
|
|
|
//
|
|
result = user != null;
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check a User Selector is Available for Registration or Not
|
|
/// </summary>
|
|
/// <param name="userSelectByParam">specified user's identifier</param>
|
|
/// <returns>a boolean value</returns>
|
|
public async Task<bool> CanRegister(
|
|
string userSelectByParam
|
|
)
|
|
{
|
|
//
|
|
// Validate ARgs ...
|
|
ValidationProvider.NotEmpty(userSelectByParam);
|
|
|
|
//
|
|
var selectByType = GetUserSelectByType(userSelectByParam);
|
|
|
|
//
|
|
if (selectByType == XUserSelectBy.Username &&
|
|
!ValidateUserName(userSelectByParam))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
var isExists = await IsUserExistsAsync(userSelectByParam);
|
|
if (isExists)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a User object
|
|
/// </summary>
|
|
/// <param name="userSelectByParam">specified user's identifier</param>
|
|
/// <param name="containDetails">specifies returned object contains all Navigation Properties or not, default is false</param>
|
|
/// <returns>an instance of <see>XUser</see></returns>
|
|
public async Task<XUser> GetUserAsync(
|
|
string userSelectByParam,
|
|
bool containDetails = false
|
|
)
|
|
{
|
|
//
|
|
var isExists = await IsUserExistsAsync(userSelectByParam);
|
|
if (!isExists)
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Fill User ...
|
|
var selectType = GetUserSelectByType(userSelectByParam);
|
|
XUser user = null;
|
|
var usersStore = GetUsersDbSet(containDetails);
|
|
|
|
//
|
|
// Select Type ...
|
|
switch (selectType)
|
|
{
|
|
case XUserSelectBy.MobileNumber:
|
|
user = await usersStore
|
|
.FirstOrDefaultAsync(ur => ur.PhoneNumber ==
|
|
userSelectByParam);
|
|
break;
|
|
|
|
case XUserSelectBy.Email:
|
|
user = await usersStore
|
|
.FirstOrDefaultAsync(ur => ur.Email ==
|
|
userSelectByParam);
|
|
break;
|
|
|
|
case XUserSelectBy.Username:
|
|
user = await usersStore
|
|
.FirstOrDefaultAsync(ur => ur.UserName ==
|
|
userSelectByParam);
|
|
break;
|
|
|
|
case XUserSelectBy.ID:
|
|
default:
|
|
user = await usersStore
|
|
.FirstOrDefaultAsync(ur => ur.Id ==
|
|
userSelectByParam);
|
|
break;
|
|
}
|
|
|
|
//
|
|
return user;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve User Names based on UserIds
|
|
/// </summary>
|
|
/// <param name="userIds">a collection of user identifiers</param>
|
|
/// <returns>a collection of Usernames</returns>
|
|
public async Task<IEnumerable<string>> GetUserNamesAsync(
|
|
IEnumerable<string> userIds
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
if (userIds == null ||
|
|
userIds.Count() == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
//
|
|
var result = await GetUsersDbSet()
|
|
.Where(
|
|
xu =>
|
|
userIds
|
|
.Contains(xu.Id))
|
|
.Select(xt => xt.UserName)
|
|
.ToListAsync();
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// get user ids and retrieve corresponding user names
|
|
/// </summary>
|
|
/// <param name="model">an instance of <see>XUserNameIdRequest</see> which represent required UserIds collection</param>
|
|
/// <returns>a collection of <see>XUserNameIdResponse</see> instances</returns>
|
|
public async Task<IEnumerable<XUserNameIdResponse>> GetUserNameIdsAsync(
|
|
XUserNameIdRequest model
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
if (model.IsNull() || !model.Ids.HasChild())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
//
|
|
var result = await GetUsersDbSet()
|
|
.Where(
|
|
xu =>
|
|
model.Ids.Contains(xu.Id) ||
|
|
model.Ids.Contains(xu.Email) ||
|
|
model.Ids.Contains(xu.UserName) ||
|
|
model.Ids.Contains(xu.PhoneNumber))
|
|
.Select(xt => new XUserNameIdResponse
|
|
{
|
|
Id = xt.Id,
|
|
UserName = xt.UserName,
|
|
})
|
|
.ToListAsync();
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a User
|
|
/// </summary>
|
|
/// <param name="user">an instance of <see>XUser</see></param>
|
|
/// <param name="password">user's password</param>
|
|
/// <param name="checkCanLoginPolicies">check a user can log in in system or not, default is false</param>
|
|
/// <param name="checkIsBanned">check user is banned or not, default is false</param>
|
|
/// <returns>an instance of <see>IdentityResult</see></returns>
|
|
public async Task<IdentityResult> CreateUserAsync(
|
|
XUser user,
|
|
string password,
|
|
bool checkCanLoginPolicies = false,
|
|
bool checkIsBanned = false
|
|
)
|
|
{
|
|
//
|
|
if (user == null ||
|
|
password.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
// Get User Select By param
|
|
var userSelectByParam = GetUserSelectByParam(user, excludes: new List<XUserSelectBy> { XUserSelectBy.ID });
|
|
|
|
//
|
|
// Check User Created before or not ...
|
|
var isExists = await IsUserExistsAsync(userSelectByParam);
|
|
if (isExists)
|
|
{
|
|
XException.Duplicate.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await UserManager.CreateAsync(user, password);
|
|
if (result.Succeeded)
|
|
{
|
|
//
|
|
var claims = await ToJwtClaims(
|
|
user,
|
|
checkCanLoginPolicies: checkCanLoginPolicies,
|
|
checkIsBanned: checkIsBanned);
|
|
|
|
//
|
|
var addClaimsResult = await UserManager.AddClaimsAsync(user, claims);
|
|
|
|
//
|
|
return addClaimsResult;
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update a User Information
|
|
/// </summary>
|
|
/// <param name="user">an instance of <see>XUser</see></param>
|
|
/// <param name="checkCanLoginPolicies">check a user can log in in system or not, default is true</param>
|
|
/// <param name="checkIsBanned">check user is banned or not, default is true</param>
|
|
/// <returns>an instance of <see>IdentityResult</see></returns>
|
|
public async Task<IdentityResult> UpdateUserAsync(
|
|
XUser user,
|
|
bool checkCanLoginPolicies = true,
|
|
bool checkIsBanned = true
|
|
)
|
|
{
|
|
//
|
|
var updateResult = await this.UserManager.UpdateAsync(user);
|
|
if (updateResult.Succeeded)
|
|
{
|
|
//
|
|
var claims = await ToJwtClaims(
|
|
user,
|
|
checkCanLoginPolicies: checkCanLoginPolicies,
|
|
checkIsBanned: checkIsBanned
|
|
);
|
|
var existsClaims = await UserManager.GetClaimsAsync(user);
|
|
|
|
//
|
|
await UserManager.RemoveClaimsAsync(user, existsClaims);
|
|
var addClaimsResult = await UserManager.AddClaimsAsync(user, claims);
|
|
|
|
//
|
|
return addClaimsResult;
|
|
}
|
|
|
|
//
|
|
return updateResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate JWT Claims froma DashboardUser object
|
|
/// </summary>
|
|
/// <param name="user">an instance of <see>XUser</see></param>
|
|
/// <param name="checkCanLoginPolicies">check a user can log in in system or not, default is true</param>
|
|
/// <param name="checkIsBanned">check user is banned or not, default is true</param>
|
|
/// <returns>a collection of <see>Claim</see> instances</returns>
|
|
public async Task<Claim[]> ToJwtClaims(
|
|
XUser user,
|
|
bool checkCanLoginPolicies = true,
|
|
bool checkIsBanned = true
|
|
)
|
|
{
|
|
//
|
|
// Get New Instance of Claims ...
|
|
var claims = new List<Claim> {
|
|
//
|
|
// Mapping to User.Identity.Name ...
|
|
new Claim (JwtClaimTypes.Name, user.UserName),
|
|
|
|
//
|
|
// Mapping to User.Identity.Name ...
|
|
new Claim (JwtRegisteredClaimNames.UniqueName, user.UserName),
|
|
|
|
//
|
|
// Other Usefull User Related Data ...
|
|
new Claim (JwtClaimTypes.GivenName, user.FirstName),
|
|
new Claim (JwtClaimTypes.FamilyName, user.LastName),
|
|
|
|
//
|
|
// User Gendre ...
|
|
new Claim (JwtClaimTypes.Gender, user.Gender.ToString (), ClaimValueTypes.Integer),
|
|
|
|
//
|
|
// User Profile Picture ...
|
|
new Claim (JwtClaimTypes.Picture, !user.Avatar.IsNullOrEmpty () ?
|
|
user.Avatar.ToString () :
|
|
""),
|
|
|
|
//
|
|
// The Unique Identifier for Each Token ...
|
|
new Claim (JwtRegisteredClaimNames.Jti, Guid.NewGuid ().ToString ()),
|
|
|
|
//
|
|
// Add isEnable and isBanned feature ...
|
|
new Claim (
|
|
XCustomClaims.IsEnabled,
|
|
user.IsEnable.ToString (),
|
|
ClaimValueTypes.Boolean
|
|
),
|
|
new Claim (
|
|
XCustomClaims.IsBanned,
|
|
user.IsBanned.ToString (),
|
|
ClaimValueTypes.Boolean
|
|
),
|
|
};
|
|
|
|
//
|
|
#region Set Claims based on Profile Policies ...
|
|
//
|
|
if (Configuration.Policy.Profile.ContainsDateOfBirth)
|
|
{
|
|
claims.Add(new Claim(JwtRegisteredClaimNames.Birthdate, user.DateOfBirth.ToString()));
|
|
}
|
|
|
|
//
|
|
if (Configuration.Policy.Profile.ContainsEmail)
|
|
{
|
|
claims.Add(new Claim(JwtClaimTypes.Email, user.Email));
|
|
claims.Add(new Claim(
|
|
JwtClaimTypes.EmailVerified,
|
|
user.EmailConfirmed.ToString(),
|
|
ClaimValueTypes.Boolean));
|
|
}
|
|
|
|
//
|
|
if (Configuration.Policy.Profile.ContainsPhoneNumber)
|
|
{
|
|
claims.Add(new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber));
|
|
claims.Add(new Claim(
|
|
JwtClaimTypes.PhoneNumberVerified,
|
|
user.PhoneNumberConfirmed.ToString(),
|
|
ClaimValueTypes.Boolean));
|
|
}
|
|
|
|
//
|
|
if (Configuration.Policy.Profile.ContainsRoles)
|
|
{
|
|
var roleNames = await GetRoleNamesAsync(
|
|
user.UserName,
|
|
checkCanLoginPolicies: checkCanLoginPolicies,
|
|
checkIsBanned: checkIsBanned);
|
|
roleNames.ToList()
|
|
.ForEach(rn =>
|
|
{
|
|
claims.Add(new Claim(JwtClaimTypes.Role, rn));
|
|
});
|
|
}
|
|
|
|
//
|
|
if (Configuration.Policy.Profile.ContainsLastLogin)
|
|
{
|
|
claims.Add(new Claim(
|
|
JwtClaimTypes.AuthenticationTime,
|
|
user.LastLogin.ToString(),
|
|
ClaimValueTypes.DateTime));
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
// Return Result ...
|
|
return claims.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check User and Password relation
|
|
/// </summary>
|
|
/// <param name="user">an instance of <see>XUser</see></param>
|
|
/// <param name="password">user's password</param>
|
|
/// <param name="device">an instance of <see>XDevice</see></param>
|
|
/// <param name="language">specify destination language</param>
|
|
/// <param name="lockoutOnFailure">specify account lockout on failure log in</param>
|
|
/// <param name="isForced">specify log in force without checking <see>XDevice</see> and lang relation</param>
|
|
/// <returns>an instance of <see>SignInResult</see></returns>
|
|
public async Task<SignInResult> CheckPasswordSignInAsync(
|
|
XUser user,
|
|
string password,
|
|
XDevice device,
|
|
string language,
|
|
bool lockoutOnFailure,
|
|
bool isForced = true
|
|
)
|
|
{
|
|
//
|
|
var result = await SignInManager.CheckPasswordSignInAsync(
|
|
user, password, lockoutOnFailure
|
|
);
|
|
|
|
//
|
|
if (result.Succeeded && isForced)
|
|
{
|
|
//
|
|
var isExistsDevice = user.Devices.Any(d => d.IsSameAs(device));
|
|
if (!isExistsDevice && !user.Email.IsNullOrEmpty() && user.EmailConfirmed)
|
|
{
|
|
//
|
|
user.Devices.Add(device);
|
|
|
|
//
|
|
var updateUserResult = await UpdateUserAsync(user);
|
|
if (updateUserResult.Succeeded)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var xMessage = GetUserNewDeviceLoggedInMessage(
|
|
language,
|
|
user.Email,
|
|
device
|
|
);
|
|
|
|
//
|
|
await MessageProvider.SendMailAsync(xMessage);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
}
|
|
} |