Initial ...

This commit is contained in:
2026-03-22 14:08:25 +03:30
commit d3464ef39c
71 changed files with 19087 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using xCommons.Extensions;
using xCommons.Providers;
using xExceptions.Constants;
using xIdentityModels;
using xIdentityModels.Configurations;
using xIdentityModels.Constants;
using xIdentityModels.Models;
using xMessageService.Interfaces;
using xStorageService.Interfaces;
using xIds.Configurations;
using xIds.DbContext;
using xIds.Interfaces;
namespace xIds.Providers
{
public partial class XIdentityManager : IXIdentityManager
{
//
private JsonSerializerSettings jsonSerializerSettings;
//
public XIdentityDbContext DbContext { get; }
public UserManager<XUser> UserManager { get; }
public IXIdentityHelper IdentityHelper { get; }
public ILogger<IXIdentityManager> Logger { get; }
public IXMessageProvider MessageProvider { get; }
public IXStorageProvider StorageProvider { get; }
public SignInManager<XUser> SignInManager { get; }
public IXSecurityProvider SecurityProvider { get; }
public XIdentityConfiguration Configuration { get; }
public RoleManager<IdentityRole> RoleManager { get; }
public XValidationProvider ValidationProvider { get; }
public XDataServiceConfiguration DataConfiguration { get; }
public IXIdentityMessageProvider IdentityMessageProvider { get; }
public XIdentityResourceConfiguration IdentityResourceConfiguration { get; }
public XIdentityManager(
XIdentityDbContext dbContext,
UserManager<XUser> userManager,
IXIdentityHelper identityHelper,
ILogger<IXIdentityManager> logger,
IXMessageProvider messageProvider,
IXStorageProvider storageProvider,
SignInManager<XUser> signInManager,
IXSecurityProvider securityProvider,
XIdentityConfiguration configuration,
RoleManager<IdentityRole> roleManager,
XValidationProvider validationProvider,
XDataServiceConfiguration dataConfiguration,
IXIdentityMessageProvider identityMessageProvider,
XIdentityResourceConfiguration identityResourceConfiguration
)
{
//
Logger = logger;
DbContext = dbContext;
RoleManager = roleManager;
UserManager = userManager;
SignInManager = signInManager;
SecurityProvider = securityProvider;
Configuration = configuration;
IdentityHelper = identityHelper;
MessageProvider = messageProvider;
StorageProvider = storageProvider;
DataConfiguration = dataConfiguration;
ValidationProvider = validationProvider;
IdentityMessageProvider = identityMessageProvider;
IdentityResourceConfiguration = identityResourceConfiguration;
//
this.jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
}
//
#region Class Getter (s) Methods ...
/// <summary>
/// Retrieve User DB Set
/// </summary>
/// <param name="containDetails">specifies returned object contains all Navigation Properties or not, default is false</param>
/// <returns></returns>
public IQueryable<XUser> GetUsersDbSet(
bool containDetails = false
)
{
//
var dbSet = containDetails ? GetUsersFullDbSet() :
UserManager.Users;
return dbSet;
}
/// <summary>
/// Retrieve User Db Set Full Navigation Properties
/// </summary>
/// <param name="enableTracking"></param>
/// <returns></returns>
public IQueryable<XUser> GetUsersFullDbSet(bool enableTracking = false)
{
//
var dbSet = UserManager.Users
.Include(u => u.Roles)
.Include(u => u.Devices)
.Include(u => u.Followers)
.Include(u => u.Followings)
.Include(u => u.Avatars);
//
return dbSet.AsSplitQuery();
}
#endregion
//
#region Tools ...
/// <summary>
/// Get User SelectBy Param
/// </summary>
/// <param name="model"></param>
/// <param name="forceNotNull"></param>
/// <param name="excludes"></param>
/// <returns></returns>
public string GetUserSelectByParam(
XActionRequest model,
bool forceNotNull = true,
ICollection<XUserSelectBy> excludes = null
)
{
//
// Validate Args ...
ValidationProvider.NotNull(model);
//
var id = model.UserId;
var userName = model.UserName;
var email = model.Email;
var phoneNumber = model.MobileNumber;
//
if (excludes != null)
{
//
if (excludes.Contains(XUserSelectBy.ID))
{
id = null;
}
if (excludes.Contains(XUserSelectBy.Email))
{
email = null;
}
if (excludes.Contains(XUserSelectBy.MobileNumber))
{
phoneNumber = null;
}
if (excludes.Contains(XUserSelectBy.Username))
{
userName = null;
}
}
var selectByParam =
id.IsNullOrEmpty() ?
userName.IsNullOrEmpty() ?
email.IsNullOrEmpty() ?
phoneNumber.IsNullOrEmpty() ? null : phoneNumber : email : userName : id;
//
// Check result ...
if (forceNotNull &&
selectByParam.IsNullOrEmpty())
{
XException.InvalidData.Throw();
}
//
return selectByParam;
}
#endregion
}
}
@@ -0,0 +1,224 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using xCommons.Extensions;
using xIdentityModels.Dtos;
namespace xIds.Providers
{
public partial class XIdentityManager
{
#region Admin Actions ...
/// <summary>
/// Retrieve All UnBanned Users Identifiers
/// </summary>
/// <param name="userSelectByParam">caller user identifier</param>
/// <returns>a collection of user identifiers</returns>
public async Task<ICollection<string>> GetAllUnbanned(
ICollection<string> userSelectByParam
)
{
//
var result = new List<string>();
if (!userSelectByParam.HasChild())
{
return result;
}
//
foreach (var uParam in userSelectByParam)
{
//
var destUser = await ValidateUserExistsAndRetrieve(uParam, checkIsBanned: false);
if (!destUser.IsBanned)
{
result.Add(uParam);
}
}
//
return result;
}
/// <summary>
/// Get All Banned Users Identifiers
/// </summary>
/// <param name="userSelectByParam">caller user identifier</param>
/// <returns>a collection of user identifiers</returns>
public async Task<ICollection<string>> GetAllBanned(
ICollection<string> userSelectByParam
)
{
//
var result = new List<string>();
if (!userSelectByParam.HasChild())
{
return result;
}
//
foreach (var uParam in userSelectByParam)
{
//
var destUser = await ValidateUserExistsAndRetrieve(uParam, checkIsBanned: false);
if (destUser.IsBanned)
{
result.Add(uParam);
}
}
//
return result;
}
/// <summary>
/// Ban a Collection of Users
/// </summary>
/// <param name="userSelectByParam">caller user identifier</param>
/// <param name="model">an instance of <see>XUserNameIdRequest</see> which represents user identifier list to Ban</param>
/// <returns>a collection of user identifiers</returns>
public async Task<ICollection<string>> Ban(
string userSelectByParam,
XUserNameIdRequest model
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(userSelectByParam);
ValidationProvider.NotNull(model);
ValidationProvider.NotZeroChilds(model.Ids);
//
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
forceAdmin: true,
checkIsBanned: true,
containDetails: false,
ignoreDisabledUser: false,
checkCanLoginPolicies: true
);
//
var result = new List<string>();
foreach (var uParam in model.Ids)
{
//
var destUser = await ValidateUserExistsAndRetrieve(uParam, checkIsBanned: false);
//
var isBanned = destUser.IsBanned;
if (!isBanned)
{
//
destUser.IsBanned = true;
//
await UpdateUserAsync(
user,
checkCanLoginPolicies: false,
checkIsBanned: false
);
//
result.Add(destUser.Id);
}
}
//
return result;
}
/// <summary>
/// UnBan a Collection of Users
/// </summary>
/// <param name="userSelectByParam">caller user identifier</param>
/// <param name="model">an instance of <see>XUserNameIdRequest</see> which represents user identifier list to Ban</param>
/// <returns>a collection of user identifiers</returns>
public async Task<ICollection<string>> UnBan(
string userSelectByParam,
XUserNameIdRequest model
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(userSelectByParam);
ValidationProvider.NotNull(model);
ValidationProvider.NotZeroChilds(model.Ids);
//
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
forceAdmin: true,
checkIsBanned: true,
containDetails: false,
ignoreDisabledUser: false,
checkCanLoginPolicies: true
);
//
var result = new List<string>();
foreach (var uParam in model.Ids)
{
//
var destUser = await ValidateUserExistsAndRetrieve(uParam, checkIsBanned: false);
//
var isBanned = destUser.IsBanned;
if (isBanned)
{
//
destUser.IsBanned = false;
//
await UpdateUserAsync(
user,
checkCanLoginPolicies: false,
checkIsBanned: false
);
//
result.Add(destUser.Id);
}
}
//
return result;
}
/// <summary>
/// Detrmines a User is Banned or not
/// </summary>
/// <param name="userSelectByParam">caller user identifier</param>
/// <param name="destUserSelectByParam">destination user identifier which checked is banned or not</param>
/// <returns>a boolean value which represent user banned or not</returns>
public async Task<bool> IsBanned(
string userSelectByParam,
string destUserSelectByParam
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(
userSelectByParam,
destUserSelectByParam
);
//
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
forceAdmin: false,
checkIsBanned: true,
containDetails: false,
ignoreDisabledUser: false,
checkCanLoginPolicies: true
);
var destUser = await ValidateUserExistsAndRetrieve(destUserSelectByParam, checkIsBanned: false);
//
var result = destUser.IsBanned;
//
return result;
}
#endregion
}
}
@@ -0,0 +1,481 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels;
using xIdentityModels.Extensions;
using xIdentityModels.Models;
using xIdentityModels.Navigations;
using xIds.Extensions;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Device Actions ...
/// <summary>
/// Check a Device Exists or Not
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>a boolean value</returns>
public async Task<bool> IsDeviceExistsAsync(
XDevice device
)
{
return await DbContext.Devices
.AnyAsync(d => d.IsSameAs(device));
}
/// <summary>
/// Add new Device
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>an instance of <see>XDevice</see></returns>
public async Task<XDevice> AddDeviceAsync(
XDevice device
)
{
//
var isDeviceExists = await IsDeviceExistsAsync(device);
if (isDeviceExists)
{
return null;
}
//
await DbContext.Devices
.AddAsync(device);
await DbContext.SaveChangesAsync();
return device;
}
/// <summary>
/// Get a Device
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>an instance of <see>XDevice</see></returns>
public Task<XDevice> GetDeviceAsync(
XDevice device
)
{
//
return DbContext.Devices
.FirstOrDefaultAsync(d =>
d.IsSameAs(device));
}
/// <summary>
/// Retrieve a List of User Related Devices
/// </summary>
/// <param name="userSelectByParam">user identifier</param>
/// <param name="checkCanLoginPolicies">check a user can log in in system or not</param>
/// <param name="checkIsBanned">check user is banned or not</param>
/// <returns>a collection of <see>XDevice</see> instances which related to user</returns>
public async Task<ICollection<XDevice>> GetUserDevices(
string userSelectByParam,
bool checkCanLoginPolicies = true,
bool checkIsBanned = true
)
{
//
// Validate ARgs ...
ValidationProvider.NotEmpty(userSelectByParam);
//
// Validate And Retrieve User ...
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: true,
checkCanLoginPolicies: checkCanLoginPolicies,
checkIsBanned: checkIsBanned
);
//
// Check User Devices ...
if (!user.Devices.HasChild())
{
return new List<XDevice>();
}
//
return user.Devices;
}
/// <summary>
/// Determines a Device is Exists in User's Devices or not
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>a boolean value</returns>
public bool IsDeviceRelateDToUser(
XUser user,
XDevice device
)
{
if (!user.Devices.HasChild())
{
return false;
}
//
var result = user.Devices.Any(d => d.IsSameAs(device));
//
return result;
}
/// <summary>
/// Determines a Device is Exists in User's Devices or not
/// </summary>
/// <param name="userSelectByParam">user identifier</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="checkCanLoginPolicies">check a user can log in in system or not</param>
/// <param name="checkIsBanned">check user is banned or not</param>
/// <returns>a boolean value</returns>
public async Task<bool> IsDeviceRelateDToUser(
string userSelectByParam,
XDevice device,
bool checkCanLoginPolicies = true,
bool checkIsBanned = true
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(userSelectByParam);
ValidationProvider.NotNull(device);
//
var userDevices = await GetUserDevices(
userSelectByParam,
checkCanLoginPolicies: checkCanLoginPolicies,
checkIsBanned: checkIsBanned
);
if (!userDevices.HasChild())
{
return false;
}
//
var result = userDevices.Any(d => d.IsSameAs(device));
//
return result;
}
/// <summary>
/// Add a Device to a User Related Devices
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>a boolean value</returns>
public async Task<bool> AddUserRelatedDevice(
XUser user,
XDevice device
)
{
//
var isExists = user.Devices.Any(d =>
d.IsSameAs(device));
if (isExists)
{
return true;
}
//
user.Devices.Add(device);
try
{
var result = await UpdateUserAsync(user);
return result.Succeeded;
}
catch
{
throw XException.ActionFailed.ToException();
}
}
/// <summary>
/// Add a Device to a User
/// </summary>
/// <param name="userSelectByParam">user identifier</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="checkCanLoginPolicies">check a user can log in in system or not</param>
/// <param name="checkIsBanned">check user is banned or not</param>
/// <returns>a boolean value</returns>
public async Task<bool> AddUserRelatedDevice(
string userSelectByParam,
XDevice device,
bool checkCanLoginPolicies = true,
bool checkIsBanned = true
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(userSelectByParam);
ValidationProvider.NotNull(device);
//
// Check Device Related ...
var isDeviceRelatedToUser = await IsDeviceRelateDToUser(
userSelectByParam,
device,
checkCanLoginPolicies: checkCanLoginPolicies,
checkIsBanned: checkIsBanned);
if (isDeviceRelatedToUser)
{
return true;
}
//
// Retrieve User ...
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: true,
checkCanLoginPolicies: checkCanLoginPolicies,
checkIsBanned: checkIsBanned
);
//
// Add User Device ...
user.Devices.Add(device);
//
var result = await UpdateUserAsync(
user,
checkCanLoginPolicies: false,
checkIsBanned: false
);
//
return result.Succeeded;
}
/// <summary>
/// Remove a Device from a User Related Devices List
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>a boolean value</returns>
public async Task<bool> RemoveUserRelatedDevice(
XUser user,
XDevice device
)
{
//
var isRelated = IsDeviceRelateDToUser(user, device);
if (!isRelated)
{
return false;
}
//
var instanse = user.Devices
.FirstOrDefault(d =>
d.IsSameAs(device));
//
user.Devices.Remove(instanse);
//
var result = await UpdateUserAsync(
user,
checkCanLoginPolicies: false,
checkIsBanned: false
);
//
return result.Succeeded;
}
/// <summary>
/// Remove a Device from a User Devices
/// </summary>
/// <param name="userSelectByParam">user identifier</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="checkCanLoginPolicies">check a user can log in in system or not</param>
/// <param name="checkIsBanned">check user is banned or not</param>
/// <returns>a boolean value</returns>
public async Task<bool> RemoveUserRelatedDevice(
string userSelectByParam,
XDevice device,
bool checkCanLoginPolicies = true,
bool checkIsBanned = true
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(userSelectByParam);
ValidationProvider.NotNull(device);
//
// Validate user exists and Retrieve it based on userselectbyparam ...
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: true,
checkCanLoginPolicies: checkCanLoginPolicies,
checkIsBanned: checkIsBanned
);
//
return await RemoveUserRelatedDevice(
user,
device
);
}
#endregion
//
#region Banned Device Actions ...
/// <summary>
/// Check a Device is exists in Banned Devices or not
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>a boolean value</returns>
public async Task<bool> IsBannedDeviceExists(
XDevice device
)
{
//
var result = false;
var bannedDeviceEnumerable = DbContext.BannedDevices.AsAsyncEnumerable();
await
foreach (var bannedDevice in bannedDeviceEnumerable)
{
//
if (bannedDevice.Device.IsSameAs(device))
{
//
result = true;
break;
}
}
//
return result;
}
/// <summary>
/// Find First Banned Device
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>an instance of <see>XDevice</see></returns>
public async Task<XBannedDevice> BannedDeviceFindOne(
XDevice device
)
{
//
var result = await DbContext.BannedDevices
.FirstOrDefaultAsync(bd =>
bd.Device.IsSameAs(device)
);
//
return result;
}
/// <summary>
/// Remove Banned Device
/// </summary>
/// <param name="model">an instance of <see>XBannedDevice</see></param>
/// <returns></returns>
public async Task BannedDeviceRemove(
XBannedDevice model
)
{
//
var isExists = await IsBannedDeviceExists(model.Device);
if (!isExists)
{
return;
}
//
var item = await BannedDeviceFindOne(model.Device);
if (item == null)
{
return;
}
//
DbContext.BannedDevices.Remove(item);
await DbContext.SaveChangesAsync();
}
/// <summary>
/// Check a Device is Banned or not
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>a boolean value</returns>
private async Task<bool> IsDeviceBanned(
XDevice device
)
{
//
var result = await IsBannedDeviceExists(device);
return result;
}
/// <summary>
/// Retrieve Specific Banned Device
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>an instance of <see>XBannedDevice</see></returns>
private async Task<XBannedDevice> GetBannedDevice(
XDevice device
)
{
//
var result = await BannedDeviceFindOne(device);
return result;
}
/// <summary>
/// Is a Banned Device passed Banning Time
/// </summary>
/// <param name="bannedDevice">an instance of <see>XBannedDevice</see></param>
/// <returns>a boolean value</returns>
private bool IsDelayTimePassed(
XBannedDevice bannedDevice
)
{
//
// Validate Args ...
if (bannedDevice == null ||
Configuration == null)
{
XException.InvalidArgs.Throw();
}
//
var isPassed = bannedDevice
.IsDelayTimePassed(Configuration.BannedDeviceTimeout);
//
return isPassed;
}
/// <summary>
/// Retrieve Passed Time from Banning Time of specific Device
/// </summary>
/// <param name="bannedDevice">an instance of <see>XBannedDevice</see></param>
/// <returns>an instance of <see>DateTime</see> which represent Passed Time</returns>
private DateTime GetPassedTime(
XBannedDevice bannedDevice
)
{
//
var result = bannedDevice.BannedOn
.AddSeconds(Configuration.BannedDeviceTimeout);
//
return result;
}
#endregion
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,342 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.Extensions.Logging;
using xCommons.Extensions;
using xExceptions.Constants;
using xExceptions.Models;
using xIdentityModels.Extensions;
using xIdentityModels.Models;
using xIds.Extensions;
using static IdentityModel.OidcConstants;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Identity Actions ...
/// <summary>
/// Request for Discovery Document
/// </summary>
/// <returns>an instance of <see>DiscoveryDocumentResponse</see></returns>
public async Task<DiscoveryDocumentResponse> RequestDiscoveryDocument()
{
//
var httpClient = GetHttpClient();
var result = httpClient
.GetDiscoveryDocumentAsync(IdentityResourceConfiguration.Authority)
.ContinueWith(docTask =>
{
//
httpClient.Dispose();
return docTask.Result;
});
//
return await result;
}
/// <summary>
/// Request AccessToken for Specific XApiScope
/// </summary>
/// <param name="scope">a member of <see>XApiScope</see></param>
/// <returns>an instance of <see>TokenResponse</see></returns>
public async Task<IdentityModel.Client.TokenResponse> RequestScopeAccessToken(
string scope
)
{
//
// Validate Args ...
if (scope.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Disco Doc ...
var discoDoc = await RequestDiscoveryDocument();
if (discoDoc.IsError)
{
XException.ActionFailed.Throw();
}
//
var request = new ClientCredentialsTokenRequest
{
Address = discoDoc.TokenEndpoint,
GrantType = GrantTypes.ClientCredentials,
ClientId = IdentityResourceConfiguration.ClientId,
ClientSecret = IdentityResourceConfiguration.ClientSecret,
Scope = scope
};
//
var httpClient = GetHttpClient();
var result = await httpClient
.RequestClientCredentialsTokenAsync(request)
.ContinueWith(response =>
{
//
httpClient.Dispose();
return response.Result;
});
//
return result;
}
/// <summary>
/// Authenticate a User
/// </summary>
/// <param name="model">an instance of <see>XLoginRequest</see> class which represent Authentication requirements</param>
/// <returns>an instance of <see>TokenResponse</see></returns>
public async Task<IdentityModel.Client.TokenResponse> Authenticate(
XLoginRequest model
)
{
//
// Validate Args ...
ValidationProvider
.GroupValidationBuilder()
.AddNotNull(model)
.AddNotEmpty(
model.UserSelectBy,
model.Password
)
.ValidateGroup();
//
// Retrieve Disco Doc ...
var discoDoc = await RequestDiscoveryDocument();
if (discoDoc.IsError)
{
XException.ActionFailed.Throw();
}
//
// Create Request ...
var request = new PasswordTokenRequest
{
//
Address = discoDoc.TokenEndpoint,
//
GrantType = GrantTypes.ClientCredentials,
ClientId = IdentityResourceConfiguration.ClientId,
ClientSecret = IdentityResourceConfiguration.ClientSecret,
//
UserName = model.UserSelectBy,
Password = model.Password,
//
// Pass Device to Request ...
Parameters = { { "force", false.ToJSON () }
}
};
//
var httpClient = GetHttpClient();
var result = await httpClient
.RequestPasswordTokenAsync(request)
.ContinueWith(response =>
{
//
httpClient.Dispose();
return response.Result;
});
//
return result;
}
/// <summary>
/// Do Login based on XLoginRequest
/// </summary>
/// <param name="model">an instance of <see>XLoginRequest</see> class which represent Authentication requirements</param>
/// <returns>an instance of <see>XLoginResponse</see></returns>
public async Task<XLoginResponse> Login(
XLoginRequest model
)
{
//
// Validate Args ...
ValidationProvider
.GroupValidationBuilder()
.AddNotNull(model, model.Device)
.AddNotEmpty(
model.UserSelectBy,
model.Password
)
.ValidateGroup();
//
// Get Token Response ...
//
// Retrieve Disco Doc ...
var discoDoc = await RequestDiscoveryDocument();
Logger.LogInformation($"discoDoc: {discoDoc.ToJSON()}");
if (discoDoc.IsError)
{
XException.ActionFailed.Throw();
}
//
// Create Request ...
var request = new PasswordTokenRequest
{
//
Address = discoDoc.TokenEndpoint,
//
GrantType = GrantTypes.ClientCredentials,
ClientId = IdentityResourceConfiguration.ClientId,
ClientSecret = IdentityResourceConfiguration.ClientSecret,
//
UserName = model.UserSelectBy,
Password = model.Password,
//
// Pass Device to Request ...
Parameters = { { "force", false.ToJSON () },
{ "device", model.Device.ToJSON () },
{ "language", model.Language }
}
};
//
var httpClient = GetHttpClient();
var authResponse = await httpClient
.RequestPasswordTokenAsync(request)
.ContinueWith(response =>
{
//
httpClient.Dispose();
return response.Result;
});
//
// Check If Response Has Error ...
Logger.LogInformation($"AuthResponse: {authResponse.ToJSON()}");
//
if (authResponse.IsError)
{
throw authResponse.GetException();
}
//
// Create XLoginResponse Model ...
var result = authResponse.CreateXLoginResponse();
//
// Get ans Set User Profile ...
result.Profile = await GetUserProfileAsync(model.UserSelectBy, model.UserSelectBy);
//
return result;
}
/// <summary>
/// Refresh Tokens
/// </summary>
/// <param name="model">Authentication Tokens, instance of <see>XTokenResponse</see></param>
/// <returns>an instance of <see>XTokenResponse</see></returns>
public async Task<XTokenResponse> RefreshTokens(
XTokenResponse model
)
{
//
// Validate Args ...
ValidationProvider
.GroupValidationBuilder()
.AddNotNull(model)
.AddNotEmpty(
model.AccessToken,
model.RefreshToken
)
.ValidateGroup();
//
var authResponse = await RequestDiscoveryDocument()
.ContinueWith((discoTask) =>
{
//
var discoDoc = discoTask.Result;
if (discoDoc.IsError)
{
XException.ActionFailed.Throw();
}
//
using (var httpClient = GetHttpClient())
{
return httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest
{
//
Address = discoDoc.TokenEndpoint,
GrantType = GrantTypes.RefreshToken,
ClientId = IdentityResourceConfiguration.ClientId,
ClientSecret = IdentityResourceConfiguration.ClientSecret,
RefreshToken = model.RefreshToken
}).Result;
}
});
//
// Check If Response Has Error ...
if (authResponse.IsError)
{
//
XError error = authResponse.ErrorDescription.FromJSON<XError>();
throw error.ToException();
}
//
// Create XLoginResponse Model ...
var result = authResponse.CreateXTokenResponse();
//
return result;
}
#endregion
//
#region Requirements ...
/// <summary>
/// Get an Instance of Http Client
/// </summary>
/// <returns>an instance of <see>HttpClient</see></returns>
public HttpClient GetHttpClient()
{
//
HttpClient httpClient = null;
httpClient = new HttpClient();
var httpClientHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
{
//
Logger.LogInformation(
$"SSL Handler: {Environment.NewLine} sender: {sender}, {Environment.NewLine} cert: {cert}, {Environment.NewLine} chain: {chain}, {Environment.NewLine} sslPolicyErrors: {sslPolicyErrors}"
);
//
return true;
},
ClientCertificateOptions = ClientCertificateOption.Manual,
};
//
httpClient = new HttpClient(httpClientHandler);
//
return httpClient;
}
#endregion
}
}
@@ -0,0 +1,341 @@
using System;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Navigations;
using xMessageService.Models;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Message Provider ...
/// <summary>
/// Generate and Prepare an XMessage Instance for
/// User Invite Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="emailAddress">reciever email address</param>
/// <param name="inviteToken">invitation token</param>
/// <param name="returnUrl">return url for invitation user to redirect</param>
/// <returns>an instance of <see>XMessage</see></returns>
private XMessage GetUserInviteMessage(
string lang,
string emailAddress,
string inviteToken,
string returnUrl
)
{
//
ValidationProvider
.NotEmpty(lang, emailAddress, inviteToken, returnUrl);
ValidationProvider
.EmailAddress(emailAddress);
ValidationProvider
.Url(returnUrl);
//
HandleMessageProviderPreperation(lang);
//
var msgStr = IdentityMessageProvider.InviteMsg +
Environment.NewLine +
returnUrl +
"?t=" +
inviteToken;
//
var message = new XMessage
{
Message = msgStr,
};
message.Recievers.Add(emailAddress);
//
return message;
}
/// <summary>
/// Generate and Prepare an XMessage Instance for
/// New Device LoggedIn Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="emailAddress">reciever email address</param>
/// <param name="device">an instance of <see>XDevice</see> which represent user new Device</param>
/// <returns>an instance of <see>XMessage</see></returns>
private XMessage GetUserNewDeviceLoggedInMessage(
string lang,
string emailAddress,
XDevice device
)
{
//
ValidationProvider
.NotEmpty(lang, emailAddress);
ValidationProvider
.EmailAddress(emailAddress);
ValidationProvider
.NotNull(device);
//
HandleMessageProviderPreperation(lang);
//
// Generate Specific Message ...
var msgStr = IdentityMessageProvider.NewDeviceLoggedInMsg +
Environment.NewLine +
nameof(XDevice.Os) + ": " + device.Os +
Environment.NewLine +
nameof(XDevice.OsVersion) + ": " + device.OsVersion +
Environment.NewLine +
nameof(XDevice.Browser) + ": " + device.Browser +
Environment.NewLine +
nameof(XDevice.UserAgent) + ": " + device.UserAgent;
//
// Create XMessage Instance ...
var message = new XMessage
{
Message = msgStr,
};
message.Recievers.Add(emailAddress);
//
return message;
}
/// <summary>
/// Generate and Prepare an XMessage Instance for
/// Verification Code Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="mobileNumberOrEmail">an string which points to a user email address or mobile number</param>
/// <param name="verificationCode">user verification code</param>
/// <returns>an instance of <see>XMessage</see></returns>
private XMessage GetVerificationCodeMessage(
string lang,
string mobileNumberOrEmail,
string verificationCode
)
{
//
ValidationProvider
.NotEmpty(lang, mobileNumberOrEmail, verificationCode);
//
HandleMessageProviderPreperation(lang);
//
mobileNumberOrEmail = mobileNumberOrEmail.ToNormalString();
//
// Validate Mobile or Email ...
var isValidEmail = mobileNumberOrEmail.IsValidEmail();
var isValidMobileNumber = mobileNumberOrEmail.IsValidMobileNumber();
if (!isValidEmail &&
!isValidMobileNumber)
{
XException.InvalidData.Throw();
}
//
// Prepare Message String ...
var msgStr = IdentityMessageProvider.VerificationCodeMsg +
Environment.NewLine +
verificationCode;
//
// Create XMessage Instance ...
var message = new XMessage
{
Message = msgStr,
};
message.Recievers.Add(mobileNumberOrEmail);
//
return message;
}
/// <summary>
/// Generate and Prepare an XMessage Instance for
/// Registration Approve Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="emailAddress">reciever email address</param>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="returnUrl">return url for redirect</param>
/// <returns>an instance of <see>XMessage</see></returns>
private XMessage GetRegistrationConfirmMessage(
string lang,
string emailAddress,
string actionToken,
string returnUrl
)
{
//
ValidationProvider
.NotEmpty(lang, emailAddress, actionToken, returnUrl);
ValidationProvider
.EmailAddress(emailAddress);
ValidationProvider
.Url(returnUrl);
//
HandleMessageProviderPreperation(lang);
//
// Prepare Message Str ...
var msgStr = IdentityMessageProvider.RegistrationApproveMsg +
Environment.NewLine +
returnUrl +
"?t=" +
actionToken;
//
var message = new XMessage
{
Message = msgStr,
};
message.Recievers.Add(emailAddress);
//
return message;
}
/// <summary>
/// Generate and Prepare an XMessage Instance for
/// Registration Finished Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="emailAddress">reciever email address</param>
/// <param name="returnUrl">return url for redirect</param>
/// <returns>an instance of <see>XMessage</see></returns>
private XMessage GetRegistrationFinishedMessage(
string lang,
string emailAddress,
string returnUrl
)
{
//
ValidationProvider
.NotEmpty(lang, emailAddress, returnUrl);
ValidationProvider
.EmailAddress(emailAddress);
ValidationProvider
.Url(returnUrl);
//
HandleMessageProviderPreperation(lang);
//
var msgStr = IdentityMessageProvider.RegisteredMsg +
Environment.NewLine +
returnUrl;
//
var message = new XMessage
{
Message = msgStr,
};
message.Recievers.Add(emailAddress);
//
return message;
}
/// <summary>
/// Generate and Prepare an XMessage Instance for
/// Password Changed Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="emailAddress">reciever email address</param>
/// <param name="returnUrl">return url for redirect</param>
/// <param name="throwException">specify throw exceptions on failure or not, default is true</param>
/// <returns>an instance of <see>XMessage</see></returns>
private XMessage GetPasswordChangedMessage(
string lang,
string emailAddress,
string returnUrl,
bool throwException = true
)
{
//
if (throwException)
{
ValidationProvider
.NotEmpty(lang, emailAddress, returnUrl);
ValidationProvider
.EmailAddress(emailAddress);
ValidationProvider
.Url(returnUrl);
}
//
HandleMessageProviderPreperation(lang);
//
var msgStr = IdentityMessageProvider.PasswordChangedMsg +
(returnUrl.IsNullOrEmpty() ?
"" :
Environment.NewLine +
returnUrl);
//
var message = new XMessage
{
Message = msgStr,
};
message.Recievers.Add(emailAddress);
//
return message;
}
/// <summary>
/// Generate and Prepare an XMessage Instance for
/// Reset Password Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="emailAddress">reciever email address</param>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="returnUrl">return url for redirect</param>
/// <returns>an instance of <see>XMessage</see></returns>
private XMessage GetResetPasswordMessage(
string lang,
string emailAddress,
string actionToken,
string returnUrl
)
{
//
ValidationProvider
.NotEmpty(lang, emailAddress, actionToken, returnUrl);
ValidationProvider
.EmailAddress(emailAddress);
ValidationProvider
.Url(returnUrl);
//
HandleMessageProviderPreperation(lang);
//
// Prepare Message Str ...
var msgStr = IdentityMessageProvider.ChangePasswordMsg +
Environment.NewLine +
returnUrl +
"?t=" +
actionToken;
//
var message = new XMessage
{
Message = msgStr,
};
message.Recievers.Add(emailAddress);
//
return message;
}
#endregion
}
}
@@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Dtos;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region OpenGet ...
public async Task<XOpenActionInnerDto> OpenGet(
XOpenActionInnerDto model
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotNull(model)
.AddNotEmpty(model.Token)
.AddNotEmpty(model.Payload)
.ValidateGroupAsync();
//
return await OpenGet(
token: model.Token,
actionRequest: model.Payload
);
}
//
public async Task<XOpenActionInnerDto> OpenGet(
string token,
string actionRequest
)
{
//
// Validatr Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(token)
.AddNotEmpty(actionRequest)
.ValidateGroupAsync();
//
// Try to Decrypt Request ...
var requestModel = ParseModel(
token: token,
request: actionRequest
);
if (requestModel.IsNull() ||
requestModel.Action.IsNullOrEmpty())
{
XException.BadRequest.Throw();
}
//
var responseJson = string.Empty;
switch (requestModel.Action)
{
//
case XOpenActions.GetUserInfo:
//
ValidationProvider.NotEmpty(requestModel.Payload);
var userInfo = await GetUserInfo(requestModel);
if (userInfo.IsNull())
{
XException.InvalidData.Throw();
}
//
responseJson = userInfo.ToJSON();
break;
//
case XOpenActions.GetUserInfos:
ValidationProvider.NotEmpty(requestModel.Payload);
var userInfos = await GetUserInfos(requestModel);
if (userInfos.IsNull())
{
XException.InvalidData.Throw();
}
//
responseJson = userInfos.ToJSON();
break;
}
//
// Encrypt Json Str ...
var responseStr = SecurityProvider.Encrypt(responseJson);
requestModel.Payload = responseStr;
requestModel.Timestamp = DateTime.UtcNow
.AddMinutes(5)
.ToTimestamp()
.ToString();
//
token = requestModel.ToOpenActionToken();
requestModel.Checksum = token;
var result = new XOpenActionInnerDto
{
Token = token,
Payload = requestModel.ToJSON()
};
//
// Check Result ...
if (result.IsNull())
{
XException.ActionFailed.Throw();
}
//
return result;
}
#endregion
public async Task<XOpenActionUserInfoDto> GetUserInfo(
XOpenActionRequestDto model
)
{
//
// Retrieve User Profile ...
var userProfile = await GetUserProfileAsync(
checkIsBanned: false,
checkCanLoginPolicies: false,
requestedUserSelectByParam: "",
forceCheckRequestedUser: false,
userSelectByParam: model.Payload
);
//
// Generate a Dynamic Object ...
var result = new XOpenActionUserInfoDto
{
Username = userProfile.UserName,
Firstname = userProfile.FirstName,
Lastname = userProfile.LastName,
Avatar = userProfile.Avatar
};
//
return result;
}
public async Task<IEnumerable<XOpenActionUserInfoDto>> GetUserInfos(
XOpenActionRequestDto model
)
{
//
var ids = model.Payload.ParseListString<string>().ToList();
var profiles = await GetUserProfilesAsync(
ids: ids,
checkIsBanned: false,
checkCanLoginPolicies: false,
requestedUserSelectByParam: "",
forceCheckRequestedUser: false
);
var result = profiles.Select(x => new XOpenActionUserInfoDto
{
Avatar = x.Avatar,
Username = x.UserName,
Lastname = x.LastName,
Firstname = x.FirstName,
});
//
return result;
}
//
#region Private ...
private XOpenActionRequestDto ParseModel(
string token,
string request
)
{
//
// Decript request to get Json string of request object ...
var result = new XOpenActionRequestDto();
//
var jsonStr = SecurityProvider.Decrypt(request);
result = jsonStr.FromJSON<XOpenActionRequestDto>();
if (result.IsNull() ||
!result.Validate() ||
token != result.Checksum
)
{
//
result = null;
return result;
}
//
var modelChecksum = result.ToOpenActionToken();
result =
token == modelChecksum
? result
: null;
//
return result;
}
#endregion
}
}
@@ -0,0 +1,552 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using xCommons.Extensions;
using xCommons.Helpers;
using xExceptions.Constants;
using xIdentityModels;
using xIdentityModels.Constants;
using xIdentityModels.Dtos;
using xIdentityModels.Models;
using xIdentityModels.Navigations;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Generators ...
/// <summary>
/// Generate a Random Number ...
/// </summary>
/// <returns>a long value</returns>
private long GenerateRandom()
{
return new Random().Next(100000, 999999);
}
#endregion
//
#region UserSelectBy Actions ...
/// <summary>
/// Retrieve UserSelectByType based on Given Info
/// </summary>
/// <param name="userSelectByParam">a user identifier</param>
/// <param name="forceUserSelectByParamNotEmpty">a boolean value which specify the identifier
/// must be specific and not empty, default is true</param>
/// <param name="forceUserSelectByTypeMustSpecified">a boolean value which specify the type
/// must be specific, default is false</param>
/// <returns>a member of <see>XUserSelectBy</see></returns>
private XUserSelectBy GetUserSelectByType(
string userSelectByParam,
bool forceUserSelectByParamNotEmpty = true,
bool forceUserSelectByTypeMustSpecified = false
)
{
//
if (forceUserSelectByParamNotEmpty)
{
ValidationProvider.NotEmpty(userSelectByParam);
}
//
var result = XUserSelectBy.NotSpecified;
var normalizedString = userSelectByParam.ToNormalString();
if (userSelectByParam.IsValidEmail())
{
//
// Find User by Email ...
result = XUserSelectBy.Email;
}
else if (userSelectByParam.IsValidMobileNumber())
{
//
// Find User by Mobile Number ...
result = XUserSelectBy.MobileNumber;
}
else if (userSelectByParam.IsGuid())
{
//
// Find User By it's ID ...
result = XUserSelectBy.ID;
}
else
{
//
// UserName ...
result = XUserSelectBy.Username;
}
//
if (forceUserSelectByTypeMustSpecified &&
result == XUserSelectBy.NotSpecified)
{
XException.InvalidData.Throw();
}
//
return result;
}
/// <summary>
/// Retrieve UserSelectByParam based on XUser Object
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <param name="forceNotNull">a boolean value which specify the identifier must nut empty,
/// and throw exception if it is empty, default is true</param>
/// <param name="excludes">a collection of <see>XUserSelectBy</see> members which
/// exclude them from result, default is null</param>
/// <returns>a user identifier</returns>
private string GetUserSelectByParam(
XUser user,
bool forceNotNull = true,
ICollection<XUserSelectBy> excludes = null
)
{
//
// Validate Args ...
ValidationProvider.NotNull(user);
//
var id = user.Id;
var userName = user.UserName;
var email = user.Email;
var phoneNumber = user.PhoneNumber;
//
if (excludes != null)
{
if (excludes.Contains(XUserSelectBy.ID))
{
id = null;
}
if (excludes.Contains(XUserSelectBy.Email))
{
email = null;
}
if (excludes.Contains(XUserSelectBy.MobileNumber))
{
phoneNumber = null;
}
if (excludes.Contains(XUserSelectBy.Username))
{
userName = null;
}
}
var selectByParam =
id.IsNullOrEmpty() ?
userName.IsNullOrEmpty() ?
email.IsNullOrEmpty() ?
phoneNumber.IsNullOrEmpty() ? null : phoneNumber : email : userName : id;
//
// Check result ...
if (forceNotNull &&
selectByParam.IsNullOrEmpty())
{
XException.InvalidData.Throw();
}
//
return selectByParam;
}
/// <summary>
/// Retrieve UserSelectByParam based on <see>XActionRequestContext</see> instance
/// </summary>
/// <param name="context">an instance of <see>XActionRequestContext</see></param>
/// <param name="forceNotNull">a boolean value which specify the identifier must nut empty,
/// and throw exception if it is empty, default is true</param>
/// <param name="excludes">a collection of <see>XUserSelectBy</see> members which
/// exclude them from result, default is null</param>
/// <returns>a user identifier</returns>
private string GetUserSelectByParam(
XActionRequestContext context,
bool forceNotNull = true,
ICollection<XUserSelectBy> excludes = null
)
{
//
// Validate Args ...
ValidationProvider.NotNull(context);
//
// Generate User SelectBy ...
var id = context.UserId;
var userName = context.UserName;
var email = context.Email;
var phoneNumber = context.MobileNumber;
//
if (excludes != null)
{
//
if (excludes.Contains(XUserSelectBy.ID))
{
id = null;
}
if (excludes.Contains(XUserSelectBy.Email))
{
email = null;
}
if (excludes.Contains(XUserSelectBy.MobileNumber))
{
phoneNumber = null;
}
if (excludes.Contains(XUserSelectBy.Username))
{
userName = null;
}
}
var selectByParam =
id.IsNullOrEmpty() ?
userName.IsNullOrEmpty() ?
email.IsNullOrEmpty() ?
phoneNumber.IsNullOrEmpty() ? null : phoneNumber : email : userName : id;
//
// Check param ...
if (forceNotNull &&
selectByParam.IsNullOrEmpty())
{
XException.InvalidData.Throw();
}
//
return selectByParam;
}
/// <summary>
/// Retrieve UserSelectByParam based on <see>XActionRequestToken</see> instance
/// </summary>
/// <param name="request">an instance of <see>XActionRequestToken</see> which
/// provides required informations</param>
/// <param name="forceNotNull">a boolean value which specify the identifier must nut empty,
/// and throw exception if it is empty, default is true</param>
/// <param name="excludes">a collection of <see>XUserSelectBy</see> members which
/// exclude them from result, default is null</param>
/// <returns>a user identifier</returns>
private string GetUserSelectByParam(
XActionRequestToken request,
bool forceNotNull = true,
ICollection<XUserSelectBy> excludes = null
)
{
//
// Validate Args ...
ValidationProvider.NotNull(request);
//
// Generate User SelectBy ...
return GetUserSelectByParam(
context: request.Context,
forceNotNull: forceNotNull,
excludes: excludes
);
}
/// <summary>
/// Retrieve Available UserSelectByParams from given XUser
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <returns></returns>
private ICollection<string> GenerateUserSelectByParams(
XUser user
)
{
//
if (user == null)
{
return null;
}
//
var result = new List<string> {
user.Id,
user.UserName,
user.PhoneNumber,
user.Email,
};
//
return result;
}
/// <summary>
/// Retrieve Required XUserSelectByTypes
/// </summary>
/// <param name="ignores">a collection of <see>XUserSelectBy</see> members which
/// ignored in result</param>
/// <returns>a collection of available <see>XUserSelectBy</see> members</returns>
private ICollection<XUserSelectBy> GenerateUserSelectByExcludes(
ICollection<XUserSelectBy> ignores
)
{
//
var result = ObjectHelper
.ToEnumerableValues<XUserSelectBy>()
.Except(ignores);
//
return result.ToList();
}
#endregion
//
#region XAction Actions ...
/// <summary>
/// Retrieve Action Result Response based on XToken
/// </summary>
/// <param name="xToken">an instance of <see>XToken</see></param>
/// <returns>an instance of <see>XActionResponse</see></returns>
private XActionResponse GetActionResultResponse(
XToken xToken
)
{
//
ValidationProvider.NotNull(xToken);
ValidationProvider.NotEmpty(xToken.Hash, xToken.Token);
//
ValidateToken(xToken.Token);
//
var expDate = GetTokenExpirationDate(xToken.Token);
ValidationProvider.NotNull(expDate);
//
// Generate Result class ...
var result = new XActionResponse
{
Token = xToken.Hash,
Expiration = expDate
};
//
return result;
}
#endregion
//
#region XIdentityMessage Actions ...
/// <summary>
/// Prepare Message Provider and Check it's State
/// </summary>
/// <param name="lang">specify destination language</param>
private void HandleMessageProviderPreperation(
string lang
)
{
//
ValidationProvider
.NotEmpty(lang);
//
// Prepare Identity Messages ...
// the PrepareMessageMethod check IsReady of Helper automatically ...
IdentityMessageProvider.PrepareMessages(lang);
//
// Check Message Provider ...
var isMessageProviderReady = MessageProvider.IsReady(true);
if (!isMessageProviderReady)
{
XException.MessageServiceInitialFailed.Throw();
}
}
#endregion
//
#region Friendship Actions ...
/// <summary>
/// Check Follow Request Sent Before
/// from dest to source
/// </summary>
/// <param name="source">an instance of <see>XUser</see></param>
/// <param name="dest">an instance of <see>XUser</see></param>
/// <returns>a boolean value</returns>
private bool IsFollowRequested(
XUser source,
XUser dest
)
{
//
// Validate Args ...
ValidationProvider.NotNull(source, dest);
//
var result = source.Followings.Any(f => f.DestId == dest.Id);
//
return result;
}
/// <summary>
/// Check Follow Request Sent Before
/// from dest to source
/// </summary>
/// <param name="source">an instance of <see>XUser</see></param>
/// <param name="dest">an instance of <see>XUser</see></param>
/// <param name="state">a member of <see>XFriendshipState</see></param>
/// <returns>a boolean value</returns>
private bool IsFollowRequested(
XUser source,
XUser dest,
XFriendshipState state
)
{
//
// Validate Args ...
ValidationProvider.NotNull(source, dest);
//
var result = source.Followings
.Any(f =>
f.DestId == dest.Id &&
f.State == state);
//
return result;
}
/// <summary>
/// Converts to XFriendDto ...
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
private async Task<XFriendDto> ToDto(XFriendshipFollower entity)
{
//
XFriendDto result = null;
//
if (entity.IsNull())
{
return result;
}
//
// Reading Dest User Profile ...
var profile = await GetUserProfileAsync(
userSelectByParam: entity.DestId,
requestedUserSelectByParam: entity.UserId,
forceCheckRequestedUser: false,
checkCanLoginPolicies: false,
checkIsBanned: false
);
//
// Preparing Result ...
result = new XFriendDto
{
UserId = profile.UserId,
Avatar = profile.Avatar,
Username = profile.UserName,
Lastname = profile.LastName,
Firstname = profile.FirstName,
Type = XFriendshipType.Follower,
State = profile.FriendshipInfo.FollowerState,
};
//
return result;
}
/// <summary>
/// Converts to XFriendDto ...
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
private async Task<XFriendDto> ToDto(XFriendshipFollowing entity)
{
//
XFriendDto result = null;
//
if (entity.IsNull())
{
return result;
}
//
// Reading Dest User Profile ...
var profile = await GetUserProfileAsync(
checkIsBanned: false,
checkCanLoginPolicies: false,
forceCheckRequestedUser: false,
userSelectByParam: entity.DestId,
requestedUserSelectByParam: entity.UserId
);
//
// Preparing Result ...
result = new XFriendDto
{
UserId = profile.UserId,
Avatar = profile.Avatar,
Username = profile.UserName,
Lastname = profile.LastName,
Firstname = profile.FirstName,
Type = XFriendshipType.Following,
State = profile.FriendshipInfo.FollowingState,
};
//
return result;
}
#endregion
//
#region User Profile ...
/// <summary>
/// Count User's Page
/// </summary>
/// <param name="pageSize">an integer value which represent page size</param>
/// <returns>an inetger value which represent pages count</returns>
public async Task<int> UserPagesCount(
int pageSize
)
{
//
int count = await UserManager.Users.CountAsync();
int pagesCount = count / pageSize;
//
if (count % pageSize > 0)
{
pagesCount++;
}
//
return pagesCount;
}
/// <summary>
/// Count Avatar's Page
/// </summary>
/// <param name="pageSize">an integer value which represent page size</param>
/// <returns>an inetger value which represent pages count</returns>
public async Task<int> ProfileImagePagesCount(
int pageSize
)
{
//
int count = await DbContext.Avatars.CountAsync();
int pagesCount = count / pageSize;
//
if (count % pageSize > 0)
{
pagesCount++;
}
//
return pagesCount;
}
#endregion
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,110 @@
using System.Linq;
using System.Threading.Tasks;
using xCommons.Extensions;
using xExceptions.Constants;
using xDataService.Extensions;
using xIds.Extensions;
using xModels.Dtos;
using xIdentityModels.Extensions;
namespace xIds.Providers
{
public partial class XIdentityManager
{
/// <summary>
/// Retrieve Users as Query Model for Query Service ...
/// </summary>
/// <param name="requestedUserSelectByParam"></param>
/// <param name="query"></param>
/// <param name="role"></param>
/// <param name="forceRole"></param>
/// <returns></returns>
public async Task<XQueryResult<string>> QueryUsers(
string requestedUserSelectByParam,
XQuery query,
string role = null,
bool forceRole = false
)
{
//
// Validation ...
if (query.IsNull())
{
XException.InvalidArgs.Throw();
}
//
// Normalize ...
query = query.NormalizeQuery(DataConfiguration.ToXDataServiceConfig());
//
// Since in Resourceable Entities we have to Search on Locales
// we Must Implement Senario Custom ...
var items = GetUsersDbSet()
.ToList()
.Where(u => !u.ContainsUserSelectByParam(requestedUserSelectByParam))
.ToList()
.AsEnumerable();
//
var totalItemsCount = items.Count();
//
// Apply Filter ...
if (!query.Filter.IsNullOrEmpty())
{
//
items = items
.ApplyFilter(query.Filter);
}
int filteredItemsCount = items.Count();
//
// Count Pages ...
var totalPagesCount = query.CountPages(totalItemsCount);
var filteredPagesCount = query.CountPages(filteredItemsCount);
//
// Apply Paging and Sorting ...
if (totalItemsCount > 0 &&
filteredItemsCount > 0)
{
//
// Apply Sorting ...
items = items
.ToList()
.ApplySorting(
query.SortBy,
query.IsAscending
);
//
// Apply Paging ...
items = items
.ToList()
.ApplyPaging(
query.Page,
query.PageSize
);
}
//
// Generate Result Object ...
// Query = query,
var result = new XQueryResult<string>
{
Items = items
.Select(u => u.Id),
Page = query.Page,
PageSize = query.PageSize,
TotalPages = totalPagesCount,
TotalItems = totalItemsCount,
TotalFilteredPages = filteredPagesCount,
TotalFilteredItems = filteredItemsCount
};
//
return result;
}
}
}
@@ -0,0 +1,700 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels;
using xIdentityModels.Constants;
using xIdentityModels.Models;
using xIdentityModels.Navigations;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Registration Actions ...
/// <summary>
/// Invite a User to Register on Dashboard
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="email">which email address is going to invite</param>
/// <param name="returnUrl">return url for invitation user to redirect</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
public async Task<XActionResponse> InviteUser(
string lang,
XDevice device,
string email,
string returnUrl
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(
lang,
email,
returnUrl
)
.AddNotNull(device)
.AddEmailAddress(email)
.ValidateGroupAsync();
//
// Check Email is Unique ...
await ValidateUserNotExists(email);
//
// Get Request Result ...
var result = await ActionRequest(
lang,
device,
email,
XAction.Invite,
forceCheckContext: false,
forceCheckUserExists: false);
//
// Prepare Propper Message ...
var xMessage = GetUserInviteMessage(lang, email, result.Token, returnUrl);
//
// Send Message ...
try
{
await MessageProvider.SendMailAsync(xMessage);
}
catch { }
//
// Send Result ...
return result;
}
/// <summary>
/// Recieve some Basic Informations and Start Registration Proccess
/// if they Valid
///
/// Registration Proccess Starts with Invoking this Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="invitationHash">optional, if user invited, this is the invitation token</param>
/// <param name="firstName">user's FirstName</param>
/// <param name="lastName">user's LastName</param>
/// <param name="dateOfBirth">user's dob date</param>
/// <param name="mobileNumber">user's Mobile Number</param>
/// <param name="emailAddress">user's Email address</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
public async Task<XActionResponse> RequestRegistration(
string lang,
XDevice device,
string invitationHash,
string firstName,
string lastName,
DateTime dateOfBirth,
string mobileNumber = null,
string emailAddress = null
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(
lang,
firstName,
lastName
)
.AddNotNull(
device,
dateOfBirth
)
.ValidateGroupAsync();
//
// Validate Mobile Number if Exists ...
if (!mobileNumber.IsNullOrEmpty())
{
//
ValidationProvider.MobileNumber(mobileNumber);
//
// Check Mobile is Uniqsue ...
await ValidateUserNotExists(mobileNumber);
}
//
// Validate Email Address if Exists ...
if (!emailAddress.IsNullOrEmpty())
{
//
ValidationProvider.EmailAddress(emailAddress);
//
// Check Email is Unique ...
await ValidateUserNotExists(emailAddress);
}
//
// Get Related Token and Validate it and
// Check Related Configurations ...
await ValidateInvitationHash(invitationHash);
//
// Check Device Validation ...
await ValidateDeviceForActions(device);
//
// Validate DateOfBirth ...
ValidateDateOfBirth(dateOfBirth);
//
// Define Empty Objects for Using ...
var userSelectByParam = "";
var context = new XActionRequestContext();
//
context.Lang = lang;
context.Device = device;
context.LastName = lastName;
context.FirstName = firstName;
context.DateOfBirth = dateOfBirth;
context.MobileNumber = mobileNumber ?? "";
context.Email = !emailAddress.IsNullOrEmpty() ? emailAddress.ToNormalString() : "";
//
userSelectByParam = GetUserSelectByParam(context);
//
// Token Data Parsing ...
if (!invitationHash.IsNullOrEmpty())
{
//
// Get Related Token, Validate it and Parse Contains
// XAction Request and Validate the Request itself and
// Return it ...
var inviteRequest = await ValidateAndParseActionHash(invitationHash);
//
// Extract User Select By Param and Type ...
userSelectByParam = GetUserSelectByParam(inviteRequest);
var userSelectBy = GetUserSelectByType(userSelectByParam);
//
// Check Invitation Only done with Email Address ...
if (userSelectBy != XUserSelectBy.Email)
{
XException.InvalidData.Throw();
}
//
// Check Invited Email and Given Email to be Same ...
if (emailAddress.IsNullOrEmpty() ||
(!emailAddress.IsNullOrEmpty() &&
!emailAddress.IsValidEmail()))
{
XException.InvalidEmailAddress.Throw();
}
var isSameEmail = inviteRequest.Context.Email.ToNormalString() == emailAddress.ToNormalString();
if (!isSameEmail)
{
XException.EmailsSame.Throw();
}
//
// Update Context Email for Request Action ...
context.Email = userSelectByParam.ToNormalString();
context.EmailVerified = true;
}
//
var result = await ActionRequest(
lang,
device,
userSelectByParam,
XAction.Registration,
context,
forceCheckUserExists: false
);
//
// Remove Invitation Token ...
if (!invitationHash.IsNullOrEmpty())
{
await RemoveTokenByHash(invitationHash);
}
//
// Return Result ...
return result;
}
/// <summary>
/// Add User Account Info
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="userName">user name</param>
/// <param name="password">assigne password</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
public async Task<XActionResponse> AddAcountInfo(
string lang,
XDevice device,
string actionToken,
string userName,
string password
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(
lang,
actionToken,
userName,
password)
.AddNotNull(device)
.ValidateGroupAsync();
//
// Check Device Validation ...
await ValidateDeviceForActions(device);
//
// Validate a UserSelector is Available or not ...
await ValidateCanRegister(userName);
//
// Check UserName is Unique ...
await ValidateUserNotExists(userName);
//
// Get Related Token to Registration Hash and Validate it,
// then Parse XActionRequest Instance from Token ...
var request = await ValidateAndParseActionHash(actionToken);
//
// Validate Requeired Data for Register User must be in Action Context ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotNull(
request.Context,
request.Context.Device,
request.Context.DateOfBirth)
.AddNotEmpty(
request.Context.Email,
request.Context.MobileNumber,
request.Context.FirstName,
request.Context.LastName)
.AddEmailAddress(request.Context.Email)
.AddMobileNumber(request.Context.MobileNumber)
.ValidateGroupAsync();
//
// Validate two Device Must Same ...
ValidateRequestAndGivenDevices(device, request.Context.Device);
//
// Extract UserSelectByParam from XActionRequest ...
var userSelectByParam = GetUserSelectByParam(request);
//
// Validate Request Pointer User doesn't Exists ...
await ValidateUserNotExists(userSelectByParam);
//
// Create an Empty Instance of XUser ...
var xUser = new XUser();
//
// Start Filling User Fields ...
xUser.UserName = userName;
xUser.Email = request.Context.Email;
xUser.PhoneNumber = request.Context.MobileNumber;
//
xUser.FirstName = request.Context.FirstName;
xUser.LastName = request.Context.LastName;
xUser.DateOfBirth = request.Context.DateOfBirth;
//
xUser.EmailConfirmed = request.Context.EmailVerified;
xUser.PhoneNumberConfirmed = request.Context.MobileVerified;
//
xUser.CreationDate = DateTime.UtcNow;
xUser.LastLogin = null;
//
#region Handling New User IsEnable State ...
//
// By Default New Users will Enables ...
xUser.IsEnable = true;
xUser.IsBanned = false;
//
// Check Auto Enable Users ...
if (Configuration.RequireRegistrationConfirm)
{
xUser.IsEnable = false;
}
//
// Check Auto Confirm Email Users ...
if (Configuration.AutoConfirmNewUsersEmail)
{
xUser.EmailConfirmed = true;
}
#endregion
//
// Check Auto Confirm Phone Number Users ...
if (Configuration.AutoConfirmNewUsersPhoneNumber)
{
xUser.PhoneNumberConfirmed = true;
}
//
// Validating given Password ...
await ValidatePasswordPolicies(xUser, password);
//
// Now add new User to Database ...
var createUserResult = await CreateUserAsync(xUser, password);
if (!createUserResult.Succeeded)
{
XException.ActionFailed.Throw();
}
//
// Update Request UserId Value ...
request.Context.UserId = xUser.Id;
request.Context.UserName = xUser.UserName;
//
#region Handling New User Role ...
//
// Now Try to Assign Default User role ...
var isContainsDefaultUserRole = !Configuration.NewUsersRole.IsNullOrEmpty();
if (isContainsDefaultUserRole)
{
//
var newUserRoleName = Configuration.NewUsersRole;
var isNewUserRoleExists = await IsRoleExistsAsync(newUserRoleName);
//
// Make Sure New User role Exists ...
if (!isNewUserRoleExists)
{
//
// Create Default Roles ...
await CreateIdentityRoles();
}
//
// Assign User to Role ...
var assignNewUserToRoleResult = await AddUserToRoleAsync(xUser, newUserRoleName);
if (!assignNewUserToRoleResult.Succeeded)
{
//
// Delete User if Role Assignment Faild ...
await UserManager.DeleteAsync(xUser);
//
// Thrown ActionFailed Error ...
XException.ActionFailed.Throw();
}
}
#endregion
//
#region Handle Profile Image ...
// Save and Attach Profile Image to User if it's Added Before ...
if (!request.Context.UserId.IsNullOrEmpty() &&
!request.Context.Thubmnail.IsNull())
{
//
xUser = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: true,
checkCanLoginPolicies: false,
checkIsBanned: false,
ignoreDisabledUser: true,
exception: XException.NotFound.ToException()
);
//
// Handle Saving File and Attach it to User ...
var saveResult = await StorageProvider
.HandleProfileImageSave(request.Context.Thubmnail);
//
var xProfileImage = new XProfileImage
{
UserId = request.Context.UserId,
Name = saveResult.FileName,
Path = saveResult.FilePath,
Thumb = saveResult.Thmbnail,
ThumbPath = saveResult.ThmbnailPath
};
//
xUser.Avatars.Add(xProfileImage);
var updateResult = await UpdateUserAsync(
xUser,
checkCanLoginPolicies: false,
checkIsBanned: false
);
//
if (updateResult.Succeeded)
{
//
xUser.Avatar = xProfileImage.ThumbPath;
await UpdateUserAsync(
xUser,
checkCanLoginPolicies: false,
checkIsBanned: false
);
}
}
#endregion
//
// Handle Attaching User device ...
var xUserDevice = await AddUserRelatedDevice(
userSelectByParam,
device,
checkCanLoginPolicies: false);
//
// Prepare New Token Result Resource,
// by Requesting an Action ...
var result = await ActionRequest(
lang,
device,
userSelectByParam,
XAction.Finish,
request.Context,
forceCheckUserExists: false
);
//
// Remove Previous Token ...
await RemoveTokenByHash(actionToken);
//
// Return Result ...
return result;
}
/// <summary>
/// Attach Profile Image
/// </summary>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="file">an instance of <see>IFormFile</see> for user's Avatar</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
public async Task<XActionResponse> AttachProfileImage(
string actionToken,
IFormFile file
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(actionToken)
.AddNotNull(file)
.ValidateGroupAsync();
//
// Get Related Token to Registration Hash and Validate it,
// then Parse XActionRequest Instance from Token ...
var request = await ValidateAndParseActionHash(actionToken);
//
// Extract UserSelectByParam from XActionRequest ...
var userSelectByParam = GetUserSelectByParam(request);
if (userSelectByParam.IsNullOrEmpty())
{
XException.InvalidData.Throw();
}
//
// Validate Given FormFile as Profile Image ...
StorageProvider.ValidateProfileImage(file);
//
request.Context.Thubmnail = file;
//
// Save and Attach Profile Image to User if it's Added Before ...
if (!request.Context.UserId.IsNullOrEmpty())
{
//
var xUser = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: true,
checkCanLoginPolicies: false,
checkIsBanned: false,
ignoreDisabledUser: true,
exception: XException.NotFound.ToException()
);
//
// Handle Saving File and Attach it to User ...
var saveResult = await StorageProvider.HandleProfileImageSave(file);
//
var xProfileImage = new XProfileImage
{
UserId = request.Context.UserId,
Name = saveResult.FileName,
Path = saveResult.FilePath,
Thumb = saveResult.Thmbnail,
ThumbPath = saveResult.ThmbnailPath
};
//
xUser.Avatars.Add(xProfileImage);
var updateResult = await UpdateUserAsync(
xUser,
checkCanLoginPolicies: false,
checkIsBanned: false
);
//
if (updateResult.Succeeded)
{
//
xUser.Avatar = xProfileImage.ThumbPath;
await UpdateUserAsync(
xUser,
checkCanLoginPolicies: false,
checkIsBanned: false
);
}
}
//
// Remove Invitation Token ...
if (!actionToken.IsNullOrEmpty())
{
await RemoveTokenByHash(actionToken);
}
//
var result = await ActionRequest(
request.Context.Lang,
request.Context.Device,
userSelectByParam,
XAction.ProfileAction,
request.Context,
forceCheckUserExists: false
);
//
// Return Result ...
return result;
}
/// <summary>
/// Finishing Registration
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="userSelectByParam">user identifier</param>
/// <param name="password">assigne password</param>
/// <param name="returnUrl">return url for invitation user to redirect</param>
/// <returns></returns>
public async Task FinishRegistration(
string lang,
XDevice device,
string userSelectByParam,
string password,
string returnUrl
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(lang, userSelectByParam, password, returnUrl)
.AddNotNull(device)
.AddUrl(returnUrl)
.ValidateGroupAsync();
//
// Validate User Exists And Retrieve it ...
var xUser = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: false,
checkCanLoginPolicies: false,
checkIsBanned: false,
ignoreDisabledUser: true,
forceAdmin: false
);
//
#region Handling New User Email Notification Senario ...
//
// Here we Check if User is Enable and Approved Email
// send RegisteredMsg and if User is not Enable and
// Verify Registration value in Configuration is True
// send Verify Registration mail to user and handle it ...
if (Configuration.RequireRegistrationConfirm)
{
//
if (xUser.IsEnable)
{
//
// Send Registration Finished Message ...
// since in this step we need to prevent errors from
// going forward. add this try catch here ...
try
{
//
var xMessage = GetRegistrationFinishedMessage(lang, xUser.Email, returnUrl);
//
await MessageProvider.SendMailAsync(xMessage);
}
catch { }
}
else
{
//
// Send Registration Confirm Message ...
// since in this step we need to prevent errors from
// going forward. add this try catch here ...
try
{
//
var request = await ActionRequest(
lang,
device,
userSelectByParam,
XAction.Finish,
forceCheckContext: false,
forceCheckUserExists: true);
//
var xMessage = GetRegistrationConfirmMessage(lang, xUser.Email, request.Token, returnUrl);
//
await MessageProvider.SendMailAsync(xMessage);
}
catch { }
}
}
// //
// await RemoveTokenByDevice (device);
#endregion
}
#endregion
}
}
@@ -0,0 +1,722 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Constants;
using xIdentityModels.Extensions;
using xIdentityModels.Models;
using xIdentityModels.Navigations;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Action Handlers ...
/// <summary>
/// Request an Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="userSelectByParam">specified user's identifier</param>
/// <param name="step">a member of <see>XAction</see> which represent Action Step</param>
/// <param name="context">an instance of <see>XActionRequestContext</see> which provides required informations for specified step</param>
/// <param name="forceCheckContext">specify checking context, default is false</param>
/// <param name="forceCheckUserExists">specify check user exists, default is true</param>
/// <param name="forceCheckUserDevice">specify check user and device relation, default is false</param>
/// <param name="forceRenewToken">specifies force renew Action Token if it's expired, default is false</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
private async Task<XActionResponse> ActionRequest(
string lang,
XDevice device,
string userSelectByParam,
XAction step,
XActionRequestContext context = null,
bool forceCheckContext = false,
bool forceCheckUserExists = true,
bool forceCheckUserDevice = false,
bool forceRenewToken = false
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(lang);
ValidationProvider.NotNull(device);
//
// Check Device not Banned ...
await ValidateDeviceForActions(device);
//
// Check User Exists and Device Relation ...
if (forceCheckUserExists)
{
if (forceCheckUserDevice)
{
//
if (context.Device != null &&
!device.IsSameAs(context.Device))
{
XException.InvalidDevice.Throw();
}
//
await ValidateUserAndDeviceRelation(userSelectByParam, device);
}
else
{
await ValidateUserExistsAsync(userSelectByParam);
}
}
//
XToken xToken = null;
XActionResponse result = null;
XActionRequestToken xRequest = null;
//
var isExists = false;
var forceUserSelectByParamNotEmpty = true;
switch (step)
{
case XAction.Registration:
//
forceUserSelectByParamNotEmpty = false;
isExists = await IsTokenExistsByDeviceAndType(device, step);
//
if (isExists)
{
xToken = await ValidateAndRetieveTokenByDeviceAndType(device, step);
}
break;
default:
//
isExists = await IsTokenExistsByUserAndType(userSelectByParam, step);
//
if (isExists)
{
xToken = await ValidateAndRetieveTokenByUserAndType(userSelectByParam, step);
}
break;
}
//
if (isExists)
{
//
var isValidToken = IsValidToken(xToken.Token);
if (!isValidToken && !forceRenewToken)
{
switch (step)
{
case XAction.Registration:
case XAction.Invite:
forceRenewToken = true;
break;
default:
throw XException.InvalidToken.ToException();
}
}
//
if (!isValidToken && forceRenewToken)
{
await ValidateAndHandleToken(xToken.Token);
return await ActionRequest(
lang,
device,
userSelectByParam,
step,
context,
forceCheckContext,
forceCheckUserExists,
forceCheckUserDevice,
forceRenewToken);
}
//
xRequest = ValidateAndParseActionToken(xToken.Token);
//
// Check Exists Context ...
if (forceCheckContext)
{
//
var isSameContext = xRequest.Context.IsSameAs(context);
if (!isSameContext)
{
XException.InvalidData.Throw();
}
}
}
else
{
//
if (context != null)
{
//
var contextUserSelectByParam = GetUserSelectByParam(
context,
false,
new List<XUserSelectBy> {
XUserSelectBy.ID,
XUserSelectBy.Email,
XUserSelectBy.MobileNumber
});
var contextLang = context.Lang;
var contextDevice = context.Device;
//
// prevent from issues on Registration Request ...
if (forceUserSelectByParamNotEmpty)
{
var userSelectByType = GetUserSelectByType(userSelectByParam);
var contextUserSelectByType = GetUserSelectByType(contextUserSelectByParam);
if (contextUserSelectByType != userSelectByType)
{
//
var excludesList = GenerateUserSelectByExcludes(new List<XUserSelectBy> { userSelectByType });
//
contextUserSelectByParam = GetUserSelectByParam(context, false, excludesList);
}
}
//
// Fix Device Not Same on Invitation and Request
if (step == XAction.Registration)
{
//
context.Device = device;
contextDevice = context.Device;
}
//
// Validate Context Data must be Same as Request Data ...
if ((!contextUserSelectByParam.IsNullOrEmpty() &&
contextUserSelectByParam != userSelectByParam) ||
(!contextLang.IsNullOrEmpty() &&
contextLang != lang) ||
(contextDevice != null &&
!contextDevice.IsSameAs(device)))
{
XException.InvalidData.Throw();
}
}
else
{
//
context = new XActionRequestContext
{
Lang = lang,
Device = device
};
//
// Add Content ...
var userSelectByType = GetUserSelectByType(
userSelectByParam,
forceUserSelectByParamNotEmpty);
switch (userSelectByType)
{
case XUserSelectBy.MobileNumber:
context.MobileNumber = userSelectByParam;
break;
case XUserSelectBy.Email:
context.Email = userSelectByParam;
break;
case XUserSelectBy.Username:
context.UserName = userSelectByParam;
break;
case XUserSelectBy.ID:
context.UserId = userSelectByParam;
break;
case XUserSelectBy.NotSpecified:
default:
if (forceCheckUserExists)
{
XException.InvalidArgs.Throw();
}
break;
}
}
//
// Generate Registration Request ...
xRequest = new XActionRequestToken
{
Action = step,
Context = context
};
//
// Add User Id if Exists to Context ...
var isUserExists = await IsUserExistsAsync(userSelectByParam);
if (isUserExists)
{
//
var user = await GetUserAsync(userSelectByParam);
if (user != null)
{
xRequest.Context.UserId = user.Id;
}
}
//
xRequest.Prepare(Configuration.IdentitySecretKey);
//
xToken = await HashRequest(
xRequest,
forceUserSelectByParamNotEmpty);
}
//
// When XToken Exists Passed XToken ...
result = GetActionResultResponse(xToken);
//
if (result == null)
{
XException.ActionFailed.Throw();
}
//
return result;
}
/// <summary>
/// Request a Mobile Verification Code
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="mobileNumber">the mobile number which is going to request a verification code</param>
/// <param name="checkMobileInUse">check mobile number is in use or not, default is true</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
private async Task<XActionResponse> RequestMobileVerificationCode(
string lang,
XDevice device,
string actionToken,
string mobileNumber,
bool checkMobileInUse = true
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(lang, actionToken, mobileNumber)
.AddMobileNumber(mobileNumber)
.AddNotNull(device)
.ValidateGroupAsync();
//
// Check Device Validation ...
await ValidateDeviceForActions(device);
//
// Check Mobile is Unique ...
if (checkMobileInUse)
{
var isUserExists = await IsUserExistsAsync(mobileNumber);
if (isUserExists)
{
//
var xUser = await GetUserAsync(mobileNumber);
//
if (xUser.PhoneNumberConfirmed)
{
XException.MobileInUsed.Throw();
}
}
}
//
// Get Related Token to Registration Hash and Validate it,
// then Parse XActionRequest Instance from Token ...
var request = await ValidateAndParseActionHash(actionToken);
//
// Validate two Device Must Same ...
ValidateRequestAndGiveDevices(device, request.Context.Device);
//
// Extract UserSelectByParam from XActionRequest ...
var userSelectByParam = GetUserSelectByParam(
request,
forceNotNull: false,
excludes: new List<XUserSelectBy> {
XUserSelectBy.ID,
XUserSelectBy.Email,
XUserSelectBy.MobileNumber
});
//
// if it's null, means user doesn't have Invitation Token
// and there is no User Selector ...
if (userSelectByParam.IsNullOrEmpty())
{
//
// Since Mobile is Unique so it can act as a
// UserSelector ...
userSelectByParam = mobileNumber;
}
//
// Define empty Verification Code
// and Empty Verification Request ...
var verificationCode = "";
XVerificationRequest xVerificationRequest = null;
//
// Check Device Request Verification Code before or Not ...
var isDeviceRequested = await IsDeviceRequestedForVerificationCodeBefor(device);
if (isDeviceRequested)
{
//
// if Requested before, Retrieve XVerification instance by Device from Db
// and Validate it ...
xVerificationRequest = await ValidateAndRetrieveVerificationRequest(device);
//
if (xVerificationRequest.VerificationCode.IsNullOrEmpty())
{
//
verificationCode = GenerateRandom().ToString();
xVerificationRequest.VerificationCode = verificationCode;
//
await RemoveVerificationCodeRequestByDevice(
device,
saveChanges: false
);
await AddVerificationRequest(
xVerificationRequest,
saveChanges: false
);
//
await DbContext.SaveChangesAsync();
}
//
// if XVerificationRequest is Valid, it must Contain an Alive Verification Code
// so retrieve it and assign it ...
verificationCode = xVerificationRequest.VerificationCode;
}
else
{
//
verificationCode = GenerateRandom().ToString();
}
//
// if it's First Verification Request of given Device
// Generate a new Verification Code ...
if (verificationCode.IsNullOrEmpty())
{
verificationCode = GenerateRandom().ToString();
}
//
// Check Context Mobile and given Mobile ...
if (!request.Context.MobileNumber.IsNullOrEmpty() &&
mobileNumber != request.Context.MobileNumber)
{
XException.InvalidMobileNumber.Throw();
}
//
// Check User Added To Db or Not ...
var isAddedUser = !request.Context.UserId.IsNullOrEmpty();
if (isAddedUser)
{
//
// Validate User Exists and Retireve User Object ...
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: false,
checkCanLoginPolicies: false,
checkIsBanned: false);
//
// Check Mobiles Same ...
if (user.PhoneNumber == mobileNumber &&
user.PhoneNumberConfirmed)
{
XException.MobilesSame.Throw();
}
//
// Update User Object's Mobile Number and
// Confirmation Status ...
user.PhoneNumber = mobileNumber;
user.PhoneNumberConfirmed = false;
//
// Save Changes to Db ...
await UpdateUserAsync(user, checkCanLoginPolicies: false, checkIsBanned: false);
}
//
// Prepare Verification Request by Checking Next Tries,
// Device Status, and etc, Update or Add XVerificationRequest to db
// and Retrieve the added Entity ...
xVerificationRequest = await HandleVerificationRequestPreparation(verificationCode, request);
//
// Update Request Context MobileNumber
// and it's Confirmation Status ...
request.Context.MobileNumber = mobileNumber;
request.Context.MobileVerified = false;
//
// Remove Previous Token ...
await RemoveTokenByHash(actionToken);
//
// Prepare New Token Result Resource,
// by Requesting an Action ...
var result = await ActionRequest(
lang,
device,
userSelectByParam,
XAction.RequestMobileVerificationCode,
request.Context,
forceCheckUserExists: isAddedUser
);
//
// Prepare Message ...
var xMessage = GetVerificationCodeMessage(lang, mobileNumber, verificationCode);
//
// Send Verification Code Message to User Using SMS Provider ...
await MessageProvider.SendSmsAsync(xMessage, throwException: false);
//
// Return Result ...
return result;
}
/// <summary>
/// Request a Email Verification Code
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="emailAddress">the email address which is going to request a verification code</param>
/// <param name="checkEmailInUse">check email address is in use or not, default is true</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
public async Task<XActionResponse> RequestEmailVerificationCode(
string lang,
XDevice device,
string actionToken,
string emailAddress,
bool checkEmailInUse = true
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(lang, actionToken, emailAddress)
.AddEmailAddress(emailAddress)
.AddNotNull(device)
.ValidateGroupAsync();
//
// Check Device Validation ...
await ValidateDeviceForActions(device);
//
// Check Email is Unique ...
if (checkEmailInUse)
{
var isUserExists = await IsUserExistsAsync(emailAddress);
if (isUserExists)
{
//
var xUser = await GetUserAsync(emailAddress);
//
if (xUser.EmailConfirmed)
{
XException.EmailInUsed.Throw();
}
}
}
//
// Get Related Token to Registration Hash and Validate it,
// then Parse XActionRequest Instance from Token ...
var request = await ValidateAndParseActionHash(actionToken);
//
// Validate two Device Must Same ...
ValidateRequestAndGiveDevices(device, request.Context.Device);
//
// Extract UserSelectByParam from XActionRequest ...
var userSelectByParam = GetUserSelectByParam(
request,
forceNotNull: false,
excludes: new List<XUserSelectBy> {
XUserSelectBy.ID,
XUserSelectBy.Email,
XUserSelectBy.MobileNumber
});
//
// if it's null, means user doesn't have Invitation Token
// and there is no User Selector ...
if (userSelectByParam.IsNullOrEmpty())
{
//
// Since Email is Unique so it can act as a
// UserSelector ...
userSelectByParam = emailAddress;
}
//
// Define empty Verification Code
// and Empty Verification Request ...
var verificationCode = "";
XVerificationRequest xVerificationRequest = null;
//
// Check Device Request Verification Code before or Not ...
var isDeviceRequested = await IsDeviceRequestedForVerificationCodeBefor(device);
if (isDeviceRequested)
{
//
// if Requested before, Retrieve XVerification instance by Device from Db
// and Validate it ...
xVerificationRequest = await ValidateAndRetrieveVerificationRequest(device);
//
if (xVerificationRequest.VerificationCode.IsNullOrEmpty())
{
//
verificationCode = GenerateRandom().ToString();
xVerificationRequest.VerificationCode = verificationCode;
//
await RemoveVerificationCodeRequestByDevice(
device,
saveChanges: false
);
await AddVerificationRequest(
xVerificationRequest,
saveChanges: true
);
}
//
// if XVerificationRequest is Valid, it must Contain an Alive Verification Code
// so retrieve it and assign it ...
verificationCode = xVerificationRequest.VerificationCode;
}
else
{
//
verificationCode = GenerateRandom().ToString();
}
//
// Check Context Email and given Email ...
if (!request.Context.Email.IsNullOrEmpty() &&
emailAddress != request.Context.Email)
{
XException.InvalidEmailAddress.Throw();
}
//
// Check User Added To Db or Not ...
var isAddedUser = !request.Context.UserId.IsNullOrEmpty();
if (isAddedUser)
{
//
// Validate User Exists and Retireve User Object ...
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: false,
checkCanLoginPolicies: false,
checkIsBanned: false);
//
// Check Email Same ...
if (user.Email == emailAddress &&
user.EmailConfirmed)
{
XException.MobilesSame.Throw();
}
//
// Update User Object's Email and
// Confirmation Status ...
user.Email = emailAddress;
user.EmailConfirmed = false;
//
// Save Changes to Db ...
await UpdateUserAsync(
user,
checkCanLoginPolicies: false,
checkIsBanned: false
);
}
//
// Prepare Verification Request by Checking Next Tries,
// Device Status, and etc, Update or Add XVerificationRequest to db
// and Retrieve the added Entity ...
xVerificationRequest = await HandleVerificationRequestPreparation(verificationCode, request);
//
// Update Request Context Email
// and it's Confirmation Status ...
request.Context.Email = emailAddress;
request.Context.EmailVerified = false;
//
// Prepare New Token Result Resource,
// by Requesting an Action ...
var result = await ActionRequest(
lang,
device,
userSelectByParam,
XAction.RequestEmailVerificationCode,
request.Context,
forceCheckUserExists: isAddedUser
);
//
// Remove Hash ...
await RemoveTokenByHash(actionToken);
//
// Prepare Message ...
var xMessage = GetVerificationCodeMessage(lang, emailAddress, verificationCode);
//
// Send Verification Code Message to User Using Mail Provider ...
await MessageProvider.SendMailAsync(xMessage, throwException: false);
//
// Return Result ...
return result;
}
#endregion
}
}
@@ -0,0 +1,273 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Role Actions ...
/// <summary>
/// Check a Role exists or not
/// </summary>
/// <param name="roleName">role name</param>
/// <returns>a boolean value</returns>
public async Task<bool> IsRoleExistsAsync(
string roleName
)
{
//
if (roleName.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
return await RoleManager.RoleExistsAsync(roleName);
}
/// <summary>
/// Create a new Role
/// </summary>
/// <param name="roleName">role name</param>
/// <returns>an instance of <see>IdentityResult</see></returns>
public async Task<IdentityResult> CreateRoleAsync(
string roleName
)
{
//
var isExists = await RoleManager
.RoleExistsAsync(roleName);
if (isExists)
{
XException.Duplicate.Throw();
}
//
var result = await RoleManager
.CreateAsync(new IdentityRole(roleName));
//
return result;
}
/// <summary>
/// Get a Role
/// </summary>
/// <param name="roleName">role name</param>
/// <returns>an instance of <see>IdentityRole</see></returns>
public async Task<IdentityRole> GetRoleAsync(
string roleName
)
{
//
var isExists = await IsRoleExistsAsync(roleName);
if (!isExists)
{
XException.NotFound.Throw();
}
//
var result = await RoleManager
.FindByNameAsync(roleName);
//
return result;
}
/// <summary>
/// Remove a User From Role
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <param name="roleName">role name</param>
/// <returns>an instance of <see>IdentityResult</see></returns>
public async Task<IdentityResult> RemoveFromRoleAsync(
XUser user,
string roleName
)
{
return await UserManager.RemoveFromRoleAsync(user, roleName);
}
/// <summary>
/// Remove a User From Roles
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <param name="roleNames">a collection of role names</param>
/// <returns>an instance of <see>IdentityResult</see></returns>
public async Task<IdentityResult> RemoveFromRolesAsync(
XUser user,
IEnumerable<string> roleNames
)
{
return await UserManager.RemoveFromRolesAsync(user, roleNames);
}
/// <summary>
/// Assign a User to Specific Role
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <param name="roleName">role name</param>
/// <returns>an instance of <see>IdentityResult</see></returns>
public async Task<IdentityResult> AddUserToRoleAsync(
XUser user,
string roleName
)
{
//
if (user == null ||
roleName.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
var userSelectByParam = GetUserSelectByParam(user);
var isExistsUser = await IsUserExistsAsync(userSelectByParam);
var isExistsRole = await IsRoleExistsAsync(roleName);
if (!isExistsUser || !isExistsRole)
{
XException.NotFound.Throw();
}
//
var result = await UserManager
.AddToRoleAsync(user, roleName);
//
return result;
}
/// <summary>
/// Retrieve Role Name by it's ID
/// </summary>
/// <param name="roleId">role id</param>
/// <returns>role name as string</returns>
public async Task<string> GetRoleNameAsync(
string roleId
)
{
//
// Validate Args ...
if (roleId.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
var role = await RoleManager
.FindByIdAsync(roleId);
//
return role.Name.ToNormalString();
}
/// <summary>
/// Get a List Of User Roles
/// </summary>
/// <param name="userSelectByParam">user identifier</param>
/// <param name="checkCanLoginPolicies"></param>
/// <param name="checkIsBanned"></param>
/// <returns>a collection of role names</returns>
public async Task<IEnumerable<string>> GetRoleNamesAsync(
string userSelectByParam,
bool checkCanLoginPolicies = true,
bool checkIsBanned = true
)
{
//
// Validate Args ...
if (userSelectByParam.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: true,
checkCanLoginPolicies: checkCanLoginPolicies,
checkIsBanned: checkIsBanned
);
//
var result = new List<string>();
foreach (var role in user.Roles)
{
//
var roleName = await GetRoleNameAsync(role.RoleId);
roleName = roleName.ToNormalString();
//
result.Add(roleName);
}
//
return result;
}
/// <summary>
/// Check a User is Adminr not
/// </summary>
/// <param name="userSelectByParam">user identifier</param>
/// <returns>a boolean value</returns>
public async Task<bool> IsAdmin(
string userSelectByParam
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(userSelectByParam);
//
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: true,
checkCanLoginPolicies: true);
//
var result = await IsAdmin(user);
//
return result;
}
/// <summary>
/// Check a User is Adminr not
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <returns>a boolean value</returns>
public async Task<bool> IsAdmin(
XUser user
)
{
//
// Validate Args ...
ValidationProvider.NotNull(user);
ValidationProvider.NotEmpty(user.Id);
//
// Retrieve Admin Role ...
// TODO: Fix this ...
var adminRole = "admin";
// XUserRole.Admin
// .GetStringValue ()
// .ToNormalString ();
//
// Check Admin Role Exists in User Roles or not ...
var userRoleNames = await GetRoleNamesAsync(user.Id);
var isContainsAdminRole = userRoleNames.Contains(adminRole);
//
// Return Result ...
return isContainsAdminRole;
}
#endregion
}
}
@@ -0,0 +1,119 @@
using System.Threading.Tasks;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Dtos;
using xModels.Dtos;
using xDataService.Extensions;
using xIds.Extensions;
using System.Linq;
using xIdentityModels.Extensions;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Actions ...
/// <summary>
/// Query User Profiles
/// </summary>
/// <param name="requestedUserSelectByParam">requested user's identifier</param>
/// <param name="query">how to filter results based on <see>XQuery</see> structure</param>
/// <returns>an instance of <see>XQueryResult</see> of <see>XUserProfileDto</see></returns>
public async Task<XQueryResult<XUserProfileDto>> QueryOpenToSearchUsers(
string requestedUserSelectByParam,
XQuery query
)
{
//
// Validation ...
if (query.IsNull())
{
XException.InvalidArgs.Throw();
}
//
// Normalize ...
query = query.NormalizeQuery(DataConfiguration.ToXDataServiceConfig());
//
// Since in Resourceable Entities we have to Search on Locales
// we Must Implement Senario Custom ...
var totalEntities = GetUsersDbSet()
.Where(u => u.OpenToSearch)
.ToList()
.Where(u => !u.ContainsUserSelectByParam(requestedUserSelectByParam))
.Select(u => u.Id)
.ToList();
//
var items = await GetUserProfilesAsync(
totalEntities,
requestedUserSelectByParam,
checkIsBanned: false,
checkCanLoginPolicies: false,
forceCheckRequestedUser: false
);
//
var totalItemsCount = items.Count();
//
// Apply Filter ...
if (!query.Filter.IsNullOrEmpty())
{
//
items = items
.ApplyFilter(query.Filter);
}
int filteredItemsCount = items.Count();
//
// Count Pages ...
var totalPagesCount = query.CountPages(totalItemsCount);
var filteredPagesCount = query.CountPages(filteredItemsCount);
//
// Apply Paging and Sorting ...
if (totalItemsCount > 0 &&
filteredItemsCount > 0)
{
//
// Apply Sorting ...
items = items
.ToList()
.ApplySorting(
query.SortBy,
query.IsAscending
);
//
// Apply Paging ...
items = items
.ToList()
.ApplyPaging(
query.Page,
query.PageSize
);
}
//
// Generate Result Object ...
// Query = query,
var result = new XQueryResult<XUserProfileDto>
{
Items = items,
Page = query.Page,
PageSize = query.PageSize,
TotalPages = totalPagesCount,
TotalItems = totalItemsCount,
TotalFilteredPages = filteredPagesCount,
TotalFilteredItems = filteredItemsCount
};
//
return result;
}
#endregion
}
}
@@ -0,0 +1,132 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Descriptors;
using xIdentityModels.Extensions;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Default Identity Preparation Actions ...
/// <summary>
/// Create Default Roles based on Identity Configuration
/// </summary>
/// <returns></returns>
public async Task CreateIdentityRoles()
{
//
if (Configuration == null ||
Configuration.IdentityRoles == null ||
Configuration.IdentityRoles.Count == 0)
{
return;
}
//
// Check New Users Role exists in IdentityRole or not ...
if (!Configuration.NewUsersRole.IsNullOrEmpty())
{
//
// Ensure NewUsersRole Exists in IdentityRoles ...
var isNewUserRoleExistsInIdentityRoles = Configuration
.IdentityRoles
.Contains(Configuration.NewUsersRole);
if (!isNewUserRoleExistsInIdentityRoles)
{
XException.InvalidArgs.Throw();
}
}
//
// loop through all roles and create them one by one ...
foreach (var roleName in Configuration.IdentityRoles)
{
//
var isRoleExists = await RoleManager.RoleExistsAsync(roleName);
if (!isRoleExists)
{
//
var result = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
}
/// <summary>
/// Create a user Based on User Descriptor
/// </summary>
/// <param name="user">an instance of <see>XIdentityUserDescriptor</see></param>
/// <returns></returns>
public async Task CreateUserAsync(
XIdentityUserDescriptor user
)
{
//
// Validate Args ...
ValidationProvider.NotNull(user);
//
var isExistsRole = await RoleManager.RoleExistsAsync(user.Role);
if (!isExistsRole)
{
//
// Check Assigned role to user exists in app roles ...
if (!Configuration
.IdentityRoles
.Contains(user.Role))
{
XException.InvalidArgs.Throw();
}
//
// Ensure All Roles Created ...
await CreateIdentityRoles();
}
//
// Check User Exists ...
var isExistsUser = await IsUserExistsAsync(user.UserName);
if (!isExistsUser)
{
//
// Create a DashboardUser instance based on Configuration Data ...
var userEntity = user.ToXUser();
//
// Try to Create User ...
var userCreateResult = await CreateUserAsync(
userEntity,
user.Password
);
if (!userCreateResult.Succeeded)
{
XException.ActionFailed.Throw();
}
//
// Assign Admin User to it's Role ...
var roleAssignResult = await UserManager.AddToRoleAsync(userEntity, user.Role);
if (!roleAssignResult.Succeeded)
{
XException.ActionFailed.Throw();
}
//
var claims = await ToJwtClaims(
userEntity,
checkCanLoginPolicies: true,
checkIsBanned: true);
//
var addClaimsResult = await UserManager.AddClaimsAsync(userEntity, claims);
if (!addClaimsResult.Succeeded)
{
XException.ActionFailed.Throw();
}
}
}
#endregion
}
}
@@ -0,0 +1,742 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using xCommons.Extensions;
using xCommons.Helpers;
using xExceptions.Constants;
using xIdentityModels;
using xIdentityModels.Constants;
using xIdentityModels.Extensions;
using xIdentityModels.Models;
using xIdentityModels.Navigations;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Token Actions ...
/// <summary>
/// Check a Token Entity Exists or not
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="type">specifies Action Step by a member of <see>XAction</see></param>
/// <returns>a boolean value</returns>
private async Task<bool> IsTokenExistsByDeviceAndType(
XDevice device,
XAction type
)
{
//
ValidationProvider.NotNull(device);
//
var isExists = false;
var enumerator = DbContext.Tokens.AsAsyncEnumerable();
await
foreach (var token in enumerator)
{
//
if (token.Type == type &&
token.Device.IsSameAs(device))
{
isExists = true;
break;
}
}
//
return isExists;
}
/// <summary>
/// Check a Token Entity Exists or not
/// </summary>
/// <param name="userSelectByParam">specified user's identifier</param>
/// <param name="type">specifies Action Step by a member of <see>XAction</see></param>
/// <returns>a boolean value</returns>
private async Task<bool> IsTokenExistsByUserAndType(
string userSelectByParam,
XAction type
)
{
//
ValidationProvider.NotEmpty(userSelectByParam);
//
var isExists = false;
var tokensEnumerator = DbContext.Tokens.AsAsyncEnumerable();
await
foreach (var token in tokensEnumerator)
{
//
if (token.Type == type &&
token.UserSelectByParam
.ToNormalString() == userSelectByParam
.ToNormalString())
{
//
isExists = true;
break;
}
}
//
return isExists;
}
/// <summary>
/// Check a Token Entity Exists or not
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns>a boolean value</returns>
private async Task<bool> IsTokenExistsByToken(
string token
)
{
//
var result = await DbContext.Tokens
.AnyAsync(xt => xt.Token == token);
//
return result;
}
/// <summary>
/// Check a Token Entity Exists or not
/// </summary>
/// <param name="hash">represent specified action token hash</param>
/// <returns>a boolean value</returns>
private async Task<bool> IsTokenExistsByHash(
string hash
)
{
//
var result = await DbContext.Tokens
.AnyAsync(xt => xt.Hash == hash);
//
return result;
}
/// <summary>
/// Check a Token Entity Exists or not
/// </summary>
/// <param name="item">an instance of <see>XToken</see></param>
/// <returns>a boolean value</returns>
private async Task<bool> IsTokenExistsByToken(
XToken item
)
{
//
var result = false;
var tokenEnumerator = DbContext.Tokens
.AsAsyncEnumerable();
await
foreach (var token in tokenEnumerator)
{
//
if (token.Hash == item.Hash &&
token.Token == item.Token &&
token.Type == item.Type &&
token.UserSelectByParam
.ToNormalString() == item.UserSelectByParam
.ToNormalString() &&
token.Device.IsSameAs(item.Device))
{
//
result = true;
break;
}
}
//
return result;
}
/// <summary>
/// Check a Token Entity Exists or not
/// </summary>
/// <param name="id">specifies token id</param>
/// <returns>a boolean value</returns>
private async Task<bool> IsTokenExistsById(
int id
)
{
return await DbContext.Tokens.AnyAsync(t => t.Id == id);
}
/// <summary>
/// Retrieve Token Entity
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="type">specifies Action Step by a member of <see>XAction</see></param>
/// <returns>an instance of <see>XToken</see></returns>
private async Task<XToken> GetTokenByDeviceAndType(
XDevice device,
XAction type
)
{
//
var isExists = await IsTokenExistsByDeviceAndType(device, type);
if (!isExists)
{
return null;
}
//
XToken item = null;
var enumerator = DbContext.Tokens.AsAsyncEnumerable();
await
foreach (var token in enumerator)
{
//
if (token.Type == type &&
token.Device.IsSameAs(device))
{
//
item = token;
break;
}
}
//
return item;
}
/// <summary>
/// Retrieve Token Entity
/// </summary>
/// <param name="userSelectByParam">specified user's identifier</param>
/// <param name="type">specifies Action Step by a member of <see>XAction</see></param>
/// <returns>an instance of <see>XToken</see></returns>
private async Task<XToken> GetTokenByUserAndType(
string userSelectByParam,
XAction type
)
{
//
var isExists = await IsTokenExistsByUserAndType(userSelectByParam, type);
if (!isExists)
{
return null;
}
//
XToken item = null;
var enumerator = DbContext.Tokens.AsAsyncEnumerable();
await
foreach (var token in enumerator)
{
//
if (token.Type == type &&
token.UserSelectByParam
.ToNormalString() == userSelectByParam
.ToNormalString())
{
//
item = token;
break;
}
}
//
return item;
}
/// <summary>
/// Retrieve a Token Entity
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns>an instance of <see>XToken</see></returns>
private async Task<XToken> GetTokenByToken(
string token
)
{
//
var isExists = await IsTokenExistsByToken(token);
if (!isExists)
{
return null;
}
//
var result = await DbContext.Tokens
.FirstOrDefaultAsync(xt => xt.Token == token);
//
return result;
}
/// <summary>
/// Retrieve Related Hash to a Token
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns>hash string</returns>
private async Task<string> GetRelatedHashByToken(
string token
)
{
//
var item = await GetTokenByToken(token);
if (item == null)
{
return null;
}
//
return item.Hash;
}
/// <summary>
/// Retrieve a Token Entity
/// </summary>
/// <param name="hash">represent specified action token hash</param>
/// <returns>an instance of <see>XToken</see></returns>
private async Task<XToken> GetTokenByHash(
string hash
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(hash);
//
var isExists = await IsTokenExistsByHash(hash);
if (!isExists)
{
return null;
}
//
var item = await DbContext.Tokens
.FirstOrDefaultAsync(t => t.Hash == hash);
//
return item;
}
/// <summary>
/// Retrieve Related Token to a Hash string
/// </summary>
/// <param name="hash">represent specified action token hash</param>
/// <returns>token string</returns>
private async Task<string> GetRelatedTokenByHash(
string hash
)
{
//
var item = await GetTokenByHash(hash);
if (item == null)
{
return null;
}
//
return item.Token;
}
/// <summary>
/// Retrieve a Token Expiration Date
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns>an instance of <see>DateTime</see></returns>
private DateTime GetTokenExpirationDate(
string token
)
{
//
ValidationProvider.NotEmpty(token);
//
var result = IdentityHelper.GetTokenExpirationDate(token);
//
return result;
}
/// <summary>
/// Remove a Token Entity
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns></returns>
private async Task RemoveTokenByToken(
string token
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(token);
//
var isExists = await IsTokenExistsByToken(token);
if (!isExists)
{
return;
}
//
var item = await GetTokenByToken(token);
if (item == null)
{
return;
}
//
DbContext.Tokens.Remove(item);
await DbContext.SaveChangesAsync();
}
/// <summary>
/// Remove a Token Entity
/// </summary>
/// <param name="hash">represent specified action token hash</param>
/// <returns></returns>
private async Task RemoveTokenByHash(
string hash
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(hash);
//
var isExists = await IsTokenExistsByHash(hash);
if (!isExists)
{
return;
}
//
var item = await GetTokenByHash(hash);
if (item == null)
{
return;
}
//
DbContext.Tokens.Remove(item);
await DbContext.SaveChangesAsync();
}
/// <summary>
/// Remove a Token Entity
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns></returns>
private async Task RemoveTokenByDevice(
XDevice device
)
{
//
// Validate Args ...
ValidationProvider.NotNull(device);
//
XToken items = null;
var enumerator = DbContext.Tokens.AsAsyncEnumerable();
await
foreach (var token in enumerator)
{
//
if (token.Device.IsSameAs(device))
{
//
items = token;
break;
}
}
//
if (!items.IsNull())
{
DbContext.Tokens.RemoveRange(items);
await DbContext.SaveChangesAsync();
}
}
/// <summary>
/// add a token Entity
/// </summary>
/// <param name="item">an instance of <see>XToken</see></param>
/// <returns>an instance of <see>XToken</see></returns>
private async Task<XToken> AddTokenAsync(
XToken item
)
{
//
var isExist = await IsTokenExistsByToken(item);
if (isExist)
{
return await GetTokenByHash(item.Hash);
}
//
var entry = await DbContext.Tokens.AddAsync(item);
await DbContext.SaveChangesAsync();
//
return entry.Entity;
}
/// <summary>
/// Hash a Request Token
/// </summary>
/// <param name="request">an instance of <see>XActionRequestToken</see></param>
/// <param name="forceNotNullUserSelectByParam"></param>
/// <param name="forceDeviceNotNull"></param>
/// <returns>an instance of <see>XToken</see></returns>
private async Task<XToken> HashRequest(
XActionRequestToken request,
bool forceNotNullUserSelectByParam = true,
bool forceDeviceNotNull = true
)
{
//
// Validate Args ...
var secretKey = Configuration.IdentitySecretKey;
ValidationProvider.NotNull(request);
ValidationProvider.NotEmpty(secretKey);
//
ValidateActionRequest(request);
//
var type = request.Action;
var device = request.Context.Device;
var tokenObject = ToSecurityToken(request);
var tokenString = ToTokenString(tokenObject);
var tokenHash = ToHash(tokenString);
var userSelectByParam = GetUserSelectByParam(request, forceNotNullUserSelectByParam);
//
if (forceNotNullUserSelectByParam)
{
ValidationProvider.NotEmpty(userSelectByParam);
}
//
ValidationProvider.NotEmpty(tokenString, tokenHash);
//
if (forceDeviceNotNull)
{
ValidationProvider.NotNull(device);
}
//
ValidationProvider.NotNull(tokenObject);
//
var xToken = new XToken
{
Type = type,
Device = device,
Hash = tokenHash,
Token = tokenString,
UserSelectByParam = userSelectByParam
};
//
var addedToken = await AddTokenAsync(xToken);
//
return addedToken;
}
/// <summary>
/// Generate Token Hash string
/// </summary>
/// <param name="token">represent specified action token</param>
/// <param name="checkValidation">check token validation parameters, default is true</param>
/// <returns>hash string</returns>
private string ToHash(
string token,
bool checkValidation = true
)
{
//
ValidationProvider.NotEmpty(token);
//
if (checkValidation)
{
ValidateToken(token);
}
//
var result = token.ToMd5String().ToNormalString();
//
return result;
}
/// <summary>
/// Generate Token string
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns>token string</returns>
private string ToTokenString(
SecurityToken token
)
{
//
ValidationProvider.NotNull(token, IdentityHelper);
//
var result = IdentityHelper.ToTokenString(token);
//
return result;
}
/// <summary>
/// Generate Token string
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns>token string</returns>
private string ToTokenString(
JwtSecurityToken token
)
{
//
ValidationProvider.NotNull(token, IdentityHelper);
//
var result = IdentityHelper.ToTokenString(token);
//
return result;
}
/// <summary>
/// Generate Security Token
/// </summary>
/// <param name="token">represent specified action token</param>
/// <returns>an instance of <see>SecurityToken</see></returns>
private SecurityToken ToSecurityToken(
string token
)
{
//
ValidationProvider.NotEmpty(token);
//
var result = IdentityHelper.ToSecurityToken(token);
//
return result;
}
/// <summary>
/// Generate Security Token
/// </summary>
/// <param name="request">an instance of <see>XActionRequestToken</see></param>
/// <returns>an instance of <see>SecurityToken</see></returns>
private SecurityToken ToSecurityToken(
XActionRequestToken request
)
{
//
ValidationProvider.NotNull(request);
//
ValidateActionRequest(request);
//
var result = IdentityHelper.ToSecurityToken(request);
if (result == null)
{
XException.InvalidToken.Throw();
}
//
return result;
}
/// <summary>
/// Cleanup all exists tokens related to specified User
/// </summary>
/// <param name="userSelectByParam">specified user's identifier</param>
/// <returns></returns>
private async Task HandleRemoveExistsTokens(
string userSelectByParam
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(userSelectByParam);
//
var xTokens = new List<XToken>();
var xTokenEnumerator = DbContext.Tokens.AsAsyncEnumerable();
await
foreach (var xToken in xTokenEnumerator)
{
//
if (xToken.UserSelectByParam == userSelectByParam &&
ObjectHelper.ToEnumerableValues<XAction>().Contains(xToken.Type))
{
xTokens.Add(xToken);
}
}
//
if (xTokens.HasChild())
{
DbContext.Tokens.RemoveRange(xTokens);
await DbContext.SaveChangesAsync();
}
}
/// <summary>
/// Cleanup all tokens related to specified User
/// </summary>
/// <param name="userSelectByParams">a collection of user identifiers</param>
/// <returns></returns>
private async Task HandleCleanUserTokens(
ICollection<string> userSelectByParams
)
{
//
// Validate Args ...
if (!userSelectByParams.HasChild())
{
return;
}
//
foreach (var userSelectByParam in userSelectByParams)
{
await HandleRemoveExistsTokens(userSelectByParam);
}
}
/// <summary>
/// Cleanup all tokens related to specified User
/// </summary>
/// <param name="user">an instance of <see>XUser</see></param>
/// <returns></returns>
private async Task HandleCleanUserTokens(
XUser user
)
{
//
if (user == null)
{
return;
}
//
var userSelectByParams = GenerateUserSelectByParams(user);
//
await HandleCleanUserTokens(userSelectByParams);
}
#endregion
}
}
@@ -0,0 +1,503 @@
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
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,220 @@
using System.Threading.Tasks;
using xCommons.Extensions;
using xIdentityModels.Extensions;
using xIdentityModels.Models;
using xIdentityModels.Navigations;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Verification Code Actions ...
/// <summary>
/// Check a Device Requeste for Verification Code before or not
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>a boolean value</returns>
private async Task<bool> IsDeviceRequestedForVerificationCodeBefor(
XDevice device
)
{
//
var result = false;
var enumerator = DbContext.VerificationRequests
.AsAsyncEnumerable();
await
foreach (var entity in enumerator)
{
//
if (entity.Device.IsSameAs(device))
{
//
result = true;
break;
}
}
//
return result;
}
/// <summary>
/// Check a Verification Code Requested before or not
/// </summary>
/// <param name="verificationCode">Confirm Verification Code</param>
/// <returns>a boolean value</returns>
private async Task<bool> IsVerificationCodeRequested(
string verificationCode
)
{
//
// Validate Args ...
if (verificationCode.IsNullOrEmpty())
{
return false;
}
//
var result = false;
var enumerator = DbContext.VerificationRequests
.AsAsyncEnumerable();
await
foreach (var entity in enumerator)
{
//
if (entity.VerificationCode == verificationCode)
{
//
result = true;
break;
}
}
//
return result;
}
/// <summary>
/// Retrieve Verification Request
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <returns>an instance of <see>XVerificationRequest</see></returns>
private async Task<XVerificationRequest> GetVerificationRequest(
XDevice device
)
{
//
XVerificationRequest result = null;
var enumerator = DbContext.VerificationRequests
.AsAsyncEnumerable();
await
foreach (var entity in enumerator)
{
//
if (entity.Device.IsSameAs(device))
{
//
result = entity;
break;
}
}
//
return result;
}
/// <summary>
/// Retrieve Verification Request
/// </summary>
/// <param name="verificationCode">Confirm Verification Code</param>
/// <returns>an instance of <see>XVerificationRequest</see></returns>
private async Task<XVerificationRequest> GetVerificationRequest(
string verificationCode
)
{
//
XVerificationRequest result = null;
var enumerator = DbContext.VerificationRequests
.AsAsyncEnumerable();
await
foreach (var entity in enumerator)
{
//
if (entity.VerificationCode == verificationCode)
{
//
result = entity;
break;
}
}
//
return result;
}
/// <summary>
/// Remove Exists Verification Code Request
/// </summary>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="saveChanges">save changes on DbContext, default is true</param>
/// <returns></returns>
private async Task RemoveVerificationCodeRequestByDevice(
XDevice device,
bool saveChanges = true
)
{
//
if (!await IsDeviceRequestedForVerificationCodeBefor(device))
{
return;
}
//
var xvr = await GetVerificationRequest(device);
DbContext.VerificationRequests.Remove(xvr);
//
if (saveChanges)
{
await DbContext.SaveChangesAsync();
}
}
/// <summary>
/// Remove Exists Verification Code Request
/// </summary>
/// <param name="verificationCode">Confirm Verification Code</param>
/// <param name="saveChanges">save changes on DbContext, default is true</param>
/// <returns></returns>
private async Task RemoveVerificationRequest(
string verificationCode,
bool saveChanges = true
)
{
//
ValidationProvider.NotEmpty(verificationCode);
//
var request = await GetVerificationRequest(verificationCode);
DbContext.VerificationRequests.Remove(request);
//
if (saveChanges)
{
await DbContext.SaveChangesAsync();
}
}
/// <summary>
/// Add Verification Code Request
/// </summary>
/// <param name="request">an instance of <see>XVerificationRequest</see></param>
/// <param name="saveChanges">save changes on DbContext, default is true</param>
/// <returns>an instance of <see>XVerificationRequest</see></returns>
private async Task<XVerificationRequest> AddVerificationRequest(
XVerificationRequest request,
bool saveChanges = true
)
{
//
if (await IsDeviceRequestedForVerificationCodeBefor(request.Device))
{
await RemoveVerificationCodeRequestByDevice(request.Device);
}
//
var entry = await DbContext.VerificationRequests.AddAsync(request);
//
if (saveChanges)
{
await DbContext.SaveChangesAsync();
}
//
return entry.Entity;
}
#endregion
}
}
+95
View File
@@ -0,0 +1,95 @@
using System.Linq;
using Microsoft.Extensions.Logging;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Configurations;
using xIds.Constants;
using xIds.Interfaces;
namespace xIds.Providers
{
public class XIdentityMessageProvider : IXIdentityMessageProvider
{
//
public string InviteMsg { get; set; }
public string RegistrationApproveMsg { get; set; }
public string RegisteredMsg { get; set; }
public string VerificationCodeMsg { get; set; }
public string ChangePasswordMsg { get; set; }
public string PasswordChangedMsg { get; set; }
public string NewDeviceLoggedInMsg { get; set; }
private readonly ILogger<IXIdentityMessageProvider> logger;
private readonly XIdentityConfiguration identityConfiguration;
public XIdentityMessageProvider(
ILogger<IXIdentityMessageProvider> logger,
XIdentityConfiguration identityConfiguration
)
{
this.logger = logger;
this.identityConfiguration = identityConfiguration;
}
/// <summary>
/// Determine All things is Ready or Not
/// </summary>
/// <returns></returns>
public bool IsReady()
{
return !InviteMsg.IsNullOrEmpty() &&
!RegistrationApproveMsg.IsNullOrEmpty() &&
!RegisteredMsg.IsNullOrEmpty() &&
!VerificationCodeMsg.IsNullOrEmpty() &&
!ChangePasswordMsg.IsNullOrEmpty() &&
!PasswordChangedMsg.IsNullOrEmpty() &&
!NewDeviceLoggedInMsg.IsNullOrEmpty();
}
/// <summary>
/// Retrieve a Translated Value Based on Given Lang
/// </summary>
/// <param name="resourceTitle"></param>
/// <param name="lang"></param>
/// <returns></returns>
private string GetStringResource(string resourceTitle, string lang)
{
//
var item = identityConfiguration.IdentityMessages
.FirstOrDefault(sr =>
sr.Language.ToNormalString() == lang.ToNormalString() &&
sr.ResourceTitle.ToNormalString() == resourceTitle.ToNormalString());
//
if (item == null)
{
return null;
}
//
return item.TranslatedValue;
}
/// <summary>
/// Prepare Helper Class and Fill Messages
/// with Specific Language
/// </summary>
/// <param name="lang"></param>
public void PrepareMessages(string lang)
{
//
InviteMsg = GetStringResource(IdentityMessagesKeys.InviteMsg, lang);
RegisteredMsg = GetStringResource(IdentityMessagesKeys.RegisteredMsg, lang);
ChangePasswordMsg = GetStringResource(IdentityMessagesKeys.ChangePasswordMsg, lang);
PasswordChangedMsg = GetStringResource(IdentityMessagesKeys.PasswordChangedMsg, lang);
VerificationCodeMsg = GetStringResource(IdentityMessagesKeys.VerificationCodeMsg, lang);
NewDeviceLoggedInMsg = GetStringResource(IdentityMessagesKeys.NewDeviceLoggedInMsg, lang);
RegistrationApproveMsg = GetStringResource(IdentityMessagesKeys.RegistrationApproveMsg, lang);
//
if (!IsReady())
{
XException.InvalidArgs.Throw();
}
}
}
}
+51
View File
@@ -0,0 +1,51 @@
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Identity;
using xCommons.Extensions;
using xIdentityModels;
using xIds.Interfaces;
namespace xIds.Providers
{
public class XIdentityProfileService : IProfileService
{
private readonly IXIdentityManager identityManager;
private readonly IUserClaimsPrincipalFactory<XUser> userClaimsPrincipalFacotry;
public XIdentityProfileService(
IXIdentityManager identityManager,
IUserClaimsPrincipalFactory<XUser> userClaimsPrincipalFacotry
)
{
this.identityManager = identityManager;
this.userClaimsPrincipalFacotry = userClaimsPrincipalFacotry;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//
var sub = context.Subject.GetSubjectId();
var user = await identityManager.GetUserAsync(sub, true);
var principal = await userClaimsPrincipalFacotry.CreateAsync(user);
//
var claims = principal.Claims.ToList();
//
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
//
var sub = context.Subject.GetSubjectId();
var user = await identityManager.GetUserAsync(sub);
//
context.IsActive = !user.IsNull() && user.IsEnable && !user.IsBanned;
}
}
}
+168
View File
@@ -0,0 +1,168 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using xCommons.Extensions;
using xCommons.Providers;
using xIds.Configurations;
using xIds.Interfaces;
namespace xIds.Providers
{
public class XSecurityProvider : IXSecurityProvider
{
//
#region Props ...
private readonly string secretKey;
private readonly byte[] iv;
private readonly byte[] key;
private readonly XValidationProvider validationProvider;
#endregion
//
#region Constructor ...
public XSecurityProvider(
XIdentityResourceConfiguration identityConfiguration,
XValidationProvider validationProvider
)
{
//
this.secretKey = identityConfiguration.XRevisionSecretKey.ToMd5String();
this.validationProvider = validationProvider;
//
var ivBytes = new byte[16];
var keyBytes = new byte[32];
var secretBytes = Encoding.UTF8.GetBytes(this.secretKey);
//
Array.Copy(
secretBytes,
ivBytes,
secretBytes.Length < ivBytes.Length ?
secretBytes.Length :
ivBytes.Length
);
this.iv = ivBytes;
//
Array.Copy(
secretBytes,
keyBytes,
secretBytes.Length < keyBytes.Length ?
secretBytes.Length :
keyBytes.Length
);
this.key = keyBytes;
// Encoding.UTF8.GetBytes (this.secretKey);
}
#endregion
//
#region Actions ...
public string Encrypt(string plainText)
{
//
validationProvider.NotEmpty(plainText);
//
var textBytes = Encoding.UTF8.GetBytes(plainText);
var result = EncryptFromBytes(textBytes);
//
return result;
}
public string Decrypt(string cipherText)
{
//
validationProvider.NotEmpty(cipherText);
//
var cipherBytes = Convert.FromBase64String(cipherText);
var result = DecryptFromBytes(cipherBytes);
//
return result;
}
public string EncryptFromBytes(byte[] textBytes)
{
//
// Validate Args ...
validationProvider.NotNull(textBytes);
// Declare the string used to hold
// the decrypted text.
byte[] encryptedBytes = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
//Settings
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
// rijAlg.FeedbackSize = 128;
rijAlg.Key = key;
rijAlg.IV = iv;
//
using (var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV))
{
encryptedBytes = encryptor.TransformFinalBlock(textBytes, 0, textBytes.Length);
}
}
//
return Convert.ToBase64String(encryptedBytes);
}
public string DecryptFromBytes(byte[] cipherBytes)
{
//
// Validate Args ...
validationProvider.NotNull(cipherBytes);
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
//Settings
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
// rijAlg.FeedbackSize = 128;
rijAlg.Key = key;
rijAlg.IV = iv;
// Create a decrytor to perform the stream transform.
var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream(cipherBytes))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
//
return plaintext;
}
#endregion
}
}