diff --git a/Documents/Boards/xDashboard.kanban b/Documents/Boards/xDashboard.kanban index 39680d9..fc248e2 100644 --- a/Documents/Boards/xDashboard.kanban +++ b/Documents/Boards/xDashboard.kanban @@ -561,7 +561,7 @@ { "id": "2828eb54-a27d-40cd-ac17-cadc06d1a68e", "title": "Add Application Controller in Ids ...", - "checked": false + "checked": true }, { "id": "2e94cc30-d26c-4db6-bff9-8306de8e577c", diff --git a/Documents/GeneratedContext/xDashboard-xCommons-20261009_203547.txt b/Documents/GeneratedContext/xDashboard-xCommons-20261009_203547.txt new file mode 100644 index 0000000..d56bbc3 --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xCommons-20261009_203547.txt @@ -0,0 +1,6273 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Attributes\RequireXPoweredAttribute.cs + +using System.Linq; +using xCommons.Constants; +using xCommons.Extensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Authorization; + +namespace xCommons.Attributes { + public partial class RequireXPoweredAttribute : AuthorizeAttribute, IAuthorizationFilter { + + private readonly bool ignoreAnonymous; + + public RequireXPoweredAttribute (bool IgnoreAnonymous = true) { + ignoreAnonymous = IgnoreAnonymous; + } + + public void OnAuthorization (AuthorizationFilterContext context) { + // + var config = context.GetXAppConfiguration (); + + // + var isExistsXPowered = context.HttpContext.Request.Headers + .Any (h => h.Key + .ToNormalString () == XAuthorization.XPoweredBy.ToNormalString () && + h.Value == config.XPoweredValue); + + // + var isAnonymous = context.IsAnonymousAllowed (); + var handler = ignoreAnonymous ? isAnonymous ? false : true : true; + + // + if (handler && + !isExistsXPowered) { + context.Result = new UnauthorizedResult (); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Authorization\RequiredRolesHandler.cs + +using System.Linq; +using IdentityModel; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; + +namespace xCommons.Authorization { + public partial class RequiredRolesHandler : AuthorizationHandler { + protected override Task HandleRequirementAsync ( + AuthorizationHandlerContext context, + RequiredRolesRequirement requirement + ) { + // + var conatinsRole = context.User.HasClaim (c => c.Type == JwtClaimTypes.Role); + if (!conatinsRole) { + return Task.CompletedTask; + } + + // + var roleClaim = context.User.Claims.FirstOrDefault (c => c.Type == JwtClaimTypes.Role); + if (roleClaim == null || + !requirement.Roles.Contains (roleClaim.Value) + ) { + return Task.CompletedTask; + } + + // + context.Succeed (requirement); + return Task.CompletedTask; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Authorization\RequiredRolesRequirement.cs + +using Microsoft.AspNetCore.Authorization; + +namespace xCommons.Authorization { + public partial class RequiredRolesRequirement : IAuthorizationRequirement { + public string[] Roles { get; } + public RequiredRolesRequirement (params string[] Roles) { + this.Roles = Roles; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Configurations\XAppConfiguration.cs + +using System.Collections.Generic; + +namespace xCommons.Configurations { + /// + /// represent an xBackend Application + /// + public partial class XAppConfiguration { + public string Name { get; set; } + public string Version { get; set; } + public string XPoweredValue { get; set; } + public IEnumerable AllowedOrigins { get; set; } + public string WelcomeMessage { get; set; } + public string DefaultLanguage { get; set; } + public XMessageResourceTitles MessageResourceTitles { get; set; } + } + + public partial class XMessageResourceTitles { + public string Terms { get; set; } + public string Invite { get; set; } + public string RegistrationApprove { get; set; } + public string Registered { get; set; } + public string VerificationCode { get; set; } + public string ChangePassword { get; set; } + public string PasswordChanged { get; set; } + public string NewDeviceLoggedIn { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Configurations\XSwaggerConfiguration.cs + +using System.Collections.Generic; + +namespace xCommons.Configurations { + public partial class XSwaggerConfiguration { + public string Title { get; set; } = ""; + public string Version { get; set; } = "V1.0"; + public string Description { get; set; } = ""; + public string TermsOfServiceUrl { get; set; } = ""; + + public XSwaggerContact Contact { get; set; } = null; + public XSwaggerLicence License { get; set; } = null; + public Dictionary DefaultValues {get; set;} = null; + } + + public partial class XSwaggerContact { + public string Name { get; set; } + public string Email { get; set; } + public string Url { get; set; } + } + + public partial class XSwaggerLicence { + public string Name { get; set; } + public string Url { get; set; } + } + + public partial class XSwaggerDefaultValues + { + public Dictionary Values { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\CommonConstants.cs + +namespace xCommons.Constants { + public partial class CommonConstants { + public static char DEFAULT_LIST_SEPERATOR = ','; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\ConfigurationNodeNames.cs + +namespace xCommons.Constants { + public partial struct ConfigurationNodeNames { + public const string CERTIFICATES_NODE = "Certificates"; + public const string SWAGGER_NODE = "SwaggerConfiguration"; + public const string MESSAGE_RESOURCE_TITLES_NODE = "MessageResourceTitles"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\XAuthentication.cs + +namespace xCommons.Constants { + public partial struct XAuthentication { + public const string JWT = "jwt"; + public const string BEARER = "Bearer"; + public const string TOKEN = "token"; + public const string INTROSPECTION = "introspection"; + public const string IDENTITY_SERVER_LOCAL_API = "IdentityServerAccessToken"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\XAuthorization.cs + +namespace xCommons.Constants { + public partial struct XAuthorization { + public const string TokenIdentifier = "Bearer "; + public const string Header = "Authorization"; + public const string AccessToken = "access_token"; + public const string RefreshToken = "refresh_token"; + public const string ExpiresAt = "expires_at"; + public const string AccessTokenExpired = "AccessToken-Expired"; + public const string RevisionChecksum = "revision_checksum"; + public const string XPoweredBy = "X-PoweredBy"; + public const int ThresholdBeforeTokenExpiration = 600; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\XCustomClaims.cs + +namespace xCommons.Constants { + public partial struct XCustomClaims { + public const string IsEnabled = "is_enabled"; + public const string IsBanned = "is_banned"; + public const string Issuer = "iss"; + public const string ExpiredOn = "exp"; + public const string Audience = "aud"; + public const string ClientId = "client_id"; + public const string AuthenticatedOn = "auth_time"; + public const string UserId = "sub"; + public const string UserName = "unique_name"; + public const string FirstName = "given_name"; + public const string LastName = "family_name"; + public const string Gender = "gender"; + public const string Picture = "picture"; + public const string BrithDate = "birthdate"; + public const string Bio ="bio"; + public const string CoverImage = "cover_image"; + public const string OpenToSearch = "open_to_search"; + public const string Email = "email"; + public const string EmailVerified = "email_verified"; + public const string PhoneNumber = "phone_number"; + public const string PhoneNumberVerified = "phone_number_verified"; + public const string Role = "role"; + public const string Scope = "scope"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\XDeviceType.cs + +namespace xCommons.Constants { + /// + /// a collection of available Client Devices + /// + public enum XDeviceType { + Unknown, + Mobile, + Tablet, + Desktop + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\XFileType.cs + +namespace xCommons.Constants { + public enum XFileType { + Image, + ProfileImasge, + CoverImage, + Audio, + Video, + Document + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Constants\XPolicy.cs + +namespace xCommons.Constants { + public partial struct XPolicy { + public const string AllowedOrigins = "AllowedOrigins"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Controllers\XBaseApiController.cs + +using xCommons.Providers; +using xCommons.Attributes; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace xCommons.Controllers { + /// + /// Base Api Controller + /// + [ApiController] + [RequireXPowered(true)] + [Route("api/[controller]")] + [Produces("application/json")] + public abstract partial class XBaseApiController : XBaseController + { + protected XBaseApiController( + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + validationProvider + ) + { } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Controllers\XBaseApiV1Controller.cs + +using xCommons.Providers; +using xCommons.Attributes; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace xCommons.Controllers { + /// + /// Base Api V1 Controller + /// + [ApiController] + [ApiVersion("1.0")] + [RequireXPowered(true)] + [Route("api/v{version:apiVersion}/[controller]")] + public abstract partial class XBaseApiV1Controller : XBaseApiController + { + protected XBaseApiV1Controller( + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + validationProvider + ) + { } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Controllers\XBaseController.cs + +using System; +using xCommons.Providers; +using xCommons.Attributes; +using xCommons.Extensions; +using xExceptions.Constants; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; + +namespace xCommons.Controllers { + /// + /// Base Controller + /// + [Route ("[controller]")] + [Produces ("application/json")] + public abstract partial class XBaseController : ControllerBase { + public ILogger Logger { get; } + public XAppConfiguration AppConfiguration { get; } + public XValidationProvider ValidationProvider { get; } + + protected XBaseController ( + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider + ) { + Logger = logger; + AppConfiguration = appConfiguration; + ValidationProvider = validationProvider; + } + + /// + /// Check the Controller Up and Running + /// + /// string message + [HttpGet] + [Route ("Test/Hi")] + [AllowAnonymous] + public virtual IActionResult Hi () { + // + var controllerName = GetControllerName (); + var message = $"{controllerName} Controller is Up and running ..."; + + // + return Ok (message); + } + + /// + /// Check Controller ByPass XPowered Filter + /// + /// string message + [HttpGet] + [AllowAnonymous] + [RequireXPowered (false)] + [Route ("Test/PassRequireXPowered")] + public virtual IActionResult PassRequireXPowered () { + // + var controllerName = GetControllerName (); + var message = $"{controllerName} Controller bypass RequiredXPowered and Up and running ..."; + + // + return Ok (message); + } + + // + #region Non Actions ... + /// + /// Convert an Exception to Propper Error Result + /// + /// + /// + [NonAction] + public ActionResult GetExceptionActionResult (Exception ex) { + // + var exception = GetExceptionResult (ex); + var error = ex.Message.ToXError (); + + // + Logger.LogError ($"exception: {exception}, error: {error}"); + + // + try { + var xError = exception.Message.ToXError (); + var xException = (XException) xError.Id; + + // + switch (xException) { + // + case XException.NotFound: + return NotFound (error); + + // + case XException.NotAuthorized: + return Unauthorized (); + } + } catch { } + + // + return BadRequest (error); + } + + /// + /// Return Structured Exception + /// + /// + /// + [NonAction] + public Exception GetExceptionResult (Exception ex) { + // + // Log Exception Message ... + Logger.LogError (ex.Message); + + // + // if Happens Exception is Know Error + // catch it and return it in BadRequest Container ... + var isEr = ex.Message.IsXError (); + + // + if (isEr) { + return ex; + } + + // + // if catched Exception Contains Unknown Error + // return an Unknown Error ... + return XException.Unknown.ToException (); + } + + /// + /// Retrieve ControllerName + /// + /// + [NonAction] + public string GetControllerName () { + return this.ControllerContext.RouteData.Values["controller"].ToString (); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\DI\XDIHelperExtension.cs + +using xCommons.Providers; +using Microsoft.Extensions.DependencyInjection; + +public static partial class XDIHelperExtension { + /// + /// Register XCommons Module Services + /// + /// + public static void AddXCommons (this IServiceCollection services) { + services.AddSingleton (); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\BackgroundServiceExtensions.cs + +using xCommons.Providers; +using Microsoft.Extensions.DependencyInjection; + +namespace xCommons.Extensions +{ + public static class BackgroundServiceExtensions + { + /// + /// Register Background Services ... + /// + /// + /// + public static void AddXBackgroundService( + this IServiceCollection source + ) + where TService : XBackgroundService + { + // + source.AddSingleton(); + source.AddHostedService(); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\CertificateExtensions.cs + +using xCommons.Models; +using xCommons.Constants; +using Microsoft.Extensions.Configuration; + +namespace xCommons.Extensions { + public static partial class CertificateExtensions { + /// + /// Retrieve Specific Certificate From app Settings + /// + /// + /// + /// + public static XCertificate GetCertificate (this IConfiguration configuration, string name) { + // + var certificatesSection = configuration.GetSection (ConfigurationNodeNames.CERTIFICATES_NODE); + var certificateSection = certificatesSection.GetSection (name); + var certificate = certificateSection.Get (); + + // + return certificate; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\CommonExtensions.cs + +using System; +using System.IO; +using System.Web; +using System.Xml; +using AutoMapper; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using xCommons.Helpers; +using System.Threading; +using System.Reflection; +using xCommons.Constants; +using Newtonsoft.Json.Linq; +using System.Globalization; +using xExceptions.Constants; +using System.Threading.Tasks; +using xExceptions.Attributes; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using Newtonsoft.Json.Serialization; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; + +namespace xCommons.Extensions +{ + public static partial class CommonExtensions + { + /// + /// Conert XMLNode to String ... + /// + /// + /// + /// + public static string ToString(this XmlNode node, int indentation = 2) + { + // + using (var sw = new StringWriter()) + { + // + using (var xw = new XmlTextWriter(sw)) + { + xw.Formatting = System.Xml.Formatting.Indented; + xw.Indentation = indentation; + node.WriteContentTo(xw); + } + + // + return sw.ToString(); + } + } + + /// + /// Converts a Json String to Dictionary of String Mappings + /// + /// Json String + /// + public static Dictionary JSONToDictionary(this string source) + { + // + try + { + return JsonConvert.DeserializeObject>(source); + } + catch + { + return null; + } + } + + /// + /// Convert properties of specific Class instance to Dictionary ... + /// + /// class instance + /// + public static Dictionary ToDictionary(this T source) where T : class + { + // + var result = new Dictionary(); + + // + if (source.IsNull()) + { + return result; + } + + // + PropertyInfo[] propertyInfos = source.GetType().GetProperties(); + if (!propertyInfos.HasChild()) + { + return result; + } + + // + foreach (PropertyInfo propertyInfo in propertyInfos) + { + result.Add( + propertyInfo.Name, + propertyInfo + .GetValue(source, null) + .ToString() + ); + } + + // + return result; + } + + /// + /// run specific task and retrieve result ... + /// + /// + public static void RunTask(this Task source) + { + source. + GetAwaiter() + .GetResult(); + } + + /// + /// run specific task and retrieve result ... + /// + /// + public static T RunTask(this Task source) + { + return source. + GetAwaiter() + .GetResult(); + } + + /// + /// Convert XML String to Json Object ... + /// + /// + /// + public static string ToJSON(this string source) + { + // + var result = string.Empty; + try + { + // + result = XMLToJSONHelper.ToJSON(source); + + // + // Handle Removing Additional Values ... + result = result.Replace("s:", "").Replace("a:", ""); + } + catch { } + + // + return result; + } + + /// + /// Convert object to Json ... + /// + /// + /// + public static string ToJSON(this T value) + where T : class + { + // + if (value.IsNull()) + { + return string.Empty; + } + + // + var setting = new JsonSerializerSettings + { + Formatting = Newtonsoft.Json.Formatting.Indented, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + PreserveReferencesHandling = PreserveReferencesHandling.None + }; + + // + return JsonConvert.SerializeObject(value, Newtonsoft.Json.Formatting.None, setting); + } + + /// + /// Convert to Json With Support of Camel Casing + /// + /// + /// + /// + public static string ToJSON(this T value, bool camelCase = false) + { + // + if (value.IsNull()) + { + return string.Empty; + } + + // + var setting = new JsonSerializerSettings + { + Formatting = Newtonsoft.Json.Formatting.Indented, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + PreserveReferencesHandling = PreserveReferencesHandling.None, + }; + + // + if (camelCase) + { + setting.ContractResolver = new CamelCasePropertyNamesContractResolver(); + } + + // + return JsonConvert.SerializeObject(value, Newtonsoft.Json.Formatting.None, setting); + } + + /// + /// Map to Dynamic Object ... + /// + /// + /// + public static dynamic ToDynamicObject(this string source) + { + return JsonConvert.DeserializeObject(source); + } + + /// + /// Map to Dynamic Object ... + /// + /// + /// + public static dynamic ToDynamicObject(this object source) + { + // + var dMapConfig = new MapperConfiguration(b => { }); + var dMapper = dMapConfig.CreateMapper(); + + // + return dMapper.Map(source); + } + + /// + /// Parse From Dynamic Object ... + /// + /// + /// + /// + public static T FromDynamicObject(this object source) + where T : class + { + // + var dMapConfig = new MapperConfiguration(b => { }); + var dMapper = dMapConfig.CreateMapper(); + dynamic dSource = source; + + // + return dMapper.Map(dSource); + } + + /// + /// Convert Type of Object to Safe Type + /// + /// + /// + /// + public static T ConvertTo(this object source) + { + // + T result = default(T); + + // + try + { + // + T item = (T)Convert.ChangeType(source, typeof(T)); + + // + result = item; + } + catch { } + + // + return result; + } + + /// + /// Convert an Instance to another Instance + /// using AutoMapper dynamic Convertion + /// + /// + /// + /// + public static T MapConvert(this T source) + where T : class + { + // + if (source.IsNull()) + { + return null; + } + + // + var dSource = (object)source.ToDynamicObject(); + return dSource.FromDynamicObject(); + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + /// + public static T MapConvert( + this E source, + Action cfgAction = null + ) + where T : class + where E : class + { + // + if (source.IsNull()) + { + return null; + } + + // + var dMapConfig = new MapperConfiguration(b => + { + // + b.CreateMap() + .ReverseMap(); + + // + // Use Specified Provided Actions if Exists ... + if (!cfgAction.IsNull()) + { + cfgAction.Invoke(b); + } + }); + var dMapper = dMapConfig.CreateMapper(); + + // + var dSource = (object)dMapper.Map(source); + var result = dMapper.Map(dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + /// + public static IEnumerable MapConvert( + this IEnumerable source, + Action cfgAction = null + ) + { + // + if (source.IsNull()) + { + return null; + } + + // + var dMapConfig = new MapperConfiguration(b => + { + // + b.CreateMap() + .ReverseMap(); + + // + // Use Specified Provided Actions if Exists ... + if (!cfgAction.IsNull()) + { + cfgAction.Invoke(b); + } + }); + var dMapper = dMapConfig.CreateMapper(); + + // + var dSource = (object)dMapper.Map(source); + var result = dMapper.Map>(dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + /// + public static ICollection MapConvert( + this ICollection source, + Action cfgAction = null + ) + { + // + if (source.IsNull()) + { + return null; + } + + // + var dMapConfig = new MapperConfiguration(b => + { + // + b.CreateMap() + .ReverseMap(); + + // + // Use Specified Provided Actions if Exists ... + if (!cfgAction.IsNull()) + { + cfgAction.Invoke(b); + } + }); + var dMapper = dMapConfig.CreateMapper(); + + // + var dSource = (object)dMapper.Map(source); + var result = dMapper.Map>(dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + /// + public static IList MapConvert( + this IList source, + Action cfgAction = null + ) + { + // + if (source.IsNull()) + { + return null; + } + + // + var dMapConfig = new MapperConfiguration(b => + { + // + b.CreateMap() + .ReverseMap(); + + // + // Use Specified Provided Actions if Exists ... + if (!cfgAction.IsNull()) + { + cfgAction.Invoke(b); + } + }); + var dMapper = dMapConfig.CreateMapper(); + + // + var dSource = (object)dMapper.Map(source); + var result = dMapper.Map>(dSource); + + // + return result; + } + + /// + /// Convert Using Auto Mapper ... + /// + /// + /// + /// + /// + /// + /// + public static IDictionary MapConvert( + this IDictionary source, + Action cfgAction = null + ) + { + // + if (source.IsNull()) + { + return null; + } + + // + var dMapConfig = new MapperConfiguration(b => + { + // + b.CreateMap() + .ReverseMap(); + + // + // Use Specified Provided Actions if Exists ... + if (!cfgAction.IsNull()) + { + cfgAction.Invoke(b); + } + }); + var dMapper = dMapConfig.CreateMapper(); + + // + var dSource = (object)dMapper.Map(source); + var result = dMapper.Map>(dSource); + + // + return result; + } + + /// + /// Convert a Type to Static Type + /// + /// + /// + /// + public static T ToStatic(this object source) + { + // + var entity = Activator.CreateInstance(); + + // + var properties = source as IDictionary; + + // + if (properties == null) + { + return entity; + } + + // + foreach (var entry in properties) + { + // + var propertyInfo = entity.GetType().GetProperty(entry.Key); + if (propertyInfo != null) + { + propertyInfo.SetValue(entity, entry.Value, null); + } + } + + // + return entity; + } + + /// + /// Convert From Json String ... + /// + /// + /// + /// + public static T FromJSON(this string value) + { + return JsonConvert.DeserializeObject(value); + } + + /// + /// Convert From Json String ... + /// + /// + /// + /// + public static object FromJSON( + this string value, + Type type + ) + { + return JsonConvert + .DeserializeObject(value, type); + } + + /// + /// Search a Json String for Specific Node ... + /// + /// + /// + /// + public static List FindTokens(this JToken containerToken, string name) + { + List matches = new List(); + FindTokens(containerToken, name, matches); + return matches; + } + + /// + /// Convert to MD5 Bytes ... + /// + /// + /// + public static byte[] ToMd5Bytes(this string source) + { + return new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(source)); + } + + /// + /// Convert to MD5 String ... + /// + /// + /// + public static string ToMd5String(this string source) + { + // + // string representation (similar to UNIX format) + return BitConverter.ToString(source.ToMd5Bytes()) + // without dashes + .Replace("-", string.Empty) + // make lowercase + .ToLower(); + } + + /// + /// Check a String is MD5 String or not ... + /// + /// + /// + public static bool IsMD5String(this string source) + { + // + if (String.IsNullOrEmpty(source)) + { + return false; + } + + // + return Regex.IsMatch(source, "^[0-9a-fA-F]{32}$", RegexOptions.Compiled); + } + + /// + /// Convert an string to Byte array Representation ... + /// + /// + /// + public static byte[] ToBytes(this string source) + { + return Encoding.UTF8.GetBytes(source); + } + + /// + /// Converts Byte Array to String Value ... + /// + /// + /// + public static string FromBytes(this byte[] source) + { + // + var result = Encoding.UTF8.GetString(source, 0, source.Length); + return result; + } + + /// + /// Check object is Null ... + /// + /// + /// + public static bool IsNull(this object source) + { + return source == null; + } + + /// + /// Check collection Has Childs ... + /// + /// + /// + /// + public static bool HasChild(this IList source) + { + return !source.IsNull() && source.Count() > 0; + } + + /// + /// Check collection Has Childs ... + /// + /// + /// + /// + public static bool HasChild(this ICollection source) + { + return !source.IsNull() && source.Count() > 0; + } + + /// + /// Check collection Has Childs ... + /// + /// + /// + /// + public static bool HasChild(this IEnumerable source) + { + return !source.IsNull() && source.Count() > 0; + } + + /// + /// Select Async ... + /// + /// + /// + /// + /// + /// + public static async Task> SelectAsync( + this IEnumerable source, Func> method + ) + { + return await Task.WhenAll(source.Select(async s => await method(s))); + } + + /// + /// Converts an Enumerable to Async Enumerable ... + /// + /// + /// + /// + /// + public static async IAsyncEnumerable ToAsyncEnumerable( + this IEnumerable source, + [System.Runtime.CompilerServices.EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + foreach (var item in source) + { + // + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + yield return item; + } + } + + /// + /// Converts an Stream to Model AsyncEnumerator ... + /// + /// + /// + /// + /// + public static async IAsyncEnumerable ToModelAsyncEnumerable( + this Stream source, + [System.Runtime.CompilerServices.EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + // Validate Stream ... + if (source.IsNull()) + { + yield break; + } + + // + while (source.CanRead) + { + // + var model = await source + .ReadModelFromStream(cancellationToken); + + // + yield return model; + } + } + + /// + /// Converts an Stream to String AsyncEnumerator ... + /// using Read Line of Stream Reader ...S + /// + /// + /// + /// + public static async IAsyncEnumerable ToStringAsyncEnumerable( + this Stream source, + [System.Runtime.CompilerServices.EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + // Validate Stream ... + if (source.IsNull()) + { + yield break; + } + + // + while (source.CanRead) + { + // + var json = await source + .ReadJsonFromStream(cancellationToken); + + // + yield return json; + } + } + + /// + /// Write a Model to Stream ... + /// + /// an Stream to Write ... + /// a Model instance to Write ... + /// + /// + /// + public static async Task WriteModelToStream( + this Stream source, + T model, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + if (source.IsNull() || model.IsNull()) + { + return; + } + + // + // Extract Json of Model ... + var json = model.ToJSON(true); + + // + await source.WriteJsonToStream( + json: json, + cancellationToken: cancellationToken + ); + } + + /// + /// Write a Json String to Stream ... + /// + /// an Stream to Write ... + /// json string to write ... + /// + /// + public static async Task WriteJsonToStream( + this Stream source, + string json, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + if (source.IsNull() || json.IsNullOrEmpty()) + { + return; + } + + // + using (var streamWriter = new StreamWriter(stream: source)) + { + // + using (var jsonWriter = new JsonTextWriter(streamWriter)) + { + // + await jsonWriter.WriteRawAsync(json, cancellationToken); + await jsonWriter.FlushAsync(cancellationToken); + } + } + } + + /// + /// Read a Json Model From Stream ... + /// + /// + /// + /// + /// + public static async Task ReadModelFromStream( + this Stream source, + CancellationToken cancellationToken = default + ) + { + // + T result = default(T); + + // + var json = await source.ReadJsonFromStream(cancellationToken); + if (!json.IsNullOrEmpty()) + { + // + try + { + result = json.FromJSON(); + } + catch + { } + } + + // + return result; + } + + /// + /// Reading Json String From Stream ... + /// + /// an Stream to Write ... + /// + /// + public static async Task ReadJsonFromStream( + this Stream source, + CancellationToken cancellationToken = default + ) + { + // + var result = string.Empty; + + // + // Validate Args ... + if (source.IsNull()) + { + return result; + } + + // + using (var streamReader = new StreamReader(source)) + { + // + result = await Task.Run(() => + { + return streamReader.ReadLine(); + }, + cancellationToken); + } + + // + return result; + } + + /// + /// Update a Collection item ... + /// + /// + /// + /// + /// + /// + public static IList Update( + this IList source, + T sourceItem, + T updateWith + ) + { + // + var result = new List(); + + // + // Validate Args ... + if (!source.HasChild() || + updateWith.IsNull() || + sourceItem.IsNull() + ) + { + return result; + } + + // + // Extract Item from source ... + result = source + .Except(new List() { sourceItem }) + .ToList(); + result.Add(updateWith); + + // + return source; + } + + /// + /// Update a Collection item ... + /// + /// + /// + /// + /// + /// + public static ICollection Update( + this ICollection source, + T sourceItem, + T updateWith + ) + { + // + var result = new List(); + + // + // Validate Args ... + if (!source.HasChild() || + updateWith.IsNull() || + sourceItem.IsNull() + ) + { + return result; + } + + // + // Extract Item from source ... + result = source + .Except(new List() { sourceItem }) + .ToList(); + result.Add(updateWith); + + // + return source; + } + + /// + /// Update a Collection item ... + /// + /// + /// + /// + /// + /// + public static IEnumerable Update( + this IEnumerable source, + T sourceItem, + T updateWith + ) + { + // + var result = new List(); + + // + // Validate Args ... + if (!source.HasChild() || + updateWith.IsNull() || + sourceItem.IsNull() + ) + { + return result; + } + + // + // Extract Item from source ... + result = source + .Except(new List() { sourceItem }) + .ToList(); + result.Add(updateWith); + + // + return source; + } + + /// + /// Select and Retrieve Nth Childs of given List ... + /// + /// + /// + /// + /// + /// + public static IEnumerable GetNthItems(this IList source, int n, bool throwException = false) + { + // + var result = new List(); + if (n < 1 || n > source.Count || source.IsNull() || !source.HasChild()) + { + if (throwException) + { + throw XException.InvalidArgs.ToException(); + } + else + { + return result; + } + } + + // + var index = 0; + foreach (var item in source) + { + // + result.Add(item); + index++; + + // + if (index >= n) + { + break; + } + } + + // + return result; + } + + /// + /// Select and Retrieve Nth Childs of given List ... + /// + /// + /// + /// + /// + /// + public static IEnumerable GetNthItems(this ICollection source, int n, bool throwException = false) + { + return source.ToList().GetNthItems( + n: n, + throwException: throwException + ); + } + + /// + /// Select and Retrieve Nth Childs of given List ... + /// + /// + /// + /// + /// + /// + public static IEnumerable GetNthItems(this IEnumerable source, int n, bool throwException = false) + { + return source.ToList().GetNthItems( + n: n, + throwException: throwException + ); + } + + /// + /// Check an string is Null or Empty ... + /// + /// + /// + public static bool IsNullOrEmpty(this string sourse) + { + return string.IsNullOrEmpty(sourse); + } + + /// + /// Get string Value Attribute of an Enum ... + /// + /// + /// + public static string GetStringValue(this Enum source) + { + var type = source.GetType(); + var fieldInfo = type.GetField(source.ToString()); + var attributes = fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[]; + + return attributes != null && attributes.Length > 0 ? + attributes[0].StringValue : null; + } + + /// + /// Extracts digit's of an string ... + /// + /// + /// + public static string GetDigits(this string source) + { + try + { + // + Regex regexObj = new Regex(@"[^\d]"); + return regexObj.Replace(source, ""); + } + catch + { + return null; + } + } + + /// + /// Check an string is contains only digits ... + /// + /// + /// + public static bool IsDigits(this string source) + { + return (source.GetDigits() != null && + source.GetDigits().ToString() == source); + } + + /// + /// Convert to normal string, Trim and ToLower ... + /// + /// + /// + public static string ToNormalString(this string source) + { + // + if (source.IsNullOrEmpty()) + { + return string.Empty; + } + + // + return source.Trim().ToLowerInvariant(); + } + + /// + /// Capitalize an string ... + /// + /// + /// + public static string Capitalize(this string source) + { + // + if (source.IsNullOrEmpty()) + { + return string.Empty; + } + + // + var result = ""; + if (source.Length > 1) + { + // + var firstCharr = source.Substring(0, 1); + var otherParts = source.Substring(1, source.Length - 1); + + // + result = firstCharr.ToUpperInvariant() + otherParts; + } + else + { + result = source.ToUpperInvariant(); + } + + // + return result; + } + + /// + /// Summarize a Text ... + /// + /// + /// + /// + /// + public static string SummarizeContent( + this string source, + int maxAllowedLength = 0, + string endsWith = "" + ) + { + // + var result = string.Empty; + if (source.IsNullOrEmpty() || maxAllowedLength <= 0) + { + // + result = source; + return result; + } + + // + // Pattern of Links ... + var pattern = @"(?:[!]\[(.*?)\]\(.*?\))"; + + // + // Apply Summarization ... + + // + // Replace Markdown Links using RegExp ... + result = Regex.Replace(source, pattern, ""); + + // + // Do Summarization if Length bigger than MaxLength ... + if (result.Length > maxAllowedLength) + { + result = result.Substring(0, maxAllowedLength); + } + + // + // Apply EndsWith if Provided ... + if (!endsWith.IsNullOrEmpty() && + !result.EndsWith(endsWith)) + { + result += endsWith; + } + + // + return result; + } + + /// + /// Check an string is a Valid Representation of PhoneNumber ... + /// + /// + /// + public static bool IsValidMobileNumber(this string source) + { + // + // Validating Mobile Numbers ... + var phoneNumberUtil = PhoneNumbers.PhoneNumberUtil.GetInstance(); + try + { + // + var mPhoneNumber = phoneNumberUtil.Parse(source, null); + if (phoneNumberUtil.IsValidNumber(mPhoneNumber)) + { + // + return true; + } + + // + return false; + } + catch + { + + // + return false; + } + } + + /// + /// Check an string is Valid Representation of Email Address ... + /// + /// + /// + public static bool IsValidEmail(this string source) + { + try + { + var addr = new System.Net.Mail.MailAddress(source); + return addr.Address == source; + } + catch + { + return false; + } + } + + /// + /// Check an string is Valid Representation of URL Address ... + /// + /// + /// + public static bool IsValidUrl(this string url) + { + return Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute); + } + + /// + /// check a URL has / at the end, if not add one ... + /// + /// + /// + public static string NormalizeUrl(this string url) + { + return url.EndsWith("/") ? url : url + "/"; + } + + /// + /// Check an string is GUID or not ... + /// + /// + /// + public static bool IsGuid(this string source) + { + // + // Validate Args ... + if (source.IsNullOrEmpty()) + { + return false; + } + + // + Guid guid; + var result = Guid.TryParse(source, out guid); + + // + return result; + } + + /// + /// Check Specific Guid string is Default Guid or not ... + /// + /// + /// + public static bool IsDefaultGuid(this string guid) + { + // + if (!guid.IsGuid()) + { + return false; + } + + // + var empty = Guid.Empty.ToString(); + return guid == empty; + } + + /// + /// Check Specific Guid is Default Guid or not ... + /// + /// + /// + public static bool IsDefaultGuid(this Guid guid) + { + // + var empty = Guid.Empty; + return guid == empty; + } + + /// + /// Parse an String to related Enumerable Items ... + /// + /// + /// + /// + /// + public static IEnumerable ParseListString( + this string source, + char? separator = null + ) + { + // + if (!separator.HasValue) + { + separator = CommonConstants.DEFAULT_LIST_SEPERATOR; + } + + // + var list = source.Split(separator.Value); + var result = new Collection(); + + // + foreach (var s in list) + { + // + try + { + // + T item = (T)Convert.ChangeType(s, typeof(T)); + + // + result.Add(item); + } + catch { } + } + + // + return result; + } + + /// + /// Converts a List String Representation to GUid List ... + /// + /// + /// + /// + public static IEnumerable ParseListGuid( + this string list, + char? separator = null + ) + { + // + var result = new List(); + + // + if (!separator.HasValue) + { + separator = CommonConstants.DEFAULT_LIST_SEPERATOR; + } + + // + if (list.IsNullOrEmpty()) + { + return result; + } + + // + var items = list.Split(separator.Value); + items.ToList().ForEach(i => + { + // + if (!i.IsNullOrEmpty() && i.IsGuid() && !i.IsDefaultGuid()) + { + // + var gID = new Guid(i); + result.Add(gID); + } + }); + + // + return result; + } + + /// + /// Convert a List to List String ... + /// + /// + /// + /// + /// + public static string ToListString( + this IEnumerable source, + char? separator = null + ) + { + // + if (!separator.HasValue) + { + separator = CommonConstants.DEFAULT_LIST_SEPERATOR; + } + + // + if (!source.HasChild()) + { + return string.Empty; + } + + // + string result = String.Join( + separator + .ToString(), + source + ); + + // + return result; + } + + /// + /// get default value of a Type + /// + /// + /// + /// + public static T Default(this T source) + where T : class + { + return default(T); + } + + /// + /// Check if an object is Null or Default Value + /// + /// + /// + /// + public static bool IsNullOrDefault(this T source) + where T : class + { + return Equals(source, default(T)); + } + + /// + /// convert a timestamp to ISO String representation ... + /// + /// + /// + public static string ISODateString(this long timestamp) + { + // + var time = timestamp.FromTimestamp(); + var result = time.ToString("o"); + + // + return result; + } + + /// + /// Converts a DateTime to Unix Like Long ... + /// + /// + /// + public static long ToTimestamp( + this DateTime source + ) + { + return new DateTimeOffset(source) + .ToUnixTimeMilliseconds(); + } + + /// + /// convert timestamp to DateTime ... + /// + /// + /// + public static DateTime FromTimestamp(this long timestamp) + { + // + var result = DateTimeOffset.FromUnixTimeMilliseconds(timestamp); + return result.DateTime; + } + public static DateTime FromTimestamp(this string source) + { + // + var timestamp = Int64.Parse(source); + var time = timestamp.FromTimestamp(); + return time; + } + + /// + /// Check a Date Time is Expired or not + /// + /// + /// + public static bool IsExPired(this DateTime source) + { + // + if (source.IsNull()) + { + return true; + } + + // + var currentDate = DateTime.UtcNow; + var isExpired = currentDate >= source; + + // + return isExpired; + } + public static bool IsExpired(this long source) + { + var time = source.FromTimestamp(); + return time.IsExPired(); + } + public static bool IsExpired(this string source) + { + // + var time = DateTime.Parse(source); + return time.IsExPired(); + } + + /// + /// Determines a File Type is Image Kind or not + /// + /// + /// + public static bool IsImageKind(this XFileType source) + { + // + var imageKind = new Collection() { + XFileType.Image, + XFileType.CoverImage, + XFileType.ProfileImasge + }; + + // + return imageKind.Contains(source); + } + + /// + /// Encode String as Url + /// + /// + /// + public static string ToUrlEncoded(this string source) + { + return HttpUtility.UrlEncode(source); + } + + /// + /// Decode Url String + /// + /// + /// + public static string ToUrlDecoded(this string source) + { + return HttpUtility.UrlDecode(source); + } + + /// + /// Convert an Object to string for use in HttpParams + /// + /// + /// + /// + public static string ToHttpParamString(this object value, CultureInfo cultureInfo) + { + // + if (value is Enum) + { + // + string name = Enum.GetName(value.GetType(), value); + if (name != null) + { + // + var field = IntrospectionExtensions + .GetTypeInfo(value.GetType()) + .GetDeclaredField(name); + + // + if (field != null) + { + // + var attribute = CustomAttributeExtensions + .GetCustomAttribute(field, typeof(EnumMemberAttribute)) + as EnumMemberAttribute; + + // + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + } + } + else if (value is bool) + { + return System.Convert.ToString(value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[])value); + } + else if (value != null && value.GetType().IsArray) + { + // + var array = Enumerable.OfType((Array)value); + return string.Join(",", Enumerable + .Select( + array, + o => o.ToHttpParamString(cultureInfo))); + } + + // + return System.Convert.ToString(value, cultureInfo); + } + + /// + /// ReShape an Object as HttpParam String + /// + /// + /// + public static string AsHttpParamString(this object value) + { + return System.Uri + .EscapeDataString(value + .ToHttpParamString(CultureInfo.InvariantCulture)); + } + + /// + /// Calculating Date Difference Provider String + /// and Generate Date based on UTC Time in Milliseconds like unix Date ... + /// + /// + /// + public static long GenerateDateTimeOffset(this string offsetProvider) + { + // + if (offsetProvider.IsNullOrEmpty()) + { + return 0; + } + + // + var mExpireValue = 0; + var mExpireValueStr = offsetProvider.GetDigits(); + var mExpireUnit = offsetProvider.Replace(mExpireValueStr, ""); + if (mExpireValueStr.IsNullOrEmpty() || mExpireUnit.IsNullOrEmpty()) + { + return 0; + } + + // + var isParsed = int.TryParse(mExpireValueStr, out mExpireValue); + if (!isParsed) + { + return 0; + } + + // + DateTime? date = null; + switch (mExpireUnit) + { + case "d": + date = DateTime.UtcNow.AddDays(mExpireValue); + break; + + case "h": + date = DateTime.UtcNow.AddHours(mExpireValue); + break; + + case "m": + date = DateTime.UtcNow.AddMinutes(mExpireValue); + break; + + case "s": + date = DateTime.UtcNow.AddSeconds(mExpireValue); + break; + + case "ms": + default: + date = DateTime.UtcNow.AddMilliseconds(mExpireValue); + break; + } + + // + var result = !date.HasValue ? 0 : new DateTimeOffset(date.Value) + .ToUnixTimeMilliseconds(); + + // + return result; + } + + // + #region Int Id Extensions ... + public static bool IsValidIntId(this int source) + { + return source > 0; + } + + public static void ValidateIntId(this int source) + { + // + if (!source.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + } + #endregion + + // + #region StringExtensions for Identity Server ... + public static string EnsureTrailingSlash(this string input) + { + if (!input.EndsWith("/")) + { + return input + "/"; + } + + return input; + } + + public static bool IsMissing(this string value) + { + return string.IsNullOrWhiteSpace(value); + } + + public static bool IsPresent(this string value) + { + return !string.IsNullOrWhiteSpace(value); + } + + public static string Sha256(this string input) + { + if (input.IsMissing()) return string.Empty; + + using (var sha = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha.ComputeHash(bytes); + + return Convert.ToBase64String(hash); + } + } + #endregion + + // + #region Private ... + private static void FindTokens(JToken containerToken, string name, List matches) + { + if (containerToken.Type == JTokenType.Object) + { + foreach (JProperty child in containerToken.Children()) + { + if (child.Name == name) + { + matches.Add(child.Value); + } + FindTokens(child.Value, name, matches); + } + } + else if (containerToken.Type == JTokenType.Array) + { + foreach (JToken child in containerToken.Children()) + { + FindTokens(child, name, matches); + } + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\DIExtensions.cs + +using System; +using System.IO; +using System.Linq; +using xCommons.Filters; +using xCommons.Constants; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi.Models; +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Swashbuckle.AspNetCore.SwaggerUI; +using Swashbuckle.AspNetCore.SwaggerGen; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace xCommons.Extensions +{ + public static partial class DIExtensions + { + + /// + /// Inject Specific Registered Service from IServiceCollection + /// + /// + /// + /// + public static T GetRegisteredService(this IServiceCollection source) + { + // + var serviceProvider = source.BuildServiceProvider(); + return serviceProvider.GetService(); + } + + /// + /// Register App Configuration as a Service + /// + /// + /// + public static void AddXAppConfiguration(this IServiceCollection services, IConfiguration configuration) + { + // + var appConfiguration = configuration.GetXAppConfiguration(); + services.AddSingleton(appConfiguration); + } + + /// + /// Search Assemblies, Extract XML Comments and + /// Integrate them inside swagger gen documents ... + /// + /// + public static void IncludeAssembliesXMLComments(this SwaggerGenOptions source) + { + // + // Extract all Exists .xml files ... + var basePath = AppContext.BaseDirectory; + var xmlFiles = Directory.GetFiles(basePath, "*.xml", SearchOption.TopDirectoryOnly); + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + // + // Loop through Detected XML Files ... + foreach (var xmlFile in xmlFiles) + { + // + try + { + // + // Retrieve file name for Assembly Checking ... + var assemblyName = Path.GetFileNameWithoutExtension(xmlFile); + + // + // Try to retrieve Assembly ... + var assembly = assemblies + .FirstOrDefault(a => a.GetName().Name == assemblyName); + if (!assembly.IsNullOrDefault() || + File.Exists(Path.Combine(basePath, $"{assemblyName}.dll")) + ) + { + // + // Includes Selected XML Comments ... + source.IncludeXmlComments(xmlFile); + } + } + catch (Exception ex) + { + Console.WriteLine($"Warning: Could not include XML comments from {xmlFile}. Error: {ex.Message}"); + } + } + } + + /// + /// Add Required OperationFilters and Authorization Fields to Swagger Options + /// + /// + /// + /// + /// + /// + public static void AddXSwaggerGenOptions( + this SwaggerGenOptions source, + string xmlFilePath = null, + bool addRequiredXPoweredFilter = true, + bool addXTokenAuthorization = true, + bool addDefaultValueFilter = true + ) + { + // + if (source.IsNull()) + { + source = new SwaggerGenOptions(); + } + + // + // Add Xml File Path ... + if (!xmlFilePath.IsNullOrEmpty()) + { + source.IncludeXmlComments(xmlFilePath); + } + + // + // Include all XML Documents ... + source.IncludeAssembliesXMLComments(); + + // + // Handle RequireXPowered Filter ... + if (addRequiredXPoweredFilter) + { + source.OperationFilter(); + } + + // + // Add Default Values Filter ... + if (addDefaultValueFilter) + { + source.OperationFilter(); + } + + // + // Handle XToken Authorizations ... + if (addXTokenAuthorization) + { + // + #region Bearer AccessToken ... + // + source.AddSecurityDefinition(nameof(XAuthorization.AccessToken), new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Bearer token", + Type = SecuritySchemeType.ApiKey, + Name = XAuthorization.Header + }); + + // + source.AddSecurityRequirement(new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Id = nameof (XAuthorization.AccessToken), + Type = ReferenceType.SecurityScheme + } + }, Array.Empty () + } + }); + #endregion + + // + #region RefreshToken ... + // + source.AddSecurityDefinition(nameof(XAuthorization.RefreshToken), new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "refresh token", + Type = SecuritySchemeType.ApiKey, + Name = nameof(XAuthorization.RefreshToken) + }); + + // + source.AddSecurityRequirement(new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Id = nameof (XAuthorization.RefreshToken), + Type = ReferenceType.SecurityScheme + } + }, Array.Empty () + } + }); + #endregion + + // + #region ExpiresAt ... + // + source.AddSecurityDefinition(nameof(XAuthorization.ExpiresAt), new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "expires at", + Type = SecuritySchemeType.ApiKey, + Name = nameof(XAuthorization.ExpiresAt) + }); + + // + source.AddSecurityRequirement(new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Id = nameof (XAuthorization.ExpiresAt), + Type = ReferenceType.SecurityScheme + } + }, Array.Empty () + } + }); + #endregion + } + } + + /// + /// Register Swagger + /// + /// + /// + /// + /// + /// + /// + /// + public static void AddXSwagger( + this IServiceCollection source, + IConfiguration configuration, + SwaggerGenOptions settings = null, + string xmlFilePath = null, + bool addRequiredXPoweredFilter = true, + bool addXTokenAuthorization = true, + bool addDefaultValueFilter = true + ) + { + // + var xSwaggerConfig = configuration.GetXSwaggerConfiguration(); + if (xSwaggerConfig.IsNull()) + { + xSwaggerConfig = new XSwaggerConfiguration(); + } + + // + source.AddSingleton(xSwaggerConfig); + + // + #region Prepare SwaggerGenOptions ... + if (settings.IsNull()) + { + settings = new SwaggerGenOptions(); + } + settings.AddXSwaggerGenOptions( + xmlFilePath: xmlFilePath, + addDefaultValueFilter: addDefaultValueFilter, + addXTokenAuthorization: addXTokenAuthorization, + addRequiredXPoweredFilter: addRequiredXPoweredFilter + ); + #endregion + + // + #region Prepare Swagger Doc ... + // + // Api Document Section ... + var apiDoc = new OpenApiInfo + { + Title = xSwaggerConfig.Title, + Version = xSwaggerConfig.Version, + Description = xSwaggerConfig.Description, + }; + + // + // Api Document TermsOfUse URL ... + if (!xSwaggerConfig.TermsOfServiceUrl.IsNullOrEmpty()) + { + apiDoc.TermsOfService = new Uri(xSwaggerConfig.TermsOfServiceUrl); + } + + // + // Contact Section ... + if (!xSwaggerConfig.Contact.IsNull()) + { + // + var apiContact = new OpenApiContact + { + Name = xSwaggerConfig.Contact.Name, + Email = xSwaggerConfig.Contact.Email + }; + + // + // Contact URL ... + if (!xSwaggerConfig.Contact.Url.IsNullOrEmpty()) + { + apiContact.Url = new Uri(xSwaggerConfig.Contact.Url); + } + + // + apiDoc.Contact = apiContact; + } + + // + // License Section ... + if (!xSwaggerConfig.License.IsNull()) + { + // + var apiLicense = new OpenApiLicense + { + Name = xSwaggerConfig.License.Name, + }; + + // + // License URL ... + if (!xSwaggerConfig.License.Url.IsNullOrEmpty()) + { + apiLicense.Url = new Uri(xSwaggerConfig.License.Url); + } + + // + apiDoc.License = apiLicense; + } + #endregion + + // + // Register Swagger ... + source.AddSwaggerGen( + opt => + { + // + opt.UpdateData(settings); + + // + opt.SwaggerDoc("v1", apiDoc); + } + ); + + // + // Register Swagger Operation Filters ... + } + + /// + /// Use Sagger + /// + /// + /// + public static void UseXSwagger( + this IApplicationBuilder source, + SwaggerUIOptions options = null + ) + { + // + source.UseSwagger(); + + // + if (options.IsNull()) + { + source.UseSwaggerUI(); + } + else + { + source.UseSwaggerUI(options: options); + } + } + + /// + /// Add Cross Origin Resource Sharings + /// + /// + /// + public static void AddXCors( + this IServiceCollection source, + IEnumerable allowedOrigins + ) + { + // + if (!allowedOrigins.HasChild()) + { + // + Console.WriteLine($"XCommons: there is no provided allowedOrigins, start app without any specific cors, this may be unsecure ..."); + + // + source.AddCors(options => + { + options.AddPolicy(XPolicy.AllowedOrigins, + builder => + { + builder + .AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod() + .WithExposedHeaders("*"); + }); + }); + + // + return; + } + + // + source.AddCors(options => + { + options.AddPolicy(XPolicy.AllowedOrigins, + builder => + { + builder + .WithOrigins(allowedOrigins.ToArray()) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials() + .WithExposedHeaders("*"); + }); + }); + } + + /// + /// Use X Registered Cross Origin Resource Sharings + /// + /// + public static void UseXCors(this IApplicationBuilder sources) + { + // + sources.UseCors(XPolicy.AllowedOrigins); + } + + /// + /// Register Api V1 Versioning ... + /// + /// + public static void AddXApiV1Versioning(this IServiceCollection services) + { + // + // Register Api Versioning ... + services.AddApiVersioning(opt => + { + // + // Set Default Api Version ... + opt.DefaultApiVersion = new ApiVersion(1, 0); + + // + // Set Routing to Default API Version, if Version unspecified ... + opt.AssumeDefaultVersionWhenUnspecified = true; + + // + // Report All Available Api Versions on Response ... + opt.ReportApiVersions = true; + }); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\EnumExtensions.cs + +using System; +using System.Collections.Generic; + +namespace xCommons.Extensions { + public static partial class EnumExtensions { + public static string GetKey (this Enum source) { + return Enum.GetName (source.GetType (), source); + } + + public static T GetValue (this string source) { + return (T) Enum.Parse (typeof (T), source); + } + + public static void Iterate (this IEnumerator source, Action action) { + // + while (source.MoveNext ()) { + // + var current = source.Current; + action (current); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\ExceptionExtensions.cs + +using System; +using xExceptions.Models; +using xExceptions.Constants; + +namespace xCommons.Extensions { + /// + /// this is an extension pack for Exceptions + /// + public static partial class ExceptionExtensions { + + /// + /// determines an string contains an XError content or not + /// + /// + /// + public static bool IsXError (this string source) { + // + if (source.IsNullOrEmpty ()) { + return false; + } + + // + var xError = source.ToXError (); + if (xError != null) { + return true; + } + + // + // If Convert Proccess fails ... + var normalString = source.ToNormalString (); + + // + // Return result based on string values ... + return normalString.Contains ("id") && normalString.Contains ("message"); + } + + /// + /// deserialize an string to to XError class instance + /// + /// + /// + public static XError ToXError (this string source) { + // + if (source.IsNullOrEmpty ()) { + return null; + } + + // + try { + // + var err = source.FromJSON (); + + // + // if err is null we have to manually get value + if (err == null) { + // + var normalString = source.ToNormalString (); + var parts = normalString.Split (','); + if (parts.Length > 2) { + return null; + } + + // + var idContainer = parts[0]; + var messageContainer = parts[1]; + } + + // + return err; + } catch { + return null; + } + } + + public static XError ToXError (this XException source) { + // + var errorId = (int) source; + var errorMessage = source.GetStringValue (); + + // + return new XError { + Id = errorId, + Message = errorMessage + }; + } + + /// + /// Convert an XError instance to an Exception + /// + /// + /// + public static Exception ToException (this XError source) { + // + var jsonStr = source.ToJSON (); + return new Exception (jsonStr); + } + + public static Exception ToException (this XException source) { + return source + .ToXError () + .ToException (); + } + + /// + /// Convert an Exception to corresponding XError Object + /// + /// + /// + public static XError FromException (this Exception source) { + // + if (source == null || source.Message.IsNullOrEmpty ()) { + return null; + } + + // + return source.Message.ToXError (); + } + + /// + /// Add Content to Contentable XException member and return XError Object + /// + /// + /// + /// + public static XError AddContentToError (this XException exception, string value) { + // + var xError = exception.ToXError (); + xError.Message = string.Format (xError.Message, value); + + // + return xError; + } + + /// + /// Add Content to Contentable XException member and return Exception Object + /// + /// + /// + /// + public static Exception AddContentToException (this XException exception, string value) { + // + var xError = exception.ToXError (); + xError.Message = string.Format (xError.Message, value); + + // + return xError.ToException (); + } + + /// + /// Throw Specific Exception ... + /// + /// + /// + public static void Throw ( + this XException source, + string content = null + ) { + // + Exception exception = null; + if (content.IsNullOrEmpty ()) { + exception = source.ToException (); + } else { + exception = source.AddContentToException (content); + } + + // + throw exception; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\FilterExtensions.cs + +using System.Linq; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Authorization; + +namespace xCommons.Extensions { + public static partial class FilterExtensions { + public static XAppConfiguration GetXAppConfiguration (this ActionExecutingContext source) { + // + var services = source.HttpContext.RequestServices; + return (XAppConfiguration) services.GetService (typeof (XAppConfiguration)); + } + + public static XAppConfiguration GetXAppConfiguration (this AuthorizationFilterContext source) { + // + var services = source.HttpContext.RequestServices; + return (XAppConfiguration) services.GetService (typeof (XAppConfiguration)); + } + + public static bool IsAnonymousAllowed (this AuthorizationFilterContext source) { + // + var result = source.Filters + .Any (f => f.GetType () == typeof (AllowAnonymousFilter)) || + source.ActionDescriptor.FilterDescriptors + .Any (f => f.GetType () == typeof (AllowAnonymousFilter)) || + source.ActionDescriptor.EndpointMetadata + .Any (f => f.GetType () == typeof (AllowAnonymousAttribute)); + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\IConfigurationExtensions.cs + +using System.Linq; +using xCommons.Constants; +using xCommons.Configurations; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; + +namespace xCommons.Extensions +{ + public static partial class IConfigurationExtensions + { + /// + /// Retrieve AppConfiguration from Configurations + /// + /// + /// + public static XAppConfiguration GetXAppConfiguration(this IConfiguration source) + { + // + var xMessageResourceTitlesConfigSection = source + .GetSection(ConfigurationNodeNames.MESSAGE_RESOURCE_TITLES_NODE); + + // + var result = new XAppConfiguration + { + Name = source[nameof(XAppConfiguration.Name)], + Version = source[nameof(XAppConfiguration.Version)], + XPoweredValue = source[nameof(XAppConfiguration.XPoweredValue)], + WelcomeMessage = source[nameof(XAppConfiguration.WelcomeMessage)], + AllowedOrigins = source + .GetSection(nameof(XAppConfiguration.AllowedOrigins)) + .Get>(), + DefaultLanguage = source[nameof(XAppConfiguration.DefaultLanguage)], + MessageResourceTitles = xMessageResourceTitlesConfigSection + .Get() + }; + + // + return result; + } + + /// + /// Retrieve Swagger Configurations + /// + /// + /// + public static XSwaggerConfiguration GetXSwaggerConfiguration(this IConfiguration source) + { + // + var xSwaggerConfigSection = source + .GetSection(ConfigurationNodeNames.SWAGGER_NODE); + return xSwaggerConfigSection.Get(); + } + + /// + /// Converts a ConfigurationSection to object Representation ... + /// + /// + /// + public static object ToObject(this IConfigurationSection section) + { + // + // an Inner Function for Create Recursive Parsing ... + object ConvertSection(IConfigurationSection sec) + { + // + var children = sec + .GetChildren() + .ToList(); + if (!children.Any()) + { + return sec.Value ?? string.Empty; + } + + // + // Detect Array Like Sections ... + bool isArray = children.All(ch => int.TryParse(ch.Key, out _)); + if (isArray) + { + // + return children + .OrderBy(c => int.Parse(c.Key)) + .Select(ConvertSection) + .ToList(); + } + + // + // Otherwise ... + return children + .ToDictionary( + c => c.Key, + c => ConvertSection(c) + ); + } + + // + // Try to Parse using Inner Function ... + var result = ConvertSection(section); + + // + return result; + } + + /// + /// Converts a ConfigurationSection to Json Representation ... + /// + /// + /// + public static string ToJSON(this IConfigurationSection section) + { + // + // Try to Parse using Inner Function ... + var obj = section.ToObject(); + + // + // Convert it to Json ... + var result = obj.ToJSON(); + + // + return result; + } + + /// + /// Get a Configuration Section as Object ... + /// + /// + /// + /// + public static object GetSectionAsObject( + this IConfiguration source, + string section + ) + { + // + object result = null; + + // + if (!section.IsNullOrEmpty()) + { + // + var sec = source.GetSection(section); + if (!sec.IsNull()) + { + result = sec.ToObject(); + } + } + + // + return result; + } + + /// + /// Retrieve a Configuration Section as Json String ... + /// + /// + /// + /// + public static string GetSectionAsJson( + this IConfiguration source, + string section + ) + { + // + var result = string.Empty; + + // + // Retrieve Secion as Object ... + var obj = source.GetSectionAsObject(section); + if (!obj.IsNull()) + { + // + // Converts Section Object to Json ... + result = obj.ToJSON(); + } + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\IFormFileExtensions.cs + +using System; +using System.IO; +using System.Text; +using UglyToad.PdfPig; +using System.Threading; +using xCommons.Extensions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace xAiModels.Extensions +{ + public static class IFormFileExtensions + { + /// + /// Read a IFormFile Content as byte array ... + /// + /// + /// + /// + public static async Task ReadAsBytes( + this IFormFile source, + CancellationToken cancellationToken = default + ) + { + // + // Create and use a Memory Stream ... + using var stream = new MemoryStream(); + + // + // Copy File Content to Stream ... + await source.CopyToAsync( + target: stream, + cancellationToken: cancellationToken + ); + + // + // Read Stream Content as byte array ... + var result = stream.ToArray(); + + // + return result; + } + + /// + /// Read a File Content by it's Path as byte array ... + /// + /// + /// + /// + /// + /// + public static async Task ReadAsBytes( + this string filePath, + int bufferSize = 81920, + CancellationToken cancellationToken = default + ) + { + // + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new Exception("Invalid Args ..."); + } + + // + // Create and use a Memory Stream ... + using var memStream = new MemoryStream(); + + // + using var stream = new FileStream( + path: filePath, + mode: FileMode.Open, + access: FileAccess.Read, + share: FileShare.ReadWrite + ); + + // + // Copy File Content to Stream ... + await stream.CopyToAsync( + destination: memStream, + bufferSize: bufferSize, + cancellationToken: cancellationToken + ); + + // + // Read Stream Content as byte array ... + var result = memStream.ToArray(); + + // + return result; + } + + /// + /// Read Text Content of Document ... + /// + /// + /// + /// + public static async Task ReadContent( + this IFormFile source, + CancellationToken cancellationToken = default + ) + { + // + // Check if File PDF or not ... + var isPDF = source.ContentType + .ToNormalString() + .Contains("pdf" + .ToNormalString()); + + // + var result = string.Empty; + + // + var isValidFileType = isPDF; + if (!isValidFileType) + { + return result; + } + + // + if (isPDF) + { + // + // Reading File Content as Byte array ... + var fileBytes = await source.ReadAsBytes(cancellationToken); + + // + // Create a PDF Document ... + using var stream = new MemoryStream(fileBytes); + using var document = PdfDocument.Open(stream); + + // + // Extract PDF Text ... + var textBuilder = new StringBuilder(); + foreach (var page in document.GetPages()) + { + textBuilder.Append(page.Text); + } + + // + result = textBuilder.ToString(); + } + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\ModelExtensions.cs + +using System; +using System.Linq; +using System.Reflection; +using System.Linq.Expressions; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace xCommons.Extensions +{ + public static partial class ModelExtensions + { + /// + /// Retrieve a Binding Flag for Retrieve all Instance Members including + /// Priate and Public ... + /// + /// + public static BindingFlags GetDefaultBindingFlag() + { + return BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + } + + /// + /// Update an Instance of a Typed Object with Data Provided by another Instance of Typed Object + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static T UpdateData( + this T source, + D updateWith, + ICollection propertyWhiteList = null, + ICollection propertyBlackList = null, + ICollection>> propertyValueProviders = null, + bool updateWithNullOrEmptyValues = false, + bool throwExceptionOnFails = false + ) + where T : class + where D : class + { + // + var hasWhiteList = propertyWhiteList.HasChild(); + var hasBlackList = propertyBlackList.HasChild(); + var hasValueProvider = propertyValueProviders.HasChild>>(); + + // + var fullPropertyList = updateWith.GetType().GetProperties().Select(prop => prop.Name); + var mustSetProps = (hasWhiteList ? propertyWhiteList : fullPropertyList); + + // + if (hasBlackList) + { + mustSetProps = mustSetProps.Where(propName => !propertyBlackList.Contains(propName)); + } + + // + mustSetProps.ToList().ForEach(propName => + { + // + try + { + // + var sourceProp = source.GetType().GetProperty(propName); + var sourceValue = sourceProp.GetValue(source); + var sourceValueType = sourceValue.IsNull() ? null : sourceValue.GetType(); + + // + var updateWithProp = updateWith.GetType().GetProperty(propName); + var updateWithValue = updateWithProp.GetValue(updateWith); + var updateWithValueType = updateWithValue.IsNull() ? null : updateWithValue.GetType(); + + // + var valueProviderValue = hasValueProvider ? propertyValueProviders + .FirstOrDefault(p => p.Key == propName).Value : null; + var updateVal = !valueProviderValue.IsNull() ? + valueProviderValue.Invoke(updateWith) : + (updateWithValue.IsNull() && !updateWithNullOrEmptyValues) ? + sourceValue : updateWithValue; + var updateValType = updateVal.IsNull() ? null : updateVal.GetType(); + + // + Type t = Nullable.GetUnderlyingType(sourceProp.PropertyType) ?? sourceProp.PropertyType; + object safeValue = + (updateVal == null) ? + null : + !updateValType.IsCollectionType() ? + Convert.ChangeType(updateVal, t) : + updateVal; + + // + sourceProp.SetValue(source, safeValue); + } + catch (Exception ex) + { + // + if (throwExceptionOnFails) + { + throw ex; + } + } + }); + + // + return source; + } + + /// + /// Check a Class is Contains a Property or not ... + /// + /// + /// + /// + /// + /// + public static bool HasProperty( + this T source, + string property, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + where T : class + { + // + var result = source + .GetType() + .GetProperties(bindingAttr) + .Any(prop => prop.Name == property); ; + + // + return result; + } + + /// + /// Check a Class is Contains a field or not ... + /// + /// + /// + /// + /// + /// + public static bool HasField( + this T source, + string field, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + where T : class + { + // + var result = source + .GetType() + .GetFields(bindingAttr) + .Any(fld => fld.Name == field); + + // + return result; + } + + /// + /// Check a Class is Contains a Method or not ... + /// + /// + /// + /// + /// + /// + public static bool HasMethod( + this T source, + string method, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + where T : class + { + // + var result = source + .GetType() + .GetMethods(bindingAttr) + .Any(mthd => mthd.Name == method); + + // + return result; + } + + /// + /// Retrieve a Class Properties Names ... + /// + /// + /// + /// + /// + public static string[] GetPropertiesNames( + this T source, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + { + // + var result = source + .GetType() + .GetProperties(bindingAttr) + .Select(p => p.Name) + .ToArray(); + + // + return result; + } + + /// + /// Retrieve a Class Fields Names ... + /// + /// + /// + /// + /// + public static string[] GetFieldsNames( + this T source, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + { + // + var result = source + .GetType() + .GetFields(bindingAttr) + .Select(p => p.Name) + .ToArray(); + + // + return result; + } + + /// + /// Retrieve a Class Methods Names ... + /// + /// + /// + /// + /// + public static string[] GetMethodsNames( + this T source, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + { + // + var result = source + .GetType() + .GetMethods(bindingAttr) + .Select(p => p.Name) + .ToArray(); + + // + return result; + } + + /// + /// Retrieve a Class Members Names ... + /// + /// + /// + /// + /// + public static string[] GetMembersNames( + this T source, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + { + // + var result = source + .GetType() + .GetMembers(bindingAttr) + .Select(p => p.Name) + .ToArray(); + + // + return result; + } + + /// + /// Get a Class Property Value using Generic Concept ... + /// + /// + /// + /// + /// + /// + /// + public static TProp GetProperty( + this T source, + string property, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + where T : class + { + // + TProp result = default; + + // + // Check if T Has Property ... + if (source.HasProperty(property)) + { + // + // Access Property ... + var prop = source + .GetType() + .GetProperty( + property, + bindingAttr + ); + + // + // Retrieve Property Value as Object ... + var propVal = prop.GetValue(source); + + // + result = (TProp)propVal; + } + + // + return result; + } + + /// + /// Get a Class Field Value using Generic Concept ... + /// + /// + /// + /// + /// + /// + /// + public static TField GetField( + this T source, + string field, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + where T : class + { + // + TField result = default; + + // + if (source.HasField(field)) + { + // + var fld = source + .GetType() + .GetField( + field, + bindingAttr + ); + + // + var fieldVal = fld.GetValue(source); + result = (TField)fieldVal; + } + + // + return result; + } + + /// + /// Set a Class Property Value using Generic Concept ... + /// + /// + /// + /// + /// + /// + /// + /// + public static bool SetProperty( + this T source, + string property, + TProp value, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + where T : class + { + // + // Check T Has Property ... + var result = source.HasProperty(property); + if (!result) + { + return result; + } + + // + // Access Property ... + var prop = source + .GetType() + .GetProperty( + property, + bindingAttr + ); + + // + // Set Property ... + try + { + // + result = true; + prop.SetValue(source, value); + } + catch + { + result = false; + } + + // + return result; + } + + /// + /// Set a Class Field Value using Generic Concept ... + /// + /// + /// + /// + /// + /// + /// + /// + public static bool SetField( + this T source, + string field, + TField value, + BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic + ) + where T : class + { + // + // Check T Has Field ... + var result = source.HasField(field); + if (!result) + { + return result; + } + + // + // Access Field ... + var fld = source + .GetType() + .GetField( + field, + bindingAttr + ); + + // + // Set Value ... + try + { + // + result = true; + fld.SetValue(source, value); + } + catch + { + result = false; + } + + // + return result; + } + + /// + /// Check two Instance of Typed Object Values are Same or not + /// + /// + /// + /// + /// + /// + /// + /// /// + /// + /// + public static bool IsSameContent( + this T source, + D dest, + ICollection propertyWhiteList = null, + ICollection propertyBlackList = null, + ICollection>> propertyValueCheckers = null, + bool throwExceptionOnFails = false + ) + where T : class + where D : class + { + // + var hasWhiteList = propertyWhiteList.HasChild(); + var hasBlackList = propertyBlackList.HasChild(); + var hasCheckerProvider = propertyValueCheckers.HasChild>>(); + + // + var fullPropertyList = dest.GetType().GetProperties().Select(prop => prop.Name); + var mustCheckProps = (hasWhiteList ? propertyWhiteList : fullPropertyList); + if (hasBlackList) + { + mustCheckProps = mustCheckProps.Where(propName => !propertyBlackList.Contains(propName)); + } + + // + var result = true; + mustCheckProps.ToList().ForEach(propName => + { + // + try + { + // + var sourceProp = source.GetType().GetProperty(propName); + var destProp = dest.GetType().GetProperty(propName); + + // + // Check Types ... + var isSameType = true; + + // + // Check + var isSameValue = false; + var mustDoCustomCheck = hasCheckerProvider && propertyValueCheckers.Any(p => p.Key == propName); + if (mustDoCustomCheck) + { + // + // Check Values ... + var valueChecker = propertyValueCheckers.FirstOrDefault(p => p.Key == propName).Value; + isSameValue = valueChecker.Invoke(source, dest); + } + else + { + // // + // isSameType = (sourceProp.PropertyType == destProp.PropertyType && + // sourceProp.ReflectedType == destProp.ReflectedType); + + // + // Check Values ... + isSameValue = Equals(sourceProp.GetValue(source), destProp.GetValue(dest)); + } + + // + if (!isSameType || !isSameValue) + { + result = false; + } + } + catch (Exception ex) + { + // + if (throwExceptionOnFails) + { + throw ex; + } + } + }); + + // + return result; + } + + /// + /// Check specific type is Collection Type or not + /// + /// + /// + public static bool IsCollectionType(this Type source) + { + // + if (source.IsNull() || !source.IsGenericType) + { + return false; + } + + // + var genericTypeDefinition = source.GetGenericTypeDefinition(); + var result = genericTypeDefinition == typeof(List<>) || + genericTypeDefinition == typeof(HashSet<>) || + genericTypeDefinition == typeof(Collection<>) || + genericTypeDefinition == typeof(ICollection<>) || + genericTypeDefinition == typeof(IEnumerable<>); + + // + return result; + } + + /// + /// Retrieve a Property Value in Generic + /// + /// + /// + /// + public static string GetPropValues(this T item) + where T : class + { + // + var props = item.GetType().GetProperties(); + var vals = props.Select(p => p.GetValue(item)); + + // + return vals.ToJSON(); + } + + /// + /// Check a Property Value Contains specific value + /// + /// + /// + /// + /// + public static bool PropValuesContains(this T item, string value) + where T : class + { + // + return item.GetPropValues() + .ToNormalString() + .Contains(value + .ToNormalString()); + } + + /// + /// Generate Default Column Map + /// + /// + /// + /// + public static IDictionary>> GetDefaultColumnsMap(this T item) + where T : class + { + // + if (item.IsNull()) + { + return null; + } + + // + var result = new Dictionary>>(); + var props = item.GetType().GetProperties(); + + // + foreach (var prop in props) + { + result.Add(prop.Name, t => t.GetType().GetProperty(prop.Name).GetValue(t, null)); + } + + // + return result; + } + + /// + /// Generate Default Column Map + /// + /// + /// + /// + public static IDictionary GetPropValuesDictionary(this T item) + where T : class + { + // + if (item.IsNull()) + { + return null; + } + + // + var result = new Dictionary(); + var props = item.GetType().GetProperties(); + + // + foreach (var prop in props) + { + result.Add(prop.Name, prop.GetValue(item).ToJSON()); + } + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\ServiceProviderExtensions.cs + +using System; +using xCommons.Helpers; +using System.Collections; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; + +namespace xCommons.Extensions { + public static class ServiceProviderExtensions { + /// + /// Get all registered + /// + /// + /// + public static Dictionary GetAllServiceDescriptors (this IServiceProvider provider) { + // + if (provider is ServiceProvider serviceProvider) { + // + var result = new Dictionary (); + + // + var engine = serviceProvider.GetFieldValue ("_engine"); + var callSiteFactory = engine.GetPropertyValue ("CallSiteFactory"); + var descriptorLookup = callSiteFactory.GetFieldValue ("_descriptorLookup"); + if (descriptorLookup is IDictionary dictionary) { + foreach (DictionaryEntry entry in dictionary) { + result.Add ((Type) entry.Key, (ServiceDescriptor) entry.Value.GetPropertyValue ("Last")); + } + } + + // + return result; + } + + // + throw new NotSupportedException ($"Type '{provider.GetType()}' is not supported!"); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\XHttpContextExtensions.cs + +using System.IO; +using Microsoft.AspNetCore.Http; + +namespace xCommons.Extensions { + public static partial class XHttpContextExtensions { + /// + /// Convert IFormFile to Bytes Array + /// + /// + /// + public static byte[] ToByteArray (this IFormFile source) { + // + byte[] data; + using (var br = new BinaryReader (source.OpenReadStream ())) { + data = br.ReadBytes ((int) source.OpenReadStream ().Length); + } + + // + return data; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Extensions\XMiddlewareExtensions.cs + +using xCommons.Middlewares; +using Microsoft.AspNetCore.Builder; + +namespace xCommons.Extensions +{ + public static class XMiddlewareExtensions + { + /// + /// Use XRequestLogger Middleware for log Requests ... + /// + /// + public static void UseXRequestLogger(this IApplicationBuilder app) { + app.UseMiddleware(); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Filters\RequireXPoweredOperationFilter.cs + +using xCommons.Constants; +using xCommons.Extensions; +using Microsoft.OpenApi.Any; +using xCommons.Configurations; +using Microsoft.OpenApi.Models; +using System.Collections.Generic; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace xCommons.Filters { + /// + /// RequireXPowered for NGSwag + /// + public partial class RequireXPoweredOperationFilter : IOperationFilter { + private readonly XAppConfiguration config; + + public RequireXPoweredOperationFilter (XAppConfiguration config) { + this.config = config; + } + + public void Apply (OpenApiOperation operation, OperationFilterContext context) { + // + if (operation.Parameters.IsNull ()) { + operation.Parameters = new List (); + } + + // + operation.Parameters.Add (new OpenApiParameter { + In = ParameterLocation.Header, + Name = XAuthorization.XPoweredBy, + Description = "a Basic Authorization Filter", + Required = false, + Schema = new OpenApiSchema { + Type = "String", + Default = new OpenApiString (config.XPoweredValue) + } + }); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Filters\SwaggerDefaultValuesOperationFilter.cs + +using System.Linq; +using xCommons.Extensions; +using Microsoft.OpenApi.Any; +using xCommons.Configurations; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace xCommons.Filters +{ + public class SwaggerDefaultValuesOperationFilter : IOperationFilter + { + private readonly XSwaggerConfiguration config; + + public SwaggerDefaultValuesOperationFilter( + XSwaggerConfiguration config + ) + { + this.config = config; + } + + public void Apply( + OpenApiOperation operation, + OperationFilterContext context + ) + { + // + // Validate Configuration ... + if ( + !config.IsNull() && + !config.DefaultValues.IsNull() && + config.DefaultValues.HasChild() + ) + { + // + foreach (var prv in config.DefaultValues) + { + // + // Detect Parameter ... + var swaggerParam = operation.Parameters.FirstOrDefault(p => p.Name == prv.Key); + if (!swaggerParam.IsNull()) + { + swaggerParam.Schema.Default = new OpenApiString(prv.Value); + } + } + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Helpers\ObjectHelper.cs + +using System; +using System.Linq; +using System.Collections.Generic; + +namespace xCommons.Helpers { + public partial class ObjectHelper { + /// + /// Convert Enum Keys to Array + /// + /// + /// + public static Array GetKeys () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = Enum.GetNames (type); + + // + return result; + } + + /// + /// Convert EnumValues to Array + /// + /// + /// + public static Array GetValues () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = Enum.GetValues (type); + + // + return result; + } + + /// + /// Convert an Enum to IEnumerable of it's Childs + /// + /// + /// + public static IEnumerable ToEnumerableValues () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = GetValues () + .Cast (); + + // + return result; + } + + /// + /// Retrieve Keys of an Enumerable + /// + /// + /// + public static IEnumerable ToEnumerableKeys () { + // + ValidateEnumbType (); + var type = typeof (T); + + // + var result = GetKeys () + .Cast (); + + // + return result; + } + + public static IDictionary ToDictionary () { + // + ValidateEnumbType (); + + // + var result = new Dictionary (); + foreach (var name in Enum.GetNames (typeof (T))) { + result.Add (name, (int) Enum.Parse (typeof (T), name)); + } + + // + return result; + } + + private static void ValidateEnumbType () { + // + var type = typeof (T); + if (!type.IsEnum) { + throw new InvalidCastException (); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Helpers\XDateHelper.cs + +using System; + +namespace xCommons.Helpers { + public partial class XDateHelper { + /// + /// retrieve utc now timestam unix like representation ... + /// + /// + public static long UtcNowTimespan () { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds (); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Helpers\XGenericHelper.cs + +using System; +using System.Linq; +using System.Reflection; +using xCommons.Extensions; +using System.Collections.Generic; + +namespace xCommons.Helpers +{ + public static class XGenericHelper + { + /// + /// Invoke a Generic Method of Specified Type ... + /// + /// + /// + /// + /// + public static void InvokeGenericMethod( + this Type source, + string methodName, + Type runtimeType, + params object[] args + ) + { + // + source.InvokeGenericMethod( + args: args, + methodName: methodName, + runtimeType: runtimeType + ); + } + + /// + /// Invoke a Generic Method of Specified Type ... + /// + /// + /// + /// + /// + /// + /// + public static TResponse InvokeGenericMethod( + this Type source, + string methodName, + Type runtimeType, + params object[] args + ) + { + // + var overLoads = source.GetGenericMethodInfo( + methodName: methodName + ); + if (!overLoads.HasChild()) + { + return default; + } + + // + var method = overLoads.ElementAt(0); + MethodInfo genericMethod = method.MakeGenericMethod(runtimeType); + + // + var resultObj = genericMethod.Invoke(null, args); + var result = (TResponse)resultObj; + + // + return result; + } + + /// + /// Invoke a Generic Method of Specified Type ... + /// + /// + /// + /// + /// + public static void InvokeGenericMethod( + this Type source, + string methodName, + Type[] runtimeTypes, + params object[] args + ) + { + // + source.InvokeGenericMethod( + args: args, + methodName: methodName, + runtimeTypes: runtimeTypes + ); + } + + /// + /// Invoke a Generic Method of Specified Type ... + /// + /// + /// + /// + /// + /// + /// + public static TResponse InvokeGenericMethod( + this Type source, + string methodName, + Type[] runtimeTypes, + params object[] args + ) + { + // + var overLoads = source.GetGenericMethodInfo( + methodName: methodName + ); + if (!overLoads.HasChild()) + { + return default; + } + + // + // Detect a Method by Generic Types ... + // var method = overLoads.ElementAt(0); + var method = overLoads.First(m => m.GetGenericArguments().Length == runtimeTypes.Length); + MethodInfo genericMethod = method.MakeGenericMethod(runtimeTypes); + + // + var resultObj = genericMethod.Invoke(null, args); + var result = resultObj.ConvertTo(); + + // + return result; + } + + /// + /// Extract a Generic Method's Overloads ... + /// + /// + /// + /// + public static List GetGenericMethodInfo( + this Type source, + string methodName + ) + { + // + var methods = source + .GetMethods(); + + // + var result = methods + .Where(mi => + mi.Name == methodName && + mi.IsGenericMethodDefinition) + .ToList(); + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Helpers\XMLToJSONHelper.cs + +using System; +using System.Xml; +using System.Text; +using System.Collections.Generic; + +namespace xCommons.Helpers { + public partial class XMLToJSONHelper { + // + #region Actions ... + public static string ToJSON (string xml) { + // + XmlDocument doc = new XmlDocument (); + doc.LoadXml (xml); + + // + return XmlToJSON (doc); + } + + public static string XmlToJSON (XmlDocument xmlDoc) { + // + StringBuilder sbJSON = new StringBuilder (); + + // + sbJSON.Append ("{ "); + XmlToJSONnode (sbJSON, xmlDoc.DocumentElement, true); + sbJSON.Append ("}"); + + // + return sbJSON.ToString (); + } + #endregion + + // + #region Private ... + // + private static void XmlToJSONnode (StringBuilder sbJSON, XmlElement node, bool showNodeName) { + // + if (showNodeName) { + sbJSON.Append ("\"" + SafeJSON (node.Name) + "\": "); + } + + // + sbJSON.Append ("{"); + + // + // Build a sorted list of key-value pairs + // where key is case-sensitive nodeName + // value is an ArrayList of string or XmlElement + // so that we know whether the nodeName is an array or not. + SortedList childNodeNames = new SortedList (); + + // + // Add in all node attributes + if (node.Attributes != null) + foreach (XmlAttribute attr in node.Attributes) + StoreChildNode (childNodeNames, attr.Name, attr.InnerText); + + // + // Add in all nodes + foreach (XmlNode cnode in node.ChildNodes) { + // + if (cnode is XmlText) { + StoreChildNode (childNodeNames, "value", cnode.InnerText); + } else if (cnode is XmlElement) { + StoreChildNode (childNodeNames, cnode.Name, cnode); + } + } + + // + // Now output all stored info + foreach (string childname in childNodeNames.Keys) { + // + List alChild = (List) childNodeNames[childname]; + if (alChild.Count == 1) { + OutputNode (childname, alChild[0], sbJSON, true); + } else { + // + sbJSON.Append (" \"" + SafeJSON (childname) + "\": [ "); + + // + foreach (object Child in alChild) { + OutputNode (childname, Child, sbJSON, false); + } + + // + sbJSON.Remove (sbJSON.Length - 2, 2); + sbJSON.Append (" ], "); + } + } + + // + sbJSON.Remove (sbJSON.Length - 2, 2); + sbJSON.Append (" }"); + } + + // + // StoreChildNode: Store data associated with each nodeName + // so that we know whether the nodeName is an array or not. + private static void StoreChildNode (SortedList childNodeNames, string nodeName, object nodeValue) { + // + // Pre-process contraction of XmlElement-s + if (nodeValue is XmlElement) { + // + // Convert into "aa":null + // xx into "aa":"xx" + XmlNode cnode = (XmlNode) nodeValue; + if (cnode.Attributes.Count == 0) { + // + XmlNodeList children = cnode.ChildNodes; + if (children.Count == 0) { + nodeValue = null; + } else if (children.Count == 1 && (children[0] is XmlText)) { + nodeValue = ((XmlText) (children[0])).InnerText; + } + } + } + + // + // Add nodeValue to ArrayList associated with each nodeName + // If nodeName doesn't exist then add it + List ValuesAL; + + // + if (childNodeNames.ContainsKey (nodeName)) { + ValuesAL = (List) childNodeNames[nodeName]; + } else { + // + ValuesAL = new List (); + childNodeNames[nodeName] = ValuesAL; + } + + // + ValuesAL.Add (nodeValue); + } + + // + private static void OutputNode (string childname, object alChild, StringBuilder sbJSON, bool showNodeName) { + if (alChild == null) { + if (showNodeName) + sbJSON.Append ("\"" + SafeJSON (childname) + "\": "); + sbJSON.Append ("null"); + } else if (alChild is string) { + if (showNodeName) + sbJSON.Append ("\"" + SafeJSON (childname) + "\": "); + string sChild = (string) alChild; + sChild = sChild.Trim (); + sbJSON.Append ("\"" + SafeJSON (sChild) + "\""); + } else + XmlToJSONnode (sbJSON, (XmlElement) alChild, showNodeName); + sbJSON.Append (", "); + } + + // + // Make a string safe for JSON + private static string SafeJSON (string sIn) { + // + StringBuilder sbOut = new StringBuilder (sIn.Length); + foreach (char ch in sIn) { + // + if (Char.IsControl (ch) || ch == '\'') { + // + int ich = (int) ch; + sbOut.Append (@"\u" + ich.ToString ("x4")); + continue; + } else if (ch == '\"' || ch == '\\' || ch == '/') { + sbOut.Append ('\\'); + } + + // + sbOut.Append (ch); + } + + // + return sbOut.ToString (); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Helpers\XNetworkHelper.cs + +using System.Net; +using System.Linq; +using xCommons.Models; +using System.Net.Sockets; +using System.Collections.Generic; +using System.Net.NetworkInformation; + +namespace xCommons.Helpers { + public static class XNetworkHelper { + /// + /// Retrieve Application Host's Machin IP Addresses ... + /// + /// string + public static string GetIp () { + // + var name = Dns.GetHostName (); // get container id + var result = Dns.GetHostEntry (name) + .AddressList + .FirstOrDefault ( + x => x.AddressFamily == AddressFamily.InterNetwork + ) + .MapToIPv4 () + .ToString (); + + // + return result; + } + + /// + /// Retrieve All Available IP's in Application Host's Machin ... + /// + /// a Collection of XNetworkInfo class instances + public static IEnumerable GetNetworkInfos () { + // + var result = System.Net.NetworkInformation.NetworkInterface + .GetAllNetworkInterfaces () + .Where ( + ni => + !ni.IsReceiveOnly && + ni.OperationalStatus == OperationalStatus.Up && + ni.NetworkInterfaceType != NetworkInterfaceType.Loopback + ) + .SelectMany (ni => + ni + .GetIPProperties () + .GatewayAddresses + .Select (ga => + new { + Name = ni.Name, + Description = ni.Description, + Gateway = ga.Address + .MapToIPv4 () + .ToString (), + NI = ni + } + ) + ) + .SelectMany (data => + data.NI + .GetIPProperties () + .UnicastAddresses + .Select (ip => + new XNetworkInfo { + Name = data.Name, + Gateway = data.Gateway, + Description = data.Description, + NetworkInterface = data.NI, + IP = ip.Address + .MapToIPv4 () + .ToString () + } + ) + ); + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Helpers\XOsHelper.cs + +using System; +using System.Diagnostics; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using xExceptions.Attributes; +using System.Runtime.InteropServices; + +namespace xCommons.Helpers +{ + /// + /// Several OS Type ... + /// + public enum XOSType + { + [StringValue("Unknown")] + XUnknown, + + [StringValue("Mac")] + XMacOs, + + [StringValue("Linux")] + XLinux, + + [StringValue("Windows")] + XWindows, + + [StringValue("FreeBSD")] + XFreeBSD, + } + + /// + /// OS Command Execution Result ... + /// + public class XOSCMDResult + { + /// + /// Execution Result ... + /// + /// + public string Result { get; set; } + + /// + /// Execution Error ... + /// + /// + public string Error { get; set; } + } + + /// + /// a Helper Class which Provides OS Command Executions ... + /// + public static partial class XOsHelper + { + /// + /// Retrieve OS Type ... + /// + /// + public static XOSType GetOSType() + { + // + XOSType result = XOSType.XUnknown; + + // + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + result = XOSType.XMacOs; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + result = XOSType.XLinux; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result = XOSType.XWindows; + } + // else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) + // { + // result = XOSType.XFreeBSD; + // } + + // + return result; + } + + /// + /// Retrieve OS Description ... + /// + /// + public static string GetOSDescription() + { + // + var result = RuntimeInformation.OSDescription; + return result; + } + + /// + /// Execute an Specified Command ... + /// + /// Specified Command + /// Command Arguments + /// + public static async Task Execute( + string cmd, + string args + ) + { + // + var result = new XOSCMDResult + { + Error = "Unknown", + }; + + // + try + { + // + // Validate CMD ... + var isValid = !cmd.IsNullOrEmpty(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Validate OS Type ... + XOSType osType = GetOSType(); + isValid = osType != XOSType.XUnknown; + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + return await Task.Run(() => + { + // + ProcessStartInfo psi = new ProcessStartInfo(); + + // + psi.FileName = cmd; + psi.Arguments = args; + psi.UseShellExecute = false; + psi.RedirectStandardError = true; + psi.RedirectStandardOutput = true; + + // + Process process = Process.Start(psi); + + // + // Read the output + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + + // + process.WaitForExit(); + + // + result = new XOSCMDResult + { + Error = error, + Result = output, + }; + + // + return result; + }); + } + catch (Exception ex) + { + result.Error = ex.Message; + } + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Helpers\XReflectionHelper.cs + +using System; +using System.Reflection; + +namespace xCommons.Helpers { + public static class XReflectionHelper { + public static object GetFieldValue (this object obj, string fieldName) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var fieldInfo = GetFieldInfo (objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException (fieldName, + $"Couldn't find field {fieldName} in type {objType.FullName}"); + return fieldInfo.GetValue (obj); + } + + public static void SetFieldValue (this object obj, string fieldName, object val) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var fieldInfo = GetFieldInfo (objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException (fieldName, + $"Couldn't find field {fieldName} in type {objType.FullName}"); + fieldInfo.SetValue (obj, val); + } + + private static FieldInfo GetFieldInfo (Type type, string fieldName) { + FieldInfo fieldInfo = null; + do { + fieldInfo = type.GetField (fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + type = type.BaseType; + } while (fieldInfo == null && type != null); + + return fieldInfo; + } + + public static object GetPropertyValue (this object obj, string propertyName) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var propertyInfo = GetPropertyInfo (objType, propertyName); + if (propertyInfo == null) + throw new ArgumentOutOfRangeException (propertyName, + $"Couldn't find property {propertyName} in type {objType.FullName}"); + return propertyInfo.GetValue (obj, null); + } + + public static void SetPropertyValue (this object obj, string propertyName, object val) { + if (obj == null) + throw new ArgumentNullException (nameof (obj)); + Type objType = obj.GetType (); + var propertyInfo = GetPropertyInfo (objType, propertyName); + if (propertyInfo == null) + throw new ArgumentOutOfRangeException (propertyName, + $"Couldn't find property {propertyName} in type {objType.FullName}"); + propertyInfo.SetValue (obj, val, null); + } + + private static PropertyInfo GetPropertyInfo (Type type, string propertyName) { + PropertyInfo propertyInfo = null; + do { + propertyInfo = type.GetProperty (propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + type = type.BaseType; + } while (propertyInfo == null && type != null); + + return propertyInfo; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Interfaces\IXCipher.cs + +namespace xCommons.Interfaces +{ + public interface IXCipher + { + string Encrypt(string plainText); + string Decrypt(string cipherText); + string EncryptFromBytes(byte[] textBytes); + string DecryptFromBytes(byte[] cipherBytes); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Middlewares\XRequestLogger.cs + +using System; +using System.IO; +using xCommons.Models; +using xCommons.Extensions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace xCommons.Middlewares { + public partial class XRequestLogger { + + private readonly ILogger logger; + private readonly RequestDelegate next; + + public XRequestLogger ( + RequestDelegate next, + ILoggerFactory loggerFactory + ) { + // + this.next = next; + this.logger = loggerFactory + .CreateLogger (); + } + + public async Task InvokeAsync (HttpContext context) { + // + var logModel = new XLoggerModel (); + + // + logModel.Path = context.Request.Path; + logModel.Schema = context.Request.Scheme; + logModel.Method = context.Request.Method; + logModel.Headers = context.Request.Headers; + logModel.Host = context.Request.Host.ToString (); + logModel.QueryString = context.Request.QueryString.ToString (); + logModel.UserID = context.User.Identity.IsAuthenticated ? context.User.Identity.Name : "Not Authenticated"; + + // + logModel.Connection = new XLoggerHttpConnection { + Id = context.Connection.Id, + RemoteIpAddress = context.Connection.RemoteIpAddress.ToString (), + RemotePort = context.Connection.RemotePort.ToString (), + LocalIpAddress = context.Connection.LocalIpAddress.ToString (), + LocalPort = context.Connection.LocalPort.ToString (), + }; + + // + // Check if a request is a Post Call so read data from Body ... + if (context.Request.Method.ToNormalString () == "post") { + // + context.Request.EnableBuffering (); + var request = await new StreamReader (context.Request.Body) + .ReadToEndAsync (); + + // + context.Request.Body.Position = 0; + logModel.Request = request; + } + + // + logModel.RequestedOn = DateTime.UtcNow; + + // + // Do Request ... + await next.Invoke (context); + + // + using (Stream originalRequest = context.Response.Body) { + try { + using (var memStream = new MemoryStream ()) { + // + context.Response.Body = memStream; + + // + // All the Request processing as described above + // happens from here. + // Response handling starts from here + // set the pointer to the beginning of the + // memory stream to read + memStream.Position = 0; + + // + // read the memory stream till the end + var response = await new StreamReader (memStream) + .ReadToEndAsync (); + + // + logModel.Response = response; + logModel.RespondedOn = DateTime.UtcNow; + logModel.StatusCode = context.Response.StatusCode; + logModel.IsSucceed = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299; + + // + // since we have read till the end of the stream, + // reset it onto the first position + memStream.Position = 0; + + // + // now copy the content of the temporary memory + // stream we have passed to the actual response body + // which will carry the response out. + await memStream.CopyToAsync (originalRequest); + } + } catch (Exception ex) { + // + // Log Exception ... + logger.LogError ($"Error on Logging Request/Response ... {ex.ToJSON()}"); + } finally { + // + // assign the response body to the actual context + context.Response.Body = originalRequest; + } + } + + // + // Log Response ... + logger.LogCritical ($"Http Response/Response Information:{Environment.NewLine}" + + $"Schema:{logModel.Schema} " + Environment.NewLine + + $"Host: {logModel.Host} " + Environment.NewLine + + $"Path: {logModel.Path} " + Environment.NewLine + + $"Method: {logModel.Method} " + Environment.NewLine + + $"UserID: {logModel.UserID} " + Environment.NewLine + + $"QueryString: {context.Request.QueryString} " + Environment.NewLine + + $"Headers: {logModel.Headers.ToJSON()}" + Environment.NewLine + + $"" + Environment.NewLine + + $"Http Connection: " + Environment.NewLine + + $"Id: {logModel.Connection.Id}" + Environment.NewLine + + $"RemoteIpAddress: {logModel.Connection.RemoteIpAddress}" + Environment.NewLine + + $"RemotePort: {logModel.Connection.RemotePort}" + Environment.NewLine + + $"LocalIpAddress: {logModel.Connection.LocalIpAddress}" + Environment.NewLine + + $"LocalPort: {logModel.Connection.LocalPort}" + Environment.NewLine + + $"" + Environment.NewLine + + $"Request Body: {logModel.Request}" + Environment.NewLine + + $"RequestedOn: {logModel.RequestedOn}" + Environment.NewLine + + $"" + Environment.NewLine + + $"IsSucceed: {logModel.IsSucceed}" + Environment.NewLine + + $"StatucCode: {logModel.StatusCode}" + Environment.NewLine + + $"Response Body: {logModel.Response}" + Environment.NewLine + + $"RespondedOn: {logModel.RespondedOn}" + Environment.NewLine + + $"" + Environment.NewLine + ); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Models\XCertificate.cs + +namespace xCommons.Models { + public partial class XCertificate { + public string Path { get; set; } + public string Secret { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Models\XLoggerModel.cs + +using System; +using Microsoft.AspNetCore.Http; + +namespace xCommons.Models { + public class XLoggerModel { + public string Schema { get; set; } + public string Host { get; set; } + public string Path { get; set; } + public string Method { get; set; } + public string UserID { get; set; } + public string QueryString { get; set; } + public IHeaderDictionary Headers { get; set; } + public string Request { get; set; } + public int StatusCode { get; set; } + public bool IsSucceed { get; set; } + public string Response { get; set; } + public DateTime RequestedOn { get; set; } + public DateTime RespondedOn { get; set; } + public XLoggerHttpConnection Connection { get; set; } + } + + public class XLoggerHttpConnection { + public string Id { get; set; } + public string RemoteIpAddress { get; set; } + public string RemotePort { get; set; } + public string LocalIpAddress { get; set; } + public string LocalPort { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Models\XNetworkInfo.cs + +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Net.NetworkInformation; + +namespace xCommons.Models { + public class XNetworkInfo { + /// + /// Network Name ... + /// + /// + public string Name { get; set; } + + /// + /// Network Description ... + /// + /// + public string Description { get; set; } + + /// + /// IP Address which Exists on Current Network ... + /// + /// + public string IP { get; set; } + + /// + /// Current Networks Gateway Address ... + /// + /// + public string Gateway { get; set; } + + /// + /// Network Interface Object ... + /// + /// + public NetworkInterface NetworkInterface { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Models\XServiceDescriptor.cs + +using System; + +namespace xCommons.Models +{ + public class XServiceDescriptor + { + public Type Service { get; set; } + public Type Implementation { get; set; } + } + + public class XServiceDescriptor : XServiceDescriptor + { + public XServiceDescriptor() : base() + { + Service = typeof(TService); + Implementation = typeof(TImplmentation); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Providers\XBackgroundService.cs + +using System; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace xCommons.Providers +{ + /// + /// Default Background Service ... + /// + public abstract class XBackgroundService : BackgroundService + { + // + protected readonly IServiceProvider serviceProvider; + protected readonly ILogger logger; + + protected XBackgroundService( + IServiceProvider serviceProvider, + ILogger logger + ) + { + // + this.logger = logger; + this.serviceProvider = serviceProvider; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Providers\XCipher.cs + +using System; +using System.IO; +using System.Text; +using xCommons.Extensions; +using xCommons.Interfaces; +using System.Security.Cryptography; + +namespace xCommons.Providers +{ + public class XCipher : IXCipher + { + // + #region Props ... + private readonly string secretKey; + private readonly byte[] iv; + private readonly byte[] key; + private readonly XValidationProvider validationProvider; + #endregion + + // + #region Constructor ... + public XCipher( + string cipherKey, + XValidationProvider validationProvider + ) + { + // + this.secretKey = cipherKey.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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Providers\XResumableBackgroundService.cs + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace xCommons.Providers +{ + /// + /// Resumable Background Task ... + /// + public abstract class XResumableBackgroundService : XBackgroundService + { + /// + /// Check is Paused ... + /// + protected bool paused = false; + + protected XResumableBackgroundService( + IServiceProvider serviceProvider, + ILogger logger + ) : base( + logger: logger, + serviceProvider: serviceProvider + ) + { } + + protected override async Task ExecuteAsync( + CancellationToken stoppingToken = default + ) + { + // + if (paused) + { + return; + } + + // + await DoWorkAsync( + payload: null, + cancellationToken: stoppingToken + ); + } + + /// + /// do all required works here ... + /// + /// + /// + /// + public abstract Task DoWorkAsync( + object payload = null, + CancellationToken cancellationToken = default + ); + + /// + /// Pause Consuming ... + /// + /// + public bool Pause() + { + // + var result = !paused; + if (!result) + { + return result; + } + + // + paused = true; + result = paused; + return result; + } + + /// + /// Resume Consuming ... + /// + /// + public bool Resume() + { + // + var result = paused; + if (!result) + { + return result; + } + + // + paused = false; + result = !paused; + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Providers\XTimeBasedBackgroundService.cs + +using System; +using System.Threading; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace xCommons.Providers +{ + /// + /// Do Background Jobs based on Time Intervals ... + /// + public abstract class XTimeBasedBackgroundService : XBackgroundService + { + // + private bool isWorking = false; + private readonly TimeSpan restartInterval; + private readonly SemaphoreSlim mlock = new(1, 1); + public DateTime? LastRunTime { get; private set; } + private CancellationTokenSource cancellationTokenSource; + + protected XTimeBasedBackgroundService( + IServiceProvider serviceProvider, + ILogger logger, + int intervalInMinutes = 10 + ) : base( + logger: logger, + serviceProvider: serviceProvider + ) + { + // + // Validate Interval ... + if (intervalInMinutes <= 0) + { + XException.InvalidArgs.Throw(); + } + + // + restartInterval = TimeSpan.FromMinutes(intervalInMinutes); + } + + /// + /// Override base action to Implement timing ... + /// + /// + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // + while (!stoppingToken.IsCancellationRequested) + { + try + { + if (!isWorking) + { + // + // Enable Working ... + await mlock.WaitAsync(stoppingToken); + try + { + isWorking = true; + } + finally + { + mlock.Release(); + } + + // + // Start a new work task ... + cancellationTokenSource = CancellationTokenSource + .CreateLinkedTokenSource(stoppingToken); + + // + // Execute the work ... + await ExecuteWorkAsync(cancellationTokenSource.Token); + + // + // Disable Working ... + await mlock.WaitAsync(stoppingToken); + try + { + isWorking = false; + } + finally + { + mlock.Release(); + } + + // + // Wait for the restart interval ... + await Task.Delay(restartInterval, stoppingToken); + } + else + { + // + // Prevent tight error loops ... + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } + } + catch (OperationCanceledException) + { + // + // Disable Working ... + await mlock.WaitAsync(stoppingToken); + try + { + isWorking = false; + } + finally + { + mlock.Release(); + } + break; + } + catch (Exception) + { + // + // Disable Working ... + await mlock.WaitAsync(stoppingToken); + try + { + isWorking = false; + } + finally + { + mlock.Release(); + } + + // + // Prevent tight error loops ... + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); + } + } + } + + /// + /// Override Stop Background Worker ... + /// + /// + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + // + // Cancel the current work session + cancellationTokenSource.Cancel(); + await base.StopAsync(cancellationToken); + } + + /// + /// Dispose all Objects ... + /// + public override void Dispose() + { + mlock.Dispose(); + cancellationTokenSource.Dispose(); + GC.SuppressFinalize(this); + base.Dispose(); + } + + /// + /// do all required works here ... + /// + /// + /// + public abstract Task DoWorkAsync( + CancellationToken cancellationToken = default + ); + + /// + /// Refresh Worker ... + /// + /// + /// + public async Task Refresh( + CancellationToken cancellationToken = default + ) + { + // + // Cancel current work if running + cancellationTokenSource.Cancel(); + + // + // Wait a moment for cancellation + await Task.Delay(100, cancellationToken); + + // + // Start new work immediately + cancellationTokenSource = CancellationTokenSource + .CreateLinkedTokenSource(cancellationToken); + await ExecuteWorkAsync(cancellationTokenSource.Token); + } + + // + #region Private ... + private async Task ExecuteWorkAsync(CancellationToken cancellationToken) + { + // + LastRunTime = DateTime.UtcNow; + try + { + await DoWorkAsync(cancellationToken); + } + catch (OperationCanceledException) + { + } + catch (Exception) + { + } + finally + { + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xCommons\Providers\XValidationProvider.cs + +using System; +using System.Linq; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace xCommons.Providers { + public partial class XValidationProvider { + private ICollection validationGroup; + + // + #region Simple Validators ... + /// + /// given objects must not null + /// + /// + public void NotNull ( + params object[] values) { + // + foreach (var o in values) { + // + if (o == null) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// given strings must not empty or null + /// + /// + public void NotEmpty ( + params string[] values) { + // + foreach (var s in values) { + // + if (s.IsNullOrEmpty ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a numeric value is bigger than 0 + /// + /// + public void NotZero ( + params int[] values) { + // + foreach (var s in values) { + // + if (s == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a numeric value is bigger than 0 + /// + /// + public void NotZero ( + params long[] values) { + // + foreach (var s in values) { + // + if (s == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a numeric value is bigger than 0 + /// + /// + public void NotZero ( + params decimal[] values) { + // + foreach (var s in values) { + // + if (s == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a Value is a Valid Guid ... + /// + /// + public void IsGuid (params string[] values) { + // + foreach (var s in values) { + // + if (!s.IsGuid () || s.IsDefaultGuid ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a Value is a Valid Guid ... + /// + /// + public void IsGuid (params Guid[] values) { + // + foreach (var s in values) { + // + if (s.IsNull () || s.IsDefaultGuid ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// Check a Value is an MD5 ... + /// + /// + public void IsMD5 (params string[] values) { + // + foreach (var s in values) { + // + if (!s.IsMD5String ()) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// non zero childs + /// + /// + public void NotZeroChilds ( + params IEnumerable[] values) { + // + foreach (var s in values) { + // + if (s == null || + s.Count () == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// non zero childs + /// + /// + public void NotZeroChilds ( + params ICollection[] values) { + // + foreach (var s in values) { + // + if (s == null || + s.Count == 0) { + XException.InvalidArgs.Throw (); + } + } + } + + /// + /// given string must be a valid URL ... + /// + /// + /// + public void Url ( + string url, + Exception exception = null + ) { + // + NotEmpty (url); + + // + if (exception == null) { + exception = XException.InavlidUrl.ToException (); + } + + // + if (!url.IsValidUrl ()) { + throw exception; + } + } + + /// + /// given string must be a valid Email Address ... + /// + /// + /// + public void EmailAddress ( + string emailAdress, + Exception exception = null + ) { + // + NotEmpty (emailAdress); + + // + if (exception == null) { + exception = XException.InvalidEmailAddress.ToException (); + } + + // + if (!emailAdress.IsValidEmail ()) { + throw exception; + } + } + + /// + /// given string must be a valid Mobile Number ... + /// + /// + /// + public void MobileNumber ( + string mobileNumber, + Exception exception = null + ) { + // + NotEmpty (mobileNumber); + + // + if (exception == null) { + exception = XException.InvalidMobileNumber.ToException (); + } + + // + if (!mobileNumber.IsValidMobileNumber ()) { + throw exception; + } + } + #endregion + + // + #region Group Validatos ... + public XValidationProvider GroupValidationBuilder () { + // + validationGroup = new List (); + + // + return this; + } + + public XValidationProvider AddNotEmpty (params string[] values) { + // + this.validationGroup.Add (() => + this.NotEmpty (values) + ); + + // + return this; + } + + public XValidationProvider AddNotNull (params object[] values) { + // + this.validationGroup.Add (() => + this.NotNull (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZero (params int[] values) { + // + this.validationGroup.Add (() => + this.NotZero (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZero (params long[] values) { + // + this.validationGroup.Add (() => + this.NotZero (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZero (params decimal[] values) { + // + this.validationGroup.Add (() => + this.NotZero (values) + ); + + // + return this; + } + + public XValidationProvider AddIsGuid (params string[] values) { + // + this.validationGroup.Add (() => + this.IsGuid (values) + ); + + // + return this; + } + + public XValidationProvider AddIsGuid (params Guid[] values) { + // + this.validationGroup.Add (() => + this.IsGuid (values) + ); + + // + return this; + } + + public XValidationProvider AddIsMD5 (params string[] values) { + // + this.validationGroup.Add (() => + this.IsMD5 (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZeroChilds (params IEnumerable[] values) { + // + this.validationGroup.Add (() => + this.NotZeroChilds (values) + ); + + // + return this; + } + + public XValidationProvider AddNotZeroChilds (params ICollection[] values) { + // + this.validationGroup.Add (() => + this.NotZeroChilds (values) + ); + + // + return this; + } + + public XValidationProvider AddEmailAddress ( + string emailAdress, + Exception exception = null) { + // + this.validationGroup.Add (() => + this.EmailAddress (emailAdress, exception) + ); + + // + return this; + } + + public XValidationProvider AddMobileNumber ( + string mobileNumber, + Exception exception = null) { + // + this.validationGroup.Add (() => + this.MobileNumber (mobileNumber, exception) + ); + + // + return this; + } + + public XValidationProvider AddUrl ( + string url, + Exception exception = null) { + // + this.validationGroup.Add (() => + this.Url (url, exception) + ); + + // + return this; + } + + public void ValidateGroup () { + // + // Run each Tasks ... + foreach (var action in validationGroup) { + action.Invoke (); + } + + // + // Clear Task List ... + validationGroup.Clear (); + } + + public async Task ValidateGroupAsync () { + // + // Run each Tasks ... + foreach (var action in validationGroup) { + await Task.Run (action); + } + + // + // Clear Task List ... + validationGroup.Clear (); + } + #endregion + + } +} diff --git a/Documents/GeneratedContext/xDashboard-xIdentityHelper-20261009_203557.txt b/Documents/GeneratedContext/xDashboard-xIdentityHelper-20261009_203557.txt new file mode 100644 index 0000000..93eaa64 --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xIdentityHelper-20261009_203557.txt @@ -0,0 +1,1196 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityHelper\XApiScopeHelper.cs + +using xExceptions.Attributes; + +namespace xIdentityHelper { + public static class XApiScopeHelper { + public partial struct XApiScopes { + public const string Read = "XAPI.read"; + public const string Write = "XAPI.write"; + public const string Admin = "XAPI.admin"; + public const string Manage = "manage"; + } + + public enum XApiScope { + [StringValue ("XAPI.read")] + Read, + + [StringValue ("XAPI.write")] + Write, + + [StringValue ("XAPI.admin")] + Admin, + + [StringValue ("manage")] + Manage, + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityHelper\XAuthorizationHelper.cs + +using IdentityModel; +using xCommons.Constants; +using xCommons.Extensions; +using xCommons.Authorization; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using static xIdentityHelper.XApiScopeHelper; +using xIdentityModels.Constants; + +namespace xIdentityHelper +{ + public class XAuthorizationHelper + { + public static IDictionary GetXAuthorizationPolicies() + { + // + var result = new Dictionary(); + + // + RequiredRolesRequirement roleRequirements = null; + + // + #region Build Policies ... + // + // Scope Policies ... + #region Scope Policies ... + // + // Read Scope ... + result.Add(XPolicies.ReadAccess, + new AuthorizationPolicyBuilder() + .RequireClaim(JwtClaimTypes.Scope, XApiScopes.Read) + .Build() + ); + + // + // Write Scope ... + result.Add(XPolicies.WriteAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiScopes.Read, + XApiScopes.Write + ) + .Build() + ); + + // + // Admin Scope ... + result.Add(XPolicies.AdminAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiScopes.Read, + XApiScopes.Write, + XApiScopes.Admin + ) + .Build() + ); + + // + // Manage Scope ... + result.Add(XPolicies.ManageAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiScopes.Read, + XApiScopes.Write, + XApiScopes.Admin, + XApiScopes.Manage + ) + .Build() + ); + #endregion + + // + // Roles Policies ... + #region Roles Policies ... + // + // User ... + #region User Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.USER, + XUserRoles.AGENT, + XUserRoles.REPORTER_ASSISTANT, + XUserRoles.REPORTER, + XUserRoles.ACTUARY_ASSISTANT, + XUserRoles.ACTUARY, + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // User Policy ... + result.Add(XPolicies.User, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled User Policy ... + result.Add(XPolicies.EnabledUser, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // Agent ... + #region Agent Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.AGENT, + XUserRoles.REPORTER_ASSISTANT, + XUserRoles.REPORTER, + XUserRoles.ACTUARY_ASSISTANT, + XUserRoles.ACTUARY, + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // Agent Policy ... + result.Add(XPolicies.Agent, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled Agent Policy ... + result.Add(XPolicies.EnabledAgent, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // ReporterAssistant ... + #region Reporter Assistant Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.REPORTER_ASSISTANT, + XUserRoles.REPORTER, + XUserRoles.ACTUARY_ASSISTANT, + XUserRoles.ACTUARY, + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // ReporterAssistant ... + result.Add(XPolicies.ReporterAssistant, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled ReporterAssistant ... + result.Add(XPolicies.EnabledReporterAssistant, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // Reporter ... + #region Reporter Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.REPORTER, + XUserRoles.ACTUARY_ASSISTANT, + XUserRoles.ACTUARY, + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // Reporter ... + result.Add(XPolicies.Reporter, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled Reporter ... + result.Add(XPolicies.EnabledReporter, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // ActuaryAssistant ... + #region Actuary Assistant Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.ACTUARY_ASSISTANT, + XUserRoles.ACTUARY, + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // ActuaryAssistant ... + result.Add(XPolicies.ActuaryAssistant, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled ActuaryAssistant ... + result.Add(XPolicies.EnabledActuaryAssistant, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // Actuary ... + #region Actuary Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.ACTUARY, + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // Actuary ... + result.Add(XPolicies.Actuary, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled Actuary ... + result.Add(XPolicies.EnabledActuary, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // ChiefClerkAssistant ... + #region Chief Clerk Assistant Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // ChiefClerkAssistant ... + result.Add(XPolicies.ChiefClerkAssistant, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled ChiefClerkAssistant ... + result.Add(XPolicies.EnabledChiefClerkAssistant, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // ChiefClerk ... + #region Chief Clerk Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.CHIEF_CLERK, + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // ChiefClerk ... + result.Add(XPolicies.ChiefClerk, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled ChiefClerk ... + result.Add(XPolicies.EnabledChiefClerk, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // Journalist ... + #region Journalist Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.JOURNALIST, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // Journalist ... + result.Add(XPolicies.Journalist, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled Journalist ... + result.Add(XPolicies.EnabledJournalist, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // ResponsibleManager ... + #region Responsible Manager Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.ADMIN + ); + + // + // ResponsibleManager ... + result.Add(XPolicies.ResponsibleManager, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled ResponsibleManager ... + result.Add(XPolicies.EnabledResponsibleManager, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // Admin ... + #region Admin Related Policies ... + // + roleRequirements = new RequiredRolesRequirement( + XUserRoles.ADMIN + ); + + // + // Admin Policy ... + result.Add(XPolicies.Admin, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .Build() + ); + + // + // Enabled Admin Policy ... + result.Add(XPolicies.EnabledAdmin, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(roleRequirements) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + #endregion + + // + // Complex Policies ... + #region AdminOrAgent Related Policies ... + // + // Admin Or Agent Policy ... + result.Add(XPolicies.AdminOrAgent, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(new RequiredRolesRequirement( + XUserRoles.ADMIN, + XUserRoles.AGENT + )) + .Build() + ); + + // + // Enabled Admin Or Agent Policy ... + result.Add(XPolicies.EnabledAdminOrAgent, + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddRequirements(new RequiredRolesRequirement( + XUserRoles.ADMIN, + XUserRoles.AGENT + )) + .RequireClaim( + XCustomClaims.IsBanned, + false.ToString(), + false.ToString() + .ToNormalString() + ) + .RequireClaim( + XCustomClaims.IsEnabled, + true.ToString(), + true.ToString() + .ToNormalString() + ) + .Build() + ); + #endregion + + // + // Apik Key Policies ... + #region ApiKey Policies ... + // + // Claims Requirements ... + + // + // ApiKey Access Policy ... + result.Add(XPolicies.ApiKeyAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiKeyScope.Read.GetStringValue(), + XApiKeyScope.Write.GetStringValue(), + XApiKeyScope.Manage.GetStringValue() + ) + .Build() + ); + + // + // ApiKey Read Policy ... + result.Add(XPolicies.ApiKeyReadAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiKeyScope.Read.GetStringValue() + ) + .Build() + ); + + // + // ApiKey Write Policy ... + result.Add(XPolicies.ApiKeyWriteAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiKeyScope.Write.GetStringValue() + ) + .Build() + ); + + // + // ApiKey Manage Policy ... + result.Add(XPolicies.ApiKeyManageAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiKeyScope.Manage.GetStringValue() + ) + .Build() + ); + + // + // ApiKey Admin Policy ... + result.Add(XPolicies.ApiKeyAdminAccess, + new AuthorizationPolicyBuilder() + .RequireClaim( + JwtClaimTypes.Scope, + XApiKeyScope.Admin.GetStringValue() + ) + .Build() + ); + #endregion + #endregion + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityHelper\XPolicies.cs + +namespace xIdentityHelper +{ + public partial struct XPolicies + { + // + // Scopes ... + public const string ReadAccess = "readScopePolicy"; + public const string WriteAccess = "writeScopePolicy"; + public const string AdminAccess = "adminScopePolicy"; + public const string ManageAccess = "manageScopePolicy"; + // + // Api Keys ... + public const string ApiKeyAccess = "apiKeyAccessPolicy"; + public const string ApiKeyReadAccess = "apiKeyReadAccessPolicy"; + public const string ApiKeyWriteAccess = "apiKeyWriteAccessPolicy"; + public const string ApiKeyAdminAccess = "apiKeyAdminAccessPolicy"; + public const string ApiKeyManageAccess = "apiKeyManageAccessPolicy"; + // + // Admin ... + public const string Admin = "adminPolicy"; + public const string EnabledAdmin = "enabledAdminPolicy"; + // + // Responsible Manager ... + public const string ResponsibleManager = "responsibleManagerPolicy"; + public const string EnabledResponsibleManager = "enabledResponsibleManagerPolicy"; + // + // Journalist ... + public const string Journalist = "journalistPolicy"; + public const string EnabledJournalist = "enabledJournalistPolicy"; + // + // Chief Clerk ... + public const string ChiefClerk = "chiefClerkPolicy"; + public const string EnabledChiefClerk = "enabledChiefClerkPolicy"; + // + // Chief Clerk Assistant ... + public const string ChiefClerkAssistant = "chiefClerkAssistantPolicy"; + public const string EnabledChiefClerkAssistant = "enabledChiefClerkAssistantPolicy"; + // + // Actuary ... + public const string Actuary = "actuaryPolicy"; + public const string EnabledActuary = "enabledActuaryPolicy"; + // + // Actuary Assistant ... + public const string ActuaryAssistant = "actuaryAssistantPolicy"; + public const string EnabledActuaryAssistant = "enabledActuaryAssistantPolicy"; + // + // Reporter ... + public const string Reporter = "reporterPolicy"; + public const string EnabledReporter = "enabledReporterPolicy"; + // + // Reporter Assistant ... + public const string ReporterAssistant = "reporterAssistantPolicy"; + public const string EnabledReporterAssistant = "enabledReporterAssistantPolicy"; + // + // Agent ... + public const string Agent = "agentPolicy"; + public const string EnabledAgent = "enabledAgentPolicy"; + // + // User ... + public const string User = "userPolicy"; + public const string EnabledUser = "enabledUserPolicy"; + // + // Complex Policies ... + public const string AdminOrAgent = "adminOrAgentPolicy"; + public const string EnabledAdminOrAgent = "enabledAdminOrAgentPolicy"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityHelper\XUserRoles.cs + +using xExceptions.Attributes; +using System.Collections.Generic; + +namespace xIdentityHelper +{ + public partial struct XUserRoles + { + /// + /// Must Top Role of System ... + /// + public const string ADMIN = "admin"; + + /// + /// مدیر مسئول + /// + public const string RESPONSIBLE_MANAGER = "responsible_manager"; + + /// + /// روزنامه نگار + /// + public const string JOURNALIST = "journalist"; + + /// + /// سر دبیر + /// + public const string CHIEF_CLERK = "chief_clerk"; + + /// + /// دستیار سرد بیر + /// + public const string CHIEF_CLERK_ASSISTANT = "chief_clerk_assistant"; + + /// + /// دبیر + /// + public const string ACTUARY = "actuary"; + + /// + /// دستیار دبیر + /// + public const string ACTUARY_ASSISTANT = "actuary_assistant"; + + /// + /// خبرنگار + /// + public const string REPORTER = "reporter"; + + /// + /// دستیار خبرنگار + /// + public const string REPORTER_ASSISTANT = "reporter_assistant"; + + /// + /// دستیار + /// + public const string AGENT = "agent"; + + /// + /// کاربر + /// + public const string USER = "user"; + } + + public enum XUserRole + { + /// + /// Must Top Role of System ... + /// + [StringValue("admin")] + Admin = 10, + + /// + /// مدیر مسئول + /// + [StringValue("responsible_manager")] + ResponsibleManager = 9, + + /// + /// روزنامه نگار + /// + [StringValue("journalist")] + Journalist = 8, + + /// + /// سر دبیر + /// + [StringValue("chief_clerk")] + ChiefClerk = 7, + + /// + /// دستیار سرد بیر + /// + [StringValue("chief_clerk_assistant")] + ChiefClerkAssistant = 6, + + /// + /// دبیر + /// + [StringValue("actuary")] + Actuary = 5, + + /// + /// دستیار دبیر + /// + [StringValue("actuary_assistant")] + ActuaryAssistant = 4, + + /// + /// خبرنگار + /// + [StringValue("reporter")] + Reporter = 3, + + /// + /// دستیار خبرنگار + /// + [StringValue("reporter_assistant")] + ReporterAssistant = 2, + + /// + /// دستیار + /// + [StringValue("agent")] + Agent = 1, + + /// + /// کاربر + /// + [StringValue("user")] + User = 0 + } + + public static class XUserRoleHelper + { + public static List GetAllRoles() + { + // + var result = new List + { + XUserRoles.ADMIN, + XUserRoles.RESPONSIBLE_MANAGER, + XUserRoles.JOURNALIST, + XUserRoles.CHIEF_CLERK, + XUserRoles.CHIEF_CLERK_ASSISTANT, + XUserRoles.ACTUARY, + XUserRoles.ACTUARY_ASSISTANT, + XUserRoles.REPORTER, + XUserRoles.REPORTER_ASSISTANT, + XUserRoles.AGENT, + XUserRoles.USER + }; + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityHelper\Extensions\XIdentityHelperExtensions.cs + +using System; +using System.Linq; +using xCommons.Extensions; +using xIdentityModels.Models; + +namespace xIdentityHelper.Extensions +{ + public static class XIdentityHelperExtensions + { + /// + /// Check User Claims ia Admin Role or not ... + /// + /// + /// + public static bool IsAdmin(this XUserClaimsInfoDto source) + { + // + var result = false; + + // + if (source.IsNull() || + source.Roles.IsNull() || + !source.Roles.HasChild()) + { + return result; + } + + // + result = source.Roles.Contains(XUserRoles.ADMIN); + + // + return result; + } + + /// + /// Check Actor is Valid for UserSelectByParam or not ... + /// + /// + /// + /// + public static bool IsValidActor(this XUserClaimsInfoDto source, string userSelectByParam) + { + // + var result = false; + + // + if (source.IsNull() || + userSelectByParam.IsNullOrEmpty()) + { + return result; + } + + // + bool isAdmin = source.IsAdmin(); + result = isAdmin || + userSelectByParam == source.Email || + userSelectByParam == source.UserId || + userSelectByParam == source.UserName || + userSelectByParam == source.PhoneNumber; + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityHelper\Extensions\XUserRoleExtensions.cs + +using System; +using System.Linq; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Collections.Generic; + +namespace xIdentityHelper.Extensions +{ + public static class XUserRoleExtensions + { + /// + /// Converts an String to XUserRole ... + /// + /// + /// + public static XUserRole ToXUserRole(this string role) + { + // + var result = XUserRole.User; + + // + // Validate ... + if (role.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + var roles = new List + { + role + }; + result = roles.GetTopRole(); + + // + return result; + } + + /// + /// Retrieve User Top Role based on Role Lists ... + /// + /// + /// + public static XUserRole GetTopRole(this IEnumerable roles) + { + // + // Validate ... + if (roles.IsNull() || !roles.HasChild()) + { + XException.InvalidArgs.Throw(); + } + + // + // Normalize Roles ... + roles = roles.Select(r => r.ToNormalString()); + + // + var result = XUserRole.User; + + // + // Admin ... + if (roles.Contains(XUserRole.Admin.GetStringValue().ToNormalString())) + { + result = XUserRole.Admin; + } + // + // ResponsibleManager ... + else if (roles.Contains(XUserRole.ResponsibleManager.GetStringValue().ToNormalString())) + { + result = XUserRole.ResponsibleManager; + } + // + // Journalist ... + else if (roles.Contains(XUserRole.Journalist.GetStringValue().ToNormalString())) + { + result = XUserRole.Journalist; + } + // + // ChiefClerk ... + else if (roles.Contains(XUserRole.ChiefClerk.GetStringValue().ToNormalString())) + { + result = XUserRole.ChiefClerk; + } + // + // ChiefClerkAssistant ... + else if (roles.Contains(XUserRole.ChiefClerkAssistant.GetStringValue().ToNormalString())) + { + result = XUserRole.ChiefClerkAssistant; + } + // + // Actuary ... + else if (roles.Contains(XUserRole.Actuary.GetStringValue().ToNormalString())) + { + result = XUserRole.Actuary; + } + // + // ActuaryAssistant ... + else if (roles.Contains(XUserRole.ActuaryAssistant.GetStringValue().ToNormalString())) + { + result = XUserRole.ActuaryAssistant; + } + // + // Reporter ... + else if (roles.Contains(XUserRole.Reporter.GetStringValue().ToNormalString())) + { + result = XUserRole.Reporter; + } + // + // ReporterAssistant ... + else if (roles.Contains(XUserRole.ReporterAssistant.GetStringValue().ToNormalString())) + { + result = XUserRole.ReporterAssistant; + } + // + // Agent ... + else if (roles.Contains(XUserRole.Agent.GetStringValue().ToNormalString())) + { + result = XUserRole.Agent; + } + // + // User ... + else if (roles.Contains(XUserRole.User.GetStringValue().ToNormalString())) + { + result = XUserRole.User; + } + else + { + XException.InvalidData.Throw(); + } + + // + return result; + } + + /// + /// Check User Roles has Specified Permissions or not ... + /// + /// + /// + /// + public static bool HasRolePermissions(this IEnumerable roles, XUserRole role) + { + // + // Validate ... + var result = + !roles.IsNull() && + roles.HasChild(); + if (!result) + { + return result; + } + + // + try + { + // + var topRole = roles.GetTopRole(); + + // + result = (int)topRole >= (int)role; + } + catch + { + result = false; + } + + // + return result; + } + public static bool HasRolePermissions(this IEnumerable roles, string role) + { + // + // Validate ... + var result = + !roles.IsNull() && + roles.HasChild() && + !role.IsNullOrEmpty(); + if (!result) + { + return result; + } + + // + var roleEnum = role.ToXUserRole(); + result = roles.HasRolePermissions(roleEnum); + + // + return result; + } + } +} diff --git a/Documents/GeneratedContext/xDashboard-xIdentityModels-20261009_203602.txt b/Documents/GeneratedContext/xDashboard-xIdentityModels-20261009_203602.txt new file mode 100644 index 0000000..1e8a7da --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xIdentityModels-20261009_203602.txt @@ -0,0 +1,3807 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\XApiToken.cs + +using xModels.Base; +using xCommons.Extensions; +using xIdentityModels.Models; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels { + public class XApiToken : XBaseIntIDEntity { + public string Content { get; set; } + + [NotMapped] + public XTokenResponse Tokens { + get { return Content.IsNullOrEmpty () ? null : Content.FromJSON (); } + set { Content = value.ToJSON (); } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\XUser.cs + +using System; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityModels.Navigations; +using Microsoft.AspNetCore.Identity; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels +{ + /// + /// represnt a User of System + /// + public partial class XUser : IdentityUser + { + // + #region Properties ... + /// + /// FirstName + /// + /// user's FirstName + [Required] + [StringLength(50)] + public string FirstName { get; set; } + /// + /// LastName + /// + /// user's LastName + [Required] + [StringLength(100)] + public string LastName { get; set; } + /// + /// User is Enabled or not + /// + /// + /// system was restrict Disabled Users access to Many Resources + /// + /// boolean value represent User is Enable or not + public bool IsEnable { get; set; } + /// + /// User is Banned or not + /// + /// + /// system was reject Banned Users access to Almost all Resources + /// + /// boolean value represent User is Banned or not + public bool IsBanned { get; set; } + /// + /// DateOfBirth + /// + /// user's DateOfBirth + [Required] + public DateTime DateOfBirth { get; set; } + /// + /// CreationDate + /// + /// user's Creation/Registration Date + [Required] + public DateTime CreationDate { get; set; } + /// + /// LastLogin + /// + /// user's Last Successfull Login Date + public DateTime? LastLogin { get; set; } + /// + /// ProfileImage/Avatar + /// + /// user's current Avatar + public string Avatar { get; set; } + /// + /// Gender + /// + /// user's Gender + [Required] + public XGender Gender { get; set; } + /// + /// User Biography ... + /// + /// + public string Bio { get; set; } + /// + /// User Bio Cover Image ... + /// + /// + public string CoverImage { get; set; } + /// + /// Can Discover User ... + /// + /// + public bool OpenToSearch { get; set; } + #endregion + + // + #region Navigation Properties ... + /// + /// User's Devices + /// + /// XDevice + /// a Collection of all assigned Devices to User + [InverseProperty("User")] + public virtual ICollection Devices { get; set; } = new HashSet(); + /// + /// User's Avatars + /// + /// XProfileImage + /// a Collection of all assigned Avatars to User + [InverseProperty("User")] + public virtual ICollection Avatars { get; set; } = new HashSet(); + /// + /// User's Followers + /// + /// XFriendshipFollower + /// a Collection of all User's Followers + [InverseProperty("User")] + public virtual ICollection Followers { get; set; } = new HashSet(); + /// + /// User's Followings + /// + /// XFriendshipFollowing + /// a Collection of all User's Followings + [InverseProperty("User")] + public virtual ICollection Followings { get; set; } = new HashSet(); + /// + /// User's Roles + /// + /// a Collection of all User's Roles in System + public virtual ICollection> Roles { get; set; } = new HashSet>(); + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Configurations\XIdentityConfiguration.cs + +using xIdentityModels.Dtos; +using System.Collections.Generic; + +namespace xIdentityModels.Configurations { + public class XIdentityConfiguration { + // + // Define All Policies which can Apply to Identity Framework + // and also create a Default instance if it's not Exists + // in AppSetting JSON file ... + public XIdentityPolicy Policy { get; set; } + + /// + /// the Security Key which used to Signing JWT Tokens + /// in Authentication Provider + /// + /// + public string IdentitySecretKey { get; set; } + + /// + /// the Issuer and Creator of Token + /// + /// + public string IdentityIssuer { get; set; } + + /// + /// the Audience and Consumer of Token + /// + /// + public string IdentityAudience { get; set; } + + /// + /// the List of Users Roles which must Provides in Identity + /// + /// + public HashSet IdentityRoles { get; set; } + + /// + /// determines All new users must Assign to which role in Registration + /// + /// + public string NewUsersRole { get; set; } + + /// + /// determines New Registered Users Confirm Email or not + /// + /// + public bool AutoConfirmNewUsersEmail { get; set; } + + /// + /// determines New Registered Users Confirm PhoneNumber or not + /// + /// + public bool AutoConfirmNewUsersPhoneNumber { get; set; } + + /// + /// determines the Registration only Can be done with Invitation or not + /// + /// + public bool RegistrationJustWithInvite { get; set; } + + /// + /// determines the Registration required confirmation with email or not + /// + /// + public bool RequireRegistrationConfirm { get; set; } + + // + public int MaxNumberOfVerificationCodeSend { get; set; } + + // + public int DeleyBetweenTwoVerificationCode { get; set; } + + public int BannedDeviceTimeout { get; set; } + + /// + /// an string Which Provide how long a action token can be valid + /// + /// + public string ActionTokenExpirationDateProvider { get; set; } + + public HashSet IdentityMessages { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Configurations\XIdentityPolicy.cs + +namespace xIdentityModels.Configurations { + /// + /// Provide the Available Policies with their Default Values + /// + public class XIdentityPolicy { + /// + /// User Account Lock Policies + /// + /// + public XIdentityPolicyLockout Lockout { get; set; } + + /// + /// User Password Policies + /// + /// + public XIdentityPolicyPassword Password { get; set; } + + /// + /// the Policies for User for Allowing to SignIn + /// + /// + public XIdentityPolicySignIn SignIn { get; set; } + + /// + /// the Policies for User + /// + /// + public XIdentityPolicyUser User { get; set; } + + /// + /// the policies which determines how to + /// populate user profile + /// + /// + public XIdentityProfile Profile { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Configurations\XIdentityPolicyLockout.cs + +namespace xIdentityModels.Configurations { + /// + /// User Account Lock Policies + /// + public class XIdentityPolicyLockout { + /// + /// The String which Provide Time Span Delais + /// between Lockout and UnLock a User Account + /// this Provided String the Parsed before Use + /// + /// + public string LockoutTimeSpanProvider { get; set; } + + /// + /// the Number of InvalidLoginAttemps which can + /// Happens to Lockout User Account + /// + /// + public byte MaxFailedAccessAttempts { get; set; } + + /// + /// determines Lockout mechanism allowed for new Users or not + /// + /// + public bool AllowedForNewUsers { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Configurations\XIdentityPolicyPassword.cs + +namespace xIdentityModels.Configurations { + /// + /// User Password Policies + /// + public class XIdentityPolicyPassword { + /// + /// determines Password must contains digits or not + /// + /// + public bool RequireDigit { get; set; } + + /// + /// determines the minimum allowed length for a password + /// + /// + public byte RequiredLength { get; set; } + + /// + /// determines the number of Unique Chars must be included in a Password + /// + /// + public byte RequiredUniqueChars { get; set; } + + /// + /// determines Password must contains LowerCase chars or not + /// + /// + public bool RequireLowercase { get; set; } + + /// + /// determines Password must contains Non-Alphabetical chars or not + /// + /// + public bool RequireNonAlphanumeric { get; set; } + + /// + /// determines Password must contains UpperCase chars or not + /// + /// + public bool RequireUppercase { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Configurations\XIdentityPolicySignIn.cs + +namespace xIdentityModels.Configurations { + /// + /// the Policies for User for Allowing to SignIn + /// + public class XIdentityPolicySignIn { + /// + /// determines only enabled users can SignIn + /// + /// + public bool RequiredEnabled { get; set; } + + /// + /// determines User must Confirm the given Email Address + /// before Login or not + /// + /// + public bool RequireConfirmedEmail { get; set; } + + /// + /// determines User must Confirm the given Phone Number + /// before Login or not + /// + /// + public bool RequireConfirmedPhoneNumber { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Configurations\XIdentityPolicyUser.cs + +using System.Collections.Generic; + +namespace xIdentityModels.Configurations { + /// + /// the Policies for User + /// + public class XIdentityPolicyUser { + /// + /// determines Allowed characters which can used in UserName + /// + /// + public string AllowedUserNameCharacters { get; set; } + + /// + /// determines the Email Address of each User must be Unique or not + /// + /// + public bool RequireUniqueEmail { get; set; } + + /// + /// minimum length of User Name + /// + /// + public byte MinLength { get; set; } + + /// + /// maximum length of user name + /// + /// + public byte MaxLength { get; set; } + + /// + /// Minimum Age for Registration + /// + /// + public int MinAgeForRegistration { get; set; } + + /// + /// Maximum age for Registration + /// + /// + public int MaxAgeForRegistration { get; set; } + + /// + /// a collection of invalid usernames + /// + /// + public ICollection InvalidUserNames { get; set; } = new HashSet (); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Configurations\XIdentityProfile.cs + +namespace xIdentityModels.Configurations { + /// + /// contains user profile configuration + /// + public class XIdentityProfile { + /// + /// determines user profile contain email or not + /// + /// + public bool ContainsEmail { get; set; } + + /// + /// determines user profile contains phone number or not + /// + /// + public bool ContainsPhoneNumber { get; set; } + + /// + /// determines user profile contains Roles collection or not + /// + /// + public bool ContainsRoles { get; set; } + + public bool ContainsDateOfBirth { get; set; } + + public bool ContainsLastLogin { get; set; } + + public bool ContainsCreationDate { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Constants\XAction.cs + +namespace xIdentityModels.Constants { + public enum XAction { + Authentication, + Invite, + Registration, + RequestMobileVerificationCode, + ConfirmMobileNumber, + RequestEmailVerificationCode, + ConfirmEmailAddress, + AddAccountInfo, + AttachProfileImage, + RequestResetPassword, + ProfileAction, + Finish, + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Constants\XApiKeyScope.cs + +using xExceptions.Attributes; + +namespace xIdentityModels.Constants +{ + public enum XApiKeyScope + { + [StringValue("read")] + Read, + + [StringValue("write")] + Write, + + [StringValue("admin")] + Admin, + + [StringValue("manage")] + Manage + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Constants\XApplicationType.cs + +namespace xIdentityModels.Constants +{ + public enum XApplicationType + { + Web, Mobile, Desktop, IoT, ThirdParty, Service + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Constants\XFriendshipState.cs + +namespace xIdentityModels.Constants { + /// + /// Represent State of Friendship between to Users + /// + public enum XFriendshipState { + None, + Pending, + Accepted, + Rejected, + Blocked, + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Constants\XGender.cs + +namespace xIdentityModels.Constants { + /// + /// Determines Available Genders + /// + public enum XGender { + Male, + Female + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Constants\xIdentityModelsConstants.cs + +namespace xIdentityModels.Constants { + public class xIdentityModelsConstants { } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Constants\XUserSelectBy.cs + +namespace xIdentityModels.Constants { + public enum XUserSelectBy { + NotSpecified, + ID, + Username, + Email, + MobileNumber + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Descriptors\XIdentityUserDescriptor.cs + +namespace xIdentityModels.Descriptors { + /// + /// contains default users information + /// + public class XIdentityUserDescriptor { + public string Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string UserName { get; set; } + public string Email { get; set; } + public bool EmailConfirmed { get; set; } + public string PhoneNumber { get; set; } + public bool PhoneNumberConfirmed { get; set; } + public string Password { get; set; } + public string Role { get; set; } + public bool IsEnable { get; set; } + public bool IsBanned { get; set; } + public string DateOfBirth { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\DI\XDIHelperExtension.cs + +namespace xIdentityModels.DI { + public static partial class XDIHelperExtension { } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XApiKeyDto.cs + +using System; +using xModels.Base; + +namespace xIdentityModels.Dtos +{ + /// + /// an Specified Api Key ... + /// + public class XApiKeyDto : XBaseGuidIDEntityDto + { + /// + /// Related Application Id ... + /// + public int ApplicationId { get; set; } + + /// + /// Hash ... + /// SHA256 of ApiKey ... + /// + public string KeyHash { get; set; } + + /// + /// a Prefix ... + /// + public string KeyPrefix { get; set; } + + /// + /// Expiration Type ... + /// + public DateTime ExpiresAt { get; set; } + + /// + /// Cration Time ... + /// + public DateTime CreatedOn { get; set; } + + /// + /// Last Usage Date ... + /// + public DateTime? LastUsedAt { get; set; } + + /// + /// Revoked Time ... + /// + public DateTime? RevokedAt { get; set; } + + /// + /// Revoker User Id ... + /// + public string RevokedBy { get; set; } + + /// + /// a Json Array String ... + /// XApiKeyScope ... + /// + public string AllowedScopes { get; set; } + + /// + /// a Comma-separated List which Specified White List IP ... + /// + public string AllowedIPs { get; set; } + + /// + /// Rate Limit Per Minute ... + /// + public int RateLimitPerMinute { get; set; } = 60; + + // + /// + /// Related Application ... + /// + public virtual XApplicationDto Application { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XApiKeyUsageDto.cs + +using System; +using xModels.Base; + +namespace xIdentityModels.Dtos +{ + public class XApiKeyUsageDto : XBaseLongIDEntityDto + { + /// + /// Api Key Id ... + /// + public Guid ApiKeyId { get; set; } + + /// + /// Called Endpoint ... + /// + public string Endpoint { get; set; } + + /// + /// Called Http Method ... + /// + public string HttpMethod { get; set; } + + /// + /// Client IP Address ... + /// + public string ClientIP { get; set; } + + /// + /// Status Code ... + /// + public int StatusCode { get; set; } + + /// + /// Request Time ... + /// + public DateTime RequestedOn { get; set; } + + /// + /// Response Time in Milliseconds ... + /// + public long ResponseTimeMs { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XApplicationDto.cs + +using System; +using xModels.Base; +using xModels.Dtos; +using xIdentityModels.Constants; + +namespace xIdentityModels.Dtos +{ + /// + /// an Application in xDashboard ... + /// + public class XApplicationDto : XBaseIntIDEntityDto + { + /// + /// Name ... + /// + public string Name { get; set; } + + /// + /// Descriptor ... + /// + public string Description { get; set; } + + /// + /// Owner Id ... + /// + public string OwnerId { get; set; } + public virtual XPersonDto Owner { get; set; } + + /// + /// Application Type ... + /// + public XApplicationType Type { get; set; } + + /// + /// Specified Application is Active or not ... + /// + public bool IsActive { get; set; } = true; + + /// + /// Application Create Time ... + /// + public DateTime CreatedOn { get; set; } + + /// + /// Application Update Type ... + /// + public DateTime UpdatedAt { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XDiscoveryDocumentDto.cs + +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace xIdentityModels.Dtos +{ + public class XDiscoveryDocumentDto + { + [JsonProperty ("issuer")] + public string Issuer { get; set; } + + [JsonProperty ("jwks_uri")] + public string JwksUri { get; set; } + + [JsonProperty ("authorization_endpoint")] + public string AuthorizationEndpoint { get; set; } + + [JsonProperty ("token_endpoint")] + public string TokenEndpoint { get; set; } + + [JsonProperty ("userinfo_endpoint")] + public string UserinfoEndpoint { get; set; } + + [JsonProperty ("end_session_endpoint")] + public string EndSessionEndpoint { get; set; } + + [JsonProperty ("check_session_iframe")] + public string CheckSessionIframe { get; set; } + + [JsonProperty ("revocation_endpoint")] + public string RevocationEndpoint { get; set; } + + [JsonProperty ("introspection_endpoint")] + public string IntrospectionEndpoint { get; set; } + + [JsonProperty ("device_authorization_endpoint")] + public string DeviceAuthorizationEndpoint { get; set; } + + [JsonProperty ("frontchannel_logout_supported")] + public bool FrontchannelLogoutSupported { get; set; } + + [JsonProperty ("frontchannel_logout_session_supported")] + public bool FrontchannelLogoutSessionSupported { get; set; } + + [JsonProperty ("backchannel_logout_supported")] + public bool BackchannelLogoutSupported { get; set; } + + [JsonProperty ("backchannel_logout_session_supported")] + public bool BackchannelLogoutSessionSupported { get; set; } + + [JsonProperty ("scopes_supported")] + public List ScopesSupported { get; set; } + + [JsonProperty ("claims_supported")] + public List ClaimsSupported { get; set; } + + [JsonProperty ("grant_types_supported")] + public List GrantTypesSupported { get; set; } + + [JsonProperty ("response_types_supported")] + public List ResponseTypesSupported { get; set; } + + [JsonProperty ("response_modes_supported")] + public List ResponseModesSupported { get; set; } + + [JsonProperty ("token_endpoint_auth_methods_supported")] + public List TokenEndpointAuthMethodsSupported { get; set; } + + [JsonProperty ("id_token_signing_alg_values_supported")] + public List IdTokenSigningAlgValuesSupported { get; set; } + + [JsonProperty ("subject_types_supported")] + public List SubjectTypesSupported { get; set; } + + [JsonProperty ("code_challenge_methods_supported")] + public List CodeChallengeMethodsSupported { get; set; } + + [JsonProperty ("request_parameter_supported")] + public bool RequestParameterSupported { get; set; } + + [JsonProperty ("account_api")] + public string AccountApi { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XFriendDto.cs + +using xModels.Base; +using xIdentityModels.Constants; + +namespace xIdentityModels.Dtos +{ + /// + /// All Available Friendship Types ... + /// + public enum XFriendshipType + { + Follower = 0, + Following = 1, + } + + /// + /// a DTO Class Which Represent a User Friends ... + /// + public class XFriendDto : XBaseDto + { + public string Avatar { get; set; } + public string UserId { get; set; } + public string Username { get; set; } + public string Lastname { get; set; } + public string Firstname { get; set; } + public XFriendshipType Type { get; set; } + public XFriendshipState State { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XFriendshipInfoDto.cs + +using xModels.Base; +using xIdentityModels.Constants; + +namespace xIdentityModels.Dtos { + public class XFriendshipInfoDto : XBaseDto { + public string UserId { get; set; } + public bool IsFollower { get; set; } + public bool IsFollowing { get; set; } + public XFriendshipState FollowerState { get; set; } + public XFriendshipState FollowingState { get; set; } + public long Followers { get; set; } + public long Followings { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XIdentityMessageDto.cs + +using xModels.Base; + +namespace xIdentityModels.Dtos { + public class XIdentityMessageDto : XBaseDto { + public string Language { get; set; } + public string ResourceTitle { get; set; } + public string TranslatedValue { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XOpenActionDto.cs + +using System; +using xModels.Base; +using xModels.Dtos; +using xCommons.Extensions; + +namespace xIdentityModels.Dtos +{ + /// + /// a List of Available Open Actions ... + /// + public struct XOpenActions + { + public const string HandShake = "HandShake"; + public const string GetUserInfo = "GetUserInfo"; + public const string GetUserInfos = "GetUserInfos"; + } + + /// + /// Open Actions Request Model ... + /// + public class XOpenActionRequestDto : XBaseDto + { + // + public string Action { get; set; } + public string Payload { get; set; } + public string Checksum { get; set; } + public XDeviceDto Device { get; set; } + public string Timestamp { get; set; } + + /// + /// Validate Model ... + /// + /// + public bool Validate() + { + // + var result = + !Device.IsNull() && + !Timestamp.IsNull() && + !Action.IsNullOrEmpty() && + (Action == XOpenActions.HandShake || + Action == XOpenActions.GetUserInfo || + Action == XOpenActions.GetUserInfos) && + !Int64.Parse(Timestamp).FromTimestamp().IsExPired(); + + // + return result; + } + + /// + /// Checksum Digest Model ... + /// + /// + public string ToOpenActionToken() + { + // + var result = string.Empty; + if (!Validate()) + { + return result; + } + + // + var tmp = this; + tmp.Checksum = string.Empty; + + // + result = $"{tmp.ToJSON()}" + .ToMd5String(); + return result; + } + + /// + /// Validate a Token is Correspond to Model or not ... + /// + /// + /// + public bool ValidateToken(string token) + { + // + var result = + Validate() && + !token.IsNullOrEmpty() && + token == ToOpenActionToken(); + + // + return result; + } + } + + /// + /// aa Dto which used between Api's ... + /// + public class XOpenActionInnerDto : XBaseDto + { + public string Token { get; set; } + public string Payload { get; set; } + } + + /// + /// User Info Providing in Open Actions ... + /// + public class XOpenActionUserInfoDto : XBaseDto + { + public string Username { get; set; } + public string Firstname { get; set; } + public string Lastname { get; set; } + public string Avatar { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XProfileImageDto.cs + +using System; +using xModels.Base; +using Newtonsoft.Json; + +namespace xIdentityModels.Dtos { + public class XProfileImageDto : XBaseDto { + public int Id { get; set; } + + [JsonProperty ( + "name", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + + [JsonProperty ( + "thumb", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Thumb { get; set; } + + [JsonProperty ( + "path", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Path { get; set; } + + [JsonProperty ( + "thumbPath", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string ThumbPath { get; set; } + + [JsonProperty ( + "creationDate", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime CreationDate { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XUserNameIdRequest.cs + +using xModels.Base; +using System.Collections.Generic; + +namespace xIdentityModels.Dtos { + public class XUserNameIdRequest : XBaseDto { + public IEnumerable Ids { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XUserNameIdResponse.cs + +using xModels.Base; + +namespace xIdentityModels.Dtos { + public class XUserNameIdResponse : XBaseDto { + public string Id { get; set; } + public string UserName { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Dtos\XUserProfileDto.cs + +using System; +using xModels.Base; +using Newtonsoft.Json; +using xIdentityModels.Constants; +using System.Collections.Generic; + +namespace xIdentityModels.Dtos { + public class XUserProfileDto : XBaseDto { + [JsonProperty ( + "userId", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string UserId { get; set; } + + [JsonProperty ( + "userName", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string UserName { get; set; } + + [JsonProperty ( + "email", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Email { get; set; } + + [JsonProperty ( + "emailConfirmed", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool EmailConfirmed { get; set; } + + [JsonProperty ( + "phoneNumber", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string PhoneNumber { get; set; } + + [JsonProperty ( + "phoneNumberConfirmed", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool PhoneNumberConfirmed { get; set; } + + [JsonProperty ( + "firstName", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string FirstName { get; set; } + + [JsonProperty ( + "lastName", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string LastName { get; set; } + + [JsonProperty ( + "dateOfBirth", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime? DateOfBirth { get; set; } + + [JsonProperty ( + "creationDate", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime? CreationDate { get; set; } + + [JsonProperty ( + "lastLogin", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime? LastLogin { get; set; } + + [JsonProperty ( + "avatar", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Avatar { get; set; } + + [JsonProperty ( + "gender", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public XGender Gender { get; set; } + + [JsonProperty ( + "bio", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Bio { get; set; } + + [JsonProperty ( + "coverImage", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string CoverImage { get; set; } + + [JsonProperty ( + "openToSearch", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool OpenToSearch { get; set; } + + [JsonProperty ( + "friendshipInfo", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public XFriendshipInfoDto FriendshipInfo { get; set; } + + [JsonProperty ( + "roles", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public ICollection Roles { get; set; } = new HashSet (); + + [JsonProperty ( + "avatars", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public ICollection Avatars { get; set; } = new HashSet (); + + [JsonProperty ( + "isEnable", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool IsEnable { get; set; } + + [JsonProperty ( + "isBanned", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public bool IsBanned { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Extensions\DIExtensions.cs + +using xCommons.Extensions; +using xIdentityModels.Providers; +using xIdentityModels.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace xIdentityModels.Extensions { + public static class DIExtensions { + /// + /// since registering IXUserRoleHelper need to add default instance if + /// user not provided we create this helper ... + /// + /// + /// + public static void AddXUserRoleHandler ( + this IServiceCollection services, + IXUserRoleHelper helper = null + ) { + // + // Check if Provider is null ... + if (helper.IsNull ()) { + helper = services.GetRegisteredService (); + } + + // + // check if Provider registered or not ... + if (helper.IsNull ()) { + // + // Create Default provider ... + services.AddSingleton (new XDefaultUserRoleHelper ()); + } else { + // + // Register Given Helper ... + services.AddSingleton (helper); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Extensions\XIdentityExtensions.cs + +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityModels.Models; +using xIdentityModels.Constants; +using System.Collections.Generic; + +namespace xIdentityModels.Extensions { + public static class XIdentityExtensions { + public static XUserSelectBy GetUserSelectByType ( + this string source, + bool forceUserSelectByParamNotEmpty = true, + bool forceUserSelectByTypeMustSpecified = false + ) { + // + // Validate Args ... + if (source.IsNullOrEmpty () && + forceUserSelectByParamNotEmpty) { + XException.InvalidArgs.Throw (); + } + + // + var result = XUserSelectBy.NotSpecified; + var userSelectByParam = source.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; + } + + // + // Validation Result ... + if (forceUserSelectByTypeMustSpecified && + result == XUserSelectBy.NotSpecified) { + XException.InvalidData.Throw (); + } + + // + return result; + } + + public static string GetUserSelectByParam ( + this XActionRequest source, + bool forceNotNull = true, + ICollection excludes = null + ) { + // + // Validate Args ... + if (source.IsNull ()) { + XException.InvalidArgs.Throw (); + } + + // + var id = source.UserId; + var userName = source.UserName; + var email = source.Email; + var phoneNumber = source.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; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Extensions\xIdentityModelsExtensions.cs + +using System; +using System.Linq; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityModels.Configurations; + +namespace xIdentityModels.Extensions +{ + public static class xIdentityModelsExtensions + { + /// + /// Converts a User Entity to Profile Dto ... + /// + /// + /// + /// + /// + /// + /// + /// + public static XUserProfileDto ToXProfileDto( + this XUser source, + bool isAdmin = false, + bool isSameUser = false, + IEnumerable roles = null, + XFriendshipInfoDto friendship = null, + XIdentityProfile profileConfig = null + ) + { + // + XUserProfileDto result = null; + + // + if (roles == null) + { + roles = new List(); + } + + // + // Prepare Dto ... + if (!source.IsNullOrDefault()) + { + // + result = new XUserProfileDto + { + Bio = source.Bio, + UserId = source.Id, + Email = source.Email, + Roles = roles.ToList(), + Avatar = source.Avatar, + IsEnable = source.IsEnable, + IsBanned = source.IsBanned, + UserName = source.UserName, + LastName = source.LastName, + FriendshipInfo = friendship, + FirstName = source.FirstName, + LastLogin = source.LastLogin, + CoverImage = source.CoverImage, + PhoneNumber = source.PhoneNumber, + DateOfBirth = source.DateOfBirth, + CreationDate = source.CreationDate, + OpenToSearch = source.OpenToSearch, + Gender = (XGender)(int)source.Gender, + EmailConfirmed = source.EmailConfirmed, + PhoneNumberConfirmed = source.PhoneNumberConfirmed, + Avatars = source.Avatars.Select(pfi => + { + // + return new XProfileImageDto + { + Id = pfi.Id, + Name = pfi.Name, + Path = pfi.Path, + Thumb = pfi.Thumb, + ThumbPath = pfi.ThumbPath, + CreationDate = pfi.CreationDate + }; + }).ToList(), + }; + } + + // + // Apply Profiling Configuration ... + + // + bool isPassed = false; + + // + // Handle Friendship ... + isPassed = !isSameUser; + if (!isPassed) + { + result.FriendshipInfo = null; + } + + // + // Handle Email Info ... + isPassed = isAdmin || isSameUser || (!profileConfig.IsNullOrDefault() && profileConfig.ContainsEmail); + if (!isPassed) + { + result.Email = string.Empty; + result.EmailConfirmed = false; + } + + // + // Handle PhoneNumber Info ... + isPassed = isAdmin || isSameUser || (!profileConfig.IsNullOrDefault() && profileConfig.ContainsPhoneNumber); + if (!isPassed) + { + source.PhoneNumber = string.Empty; + source.PhoneNumberConfirmed = false; + } + + // + // Handle Last Login ... + isPassed = isAdmin || isSameUser || (!profileConfig.IsNullOrDefault() && profileConfig.ContainsLastLogin); + if (!isPassed) + { + result.LastLogin = null; + } + + // + // Handle Date Of Birth ... + isPassed = isAdmin || isSameUser || (!profileConfig.IsNullOrDefault() && profileConfig.ContainsDateOfBirth); + if (!isPassed) + { + result.DateOfBirth = null; + } + + // + // Handle Creation Date ... + isPassed = isAdmin || isSameUser || (!profileConfig.IsNullOrDefault() && profileConfig.ContainsCreationDate); + if (!isPassed) + { + result.CreationDate = null; + } + + // + // Handle Roles ... + isPassed = isAdmin || isSameUser || (!profileConfig.IsNullOrDefault() && profileConfig.ContainsRoles); + if (!isPassed) + { + result.Roles = new List(); + } + + // + return result; + } + + /// + /// Check Specified User Has Access to Provided Roles ... + /// + /// + /// + /// + public static bool HasAccess( + this XUserClaimsInfoDto source, + string[] roles + ) + { + // + var result = + !source.IsNullOrDefault() && + source.Roles + .Any(r => roles + .Any(ar => r.ToNormalString() + .Contains(ar.ToNormalString()))); + + // + return result; + } + + /// + /// Check Specified User Contains Specified UserSelectByParam or not ... + /// + /// + /// + /// + public static bool ContainsUserSelectByParam( + this XUser source, + string userSelectByParam + ) + { + // + var result = + !source.IsNull() && + !userSelectByParam.IsNullOrEmpty() && + (source.Id == userSelectByParam || + source.Email.ToNormalString() == userSelectByParam.ToNormalString() || + source.UserName.ToNormalString() == userSelectByParam.ToNormalString() || + source.PhoneNumber.ToNormalString() == userSelectByParam.ToNormalString()); + + // + return result; + } + + /// + /// Check Specified Api Key is Revoked or not ... + /// + /// + /// + public static bool IsRevoked(this XApiKey source) + { + // + var result = + !source.IsNullOrDefault() && + source.RevokedAt.HasValue; + + // + return result; + } + + /// + /// Check Specified Api Key is Expired or not ... + /// + /// + /// + public static bool IsExpired(this XApiKey source) + { + // + var result = + !source.IsNullOrDefault() && + DateTime.UtcNow >= source.ExpiresAt; + + // + return result; + } + + /// + /// Check Specified Api Key is Active or not ... + /// + /// + /// + public static bool IsActive(this XApiKey source) + { + // + var result = + !source.IsNullOrDefault() && + !source.IsRevoked() && + !source.IsExpired(); + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Extensions\XModelExtensions.cs + +using System; +using xModels.Dtos; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +using System.Collections.Generic; +using xIdentityModels.Descriptors; +using xIdentityModels.Navigations; + +namespace xIdentityModels.Extensions +{ + public static class XModelExtensions + { + /// + /// /// Convert XIdentityUserDescriptor instance to XUser + /// + /// + /// + public static XUser ToXUser(this XIdentityUserDescriptor source) + { + return new XUser + { + Id = source.Id, + FirstName = source.FirstName, + LastName = source.LastName, + UserName = source.UserName, + Email = source.Email, + EmailConfirmed = source.EmailConfirmed, + PhoneNumber = source.PhoneNumber, + PhoneNumberConfirmed = source.PhoneNumberConfirmed, + IsEnable = true, + IsBanned = false, + CreationDate = DateTime.UtcNow, + DateOfBirth = DateTime.Parse(source.DateOfBirth) + }; + } + + /// + /// Converts a User to Person ... + /// + /// + /// + public static XPersonDto ToXPerson(this XUser source) + { + // + XPersonDto result = null; + + // + if (!source.IsNullOrDefault()) + { + // + result = new XPersonDto + { + Avatar = source.Avatar, + Username = source.UserName, + Lastname = source.LastName, + Firstname = source.FirstName, + }; + } + + // + return result; + } + + /// + /// Check a User is Same as Descriptor User + /// + /// + /// + /// + public static bool IsSameAs(this XUser source, XIdentityUserDescriptor dest) + { + // + if (source.IsNull() || + dest.IsNull()) + { + return false; + } + + // + var isSame = source.IsSameContent(dest, propertyWhiteList: new[] { + nameof (XIdentityUserDescriptor.FirstName), + nameof (XIdentityUserDescriptor.LastName), + nameof (XIdentityUserDescriptor.UserName), + nameof (XIdentityUserDescriptor.Email), + nameof (XIdentityUserDescriptor.EmailConfirmed), + nameof (XIdentityUserDescriptor.PhoneNumber), + nameof (XIdentityUserDescriptor.PhoneNumberConfirmed), + nameof (XIdentityUserDescriptor.IsEnable), + nameof (XIdentityUserDescriptor.IsBanned), + nameof (XIdentityUserDescriptor.DateOfBirth) + }, + propertyValueCheckers: new[] { + new KeyValuePair> ( + nameof (XIdentityUserDescriptor.DateOfBirth), + (user, descriptorUser) => { + // + var descriptorUserDateOfBirth = DateTime.Parse (descriptorUser.DateOfBirth); + return DateTime.Equals (user.DateOfBirth, descriptorUserDateOfBirth); + }) + } + ); + + // + return isSame; + } + + /// + /// Check to XDevice instance to be same or not + /// + /// + /// + /// + public static bool IsSameAs(this XDevice source, XDevice dest) + { + // + if (source.IsNull() || + dest.IsNull()) + { + return false; + } + + // + var isSame = source.Os.ToNormalString() == dest.Os.ToNormalString() && + source.OsVersion.ToNormalString() == dest.OsVersion.ToNormalString() && + source.Browser.ToNormalString() == dest.Browser.ToNormalString() && + source.UserAgent.ToNormalString() == dest.UserAgent.ToNormalString() && + source.DeviceType == dest.DeviceType; + + // + return isSame; + } + + /// + /// convert UserClaimsInfo to TokenResponse ... + /// + /// + /// + public static XTokenResponse ToXTokenResponse(this XUserClaimsInfoDto source) + { + // + if (source.IsNull() || + (source.AccessToken.IsNullOrEmpty() && source.RefreshToken.IsNullOrEmpty())) + { + return null; + } + + // + var result = new XTokenResponse + { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = source.ExpiresAt + }; + + // + return result; + } + + /// + /// Validate ... + /// + /// + /// + public static bool IsValid(this XCreateApiKeyRequest source) + { + // + var result = !source.IsNull(); + + // + return result; + } + + /// + /// Validate Item for Add or Update ... + /// + /// + /// + public static bool IsValidForAdd(this XApplication source) + { + // + var result = + !source.IsNullOrDefault() && + !source.Name.IsNullOrEmpty(); + + // + return result; + } + + /// + /// Validate Item for Add or Update ... + /// + /// + /// + public static bool IsValidForAdd(this XApplicationDto source) + { + // + var result = + !source.IsNullOrDefault() && + !source.Name.IsNullOrEmpty(); + + // + return result; + } + + /// + /// Validate Item for Add or Update ... + /// + /// + /// + public static bool IsValidForAdd(this XApiKey source) + { + // + var result = + !source.IsNullOrDefault() && + !source.ApplicationId.IsValidIntId() && + !source.KeyHash.IsNullOrEmpty(); + + // + return result; + } + + /// + /// Validate Item for Add or Update ... + /// + /// + /// + public static bool IsValidForAdd(this XApiKeyDto source) + { + // + var result = + !source.IsNullOrDefault() && + source.ApplicationId.IsValidIntId() && + !source.KeyHash.IsNullOrEmpty(); + + // + return result; + } + + /// + /// Validate Item for Add or Update ... + /// + /// + /// + public static bool IsValidForAdd(this XApiKeyUsage source) + { + // + var result = + !source.IsNullOrDefault() && + !source.ApiKeyId.IsDefaultGuid() && + !source.ClientIP.IsNullOrEmpty() && + !source.Endpoint.IsNullOrEmpty() && + !source.HttpMethod.IsNullOrEmpty(); + + // + return result; + } + + /// + /// Validate Item for Add or Update ... + /// + /// + /// + public static bool IsValidForAdd(this XApiKeyUsageDto source) + { + // + var result = + !source.IsNullOrDefault() && + !source.ApiKeyId.IsDefaultGuid() && + !source.ClientIP.IsNullOrEmpty() && + !source.Endpoint.IsNullOrEmpty() && + !source.HttpMethod.IsNullOrEmpty(); + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Extensions\XTokenExtensions.cs + +using System; +using System.Linq; +using System.Net.Http; +using xCommons.Constants; +using xCommons.Extensions; +using IdentityModel.Client; +using xExceptions.Constants; +using System.Security.Claims; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Constants; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Authentication; + +namespace xIdentityModels.Extensions { + public static class XTokenExtensions { + // + #region Identity Token Extensions ... + /// + /// Convert a SecurityToken to it's Token String + /// + /// + /// + /// + public static string ToTokenString ( + this SecurityToken source, + JwtSecurityTokenHandler tokenHandler + ) { + return tokenHandler.WriteToken (source); + } + + /// + /// Validate a Token + /// + /// + /// + /// + /// + public static bool ValidateToken ( + this string token, + JwtSecurityTokenHandler tokenHandler, + TokenValidationParameters tokenValidationParams) { + // + if (token.IsNullOrEmpty ()) { + return false; + } + + // + SecurityToken tokenObj = null; + try { + tokenHandler.ValidateToken (token, tokenValidationParams, out tokenObj); + } catch { + return false; + } + + // + return tokenObj != null; + } + + /// + /// Convert a token string to Security Token + /// + /// + /// + /// + /// + public static SecurityToken ToSecurityToken ( + this string token, + JwtSecurityTokenHandler tokenHandler, + TokenValidationParameters tokenValidationParams) { + // + if (token.IsNullOrEmpty () || + !token.ValidateToken (tokenHandler, tokenValidationParams)) { + // + return null; + } + + // + // convert token from String to JWTSecurity Token ... + try { + return tokenHandler.ReadToken (token); + } catch { + return null; + } + } + + /// + /// Parse Action Token and return Corresponding RegistrationRequestDto + /// + /// + /// + /// + /// + public static XActionRequestToken ParseActionRequestToken ( + this string token, + JwtSecurityTokenHandler tokenHandler, + TokenValidationParameters tokenValidationParams) { + // + if (token.IsNullOrEmpty () || + !token.ValidateToken (tokenHandler, tokenValidationParams)) { + // + return null; + } + + // + // convert token from String to JWTSecurity Token ... + JwtSecurityToken tokenObj = null; + try { + tokenObj = tokenHandler.ReadToken (token) as JwtSecurityToken; + } catch { + return null; + } + + // + if (tokenObj == null) { + return null; + } + + // + // Extract Registered Payload Claims ... + var mCurrentStepClaim = tokenObj.Claims + .FirstOrDefault (c => c.Type == nameof (XActionRequestToken.Action)); + var mContextClaim = tokenObj.Claims + .FirstOrDefault (c => c.Type == nameof (XActionRequestToken.Context)); + var mTokenClaim = tokenObj.Claims + .FirstOrDefault (c => c.Type == nameof (XActionRequestToken.Token)); + + // + // Check All Claims to be NotNull ... + if (mCurrentStepClaim == null || + mContextClaim == null || + mTokenClaim == null) { + return null; + } + + // + // Reading all Claims Values ... + var mCurrentStepStr = mCurrentStepClaim.Value; + var mCurrentStep = 0; + try { + int.TryParse (mCurrentStepStr, out mCurrentStep); + } catch { } + + // + var mContext = mContextClaim.Value.IsNullOrEmpty () ? + null : mContextClaim.Value.FromJSON (); + var mToken = mTokenClaim.Value; + + // + // Create Instance of UserInvite ... + var result = new XActionRequestToken { + Action = (XAction) mCurrentStep, + Context = mContext, + Token = mToken + }; + + // + return result; + } + + /// + /// Check to ActionRequestContext object is same or not + /// + /// + /// + /// + public static bool IsSameAs ( + this XActionRequestContext source, + XActionRequestContext dest + ) { + // + if (source == null || + dest == null) { + return false; + } + + // + var result = source.ToJSON () == dest.ToJSON (); + + // + return result; + } + + /// + /// Convert a RegistrationRequestDto object to it's jwt token Claims + /// + /// + /// + public static Claim[] ToJwtClaims (this XActionRequestToken source) { + // + // Generate Required Fields ... + var action = source.Action; + var context = source.Context; + var token = source.Token; + + // + // Generate Claims Array ... + var claims = new Claim[] { + new Claim (nameof (XActionRequestToken.Action), ((int) action).ToString (), ClaimValueTypes.Integer), + new Claim (nameof (XActionRequestToken.Context), context == null ? "" : context.ToJSON (), ClaimValueTypes.String), + new Claim (nameof (XActionRequestToken.Token), token.IsNullOrEmpty () ? "" : token, ClaimValueTypes.String) + }; + + // + // Return Result ... + return claims; + } + + /// + /// Generate a SecurityToken based on RegistrationRequestDto object + /// + /// + /// + /// + /// + public static SecurityToken ToJwtToken ( + this XActionRequestToken source, + JwtSecurityTokenHandler tokenHandler, + SecurityTokenDescriptor tokenDescriptor) { + // + // Getting Claims ... + var claims = source.ToJwtClaims (); + tokenDescriptor.Subject = new ClaimsIdentity (claims); + + // + // Generate Token Object ... + var tokenObject = tokenHandler.CreateToken (tokenDescriptor); + + // + // Return Result ... + return tokenObject; + } + #endregion + + // + #region IdentityServer Token Extensions ... + /// + /// Retrieve Tokens From Headers + /// + /// + /// + public static XTokenResponse RetrieveTokens (this HttpRequest source) { + // + var accessToken = source.Headers[XAuthorization.Header].ToString (); + var refreshToken = source.Headers[XAuthorization.RefreshToken].ToString (); + + // + var expiresAtStr = source.Headers[XAuthorization.ExpiresAt].ToString (); + long expiresAt = expiresAtStr.ConvertTo (); + + // + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + // + return new XTokenResponse { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + + /// + /// Retrieve Tokens From HttpContext + /// + /// + /// + public static async Task RetrieveTokens (this HttpContext source) { + // + var accessToken = await source.GetTokenAsync (XAuthorization.AccessToken); + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + var refreshToken = await source.GetTokenAsync (XAuthorization.RefreshToken); + var expiresAtStr = await source.GetTokenAsync (XAuthorization.ExpiresAt); + long expiresAt = expiresAtStr.ConvertTo (); + + // + var headerRequestTokens = source.Request.RetrieveTokens (); + + // + var result = new XTokenResponse { + AccessToken = accessToken ?? headerRequestTokens.AccessToken, + RefreshToken = refreshToken ?? headerRequestTokens.RefreshToken, + ExpiresAt = expiresAt != 0 ? expiresAt : headerRequestTokens.ExpiresAt + }; + + // + return result; + } + + /// + /// Retrieve Tokens From ActionExecutingContext + /// + /// + /// + public static async Task RetrieveTokens (this ActionExecutingContext source) { + // + var result = await source.HttpContext.RetrieveTokens (); + return result; + } + + /// + /// Retrieve Tokens From XUserInfo instance + /// + /// + /// + public static XTokenResponse RetrieveTokens (this XUserClaimsInfoDto source) { + // + var result = new XTokenResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = source.ExpiresAt + }; + + // + return result; + } + + /// + /// Retrieve Tokens From IHeader Dictionary + /// + /// + /// + public static XTokenResponse RetrieveTokens (this IHeaderDictionary source) { + // + var accessToken = source[XAuthorization.Header].ToString () ?? ""; + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + // + var refreshToken = source[XAuthorization.RefreshToken].ToString () ?? ""; + + // + var expiresAtStr = source[XAuthorization.ExpiresAt].ToString () ?? ""; + var expiresAt = expiresAtStr.ConvertTo (); + + // + var result = new XTokenResponse { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Generate XTokenResponse instance based on TokenResponse + /// + /// + /// + public static XLoginResponse CreateXLoginResponse (this TokenResponse source) { + // + if (source.IsNull () || + source.IsError) { + XException.InvalidToken.Throw (); + } + + // + var expiresAt = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds () + + source.ExpiresIn; + + // + var result = new XLoginResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Generate XTokenResponse instance based on TokenResponse + /// + /// + /// + public static XTokenResponse CreateXTokenResponse (this TokenResponse source) { + // + if (source.IsNull () || + source.IsError) { + XException.InvalidToken.Throw (); + } + + // + var expiresAt = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds () + + source.ExpiresIn; + + // + var result = new XTokenResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Convert XLoginResponse to XTokenResponse + /// + /// + /// + public static XTokenResponse ToXTokenResponse (this XLoginResponse source) { + // + if (source.IsNull ()) { + return null; + } + + // + return new XTokenResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = source.ExpiresAt + }; + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + /// + public static void SetXAuthenticationTokens ( + this IHeaderDictionary source, + XLoginResponse model, + string secret = null + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + + // + if (!secret.IsNullOrEmpty ()) { + var revisionChecksum = model.GetRevisionChecksum (secret); + source.Add (XAuthorization.RevisionChecksum, revisionChecksum); + } + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + /// + public static void SetXAuthenticationTokens ( + this IHeaderDictionary source, + XTokenResponse model, + string secret = null + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + + // + if (!secret.IsNullOrEmpty ()) { + var revisionChecksum = model.GetRevisionChecksum (secret); + source.Add (XAuthorization.RevisionChecksum, revisionChecksum); + } + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestHeaders source, + XLoginResponse model + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestHeaders source, + XTokenResponse model + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequest source, + XLoginResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequest source, + XTokenResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestMessage source, + XLoginResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpRequestMessage source, + XTokenResponse model + ) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add ReNewed Authorization Tokens in Headers of + /// Specific Response. + /// Later in HttpInterceptors we can Look for AccessToken-ReNewed + /// header Value and Renew User Tokens based on it + /// + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpResponse source, + XLoginResponse model, + string secret + ) { + source.Headers.SetXAuthenticationTokens (model, secret); + } + + /// + /// Add ReNewed Authorization Tokens in Headers of + /// Specific Response. + /// Later in HttpInterceptors we can Look for AccessToken-ReNewed + /// header Value and Renew User Tokens based on it + /// + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpResponse source, + XTokenResponse model, + string secret + ) { + source.Headers.SetXAuthenticationTokens (model, secret); + } + + /// + /// Determines a Token Can Refrsh or not + /// + /// + /// + public static bool IsRefreshable (this XLoginResponse source) { + // + var containsTokenData = !source.IsNull () && + !source.AccessToken.IsNullOrEmpty () && + !source.RefreshToken.IsNullOrEmpty (); + if (!containsTokenData) { + return false; + } + + // + var current = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds (); + + // + // var result = current > source.ExpiresAt || current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + var result = current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + + // + return result; + } + + /// + /// Determines a Token Can Refrsh or not + /// + /// + /// + public static bool IsRefreshable (this XTokenResponse source) { + // + var containsTokenData = !source.IsNull () && + !source.AccessToken.IsNullOrEmpty () && + !source.RefreshToken.IsNullOrEmpty (); + if (!containsTokenData) { + return false; + } + + // + var current = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds (); + + // + // var result = current > source.ExpiresAt || current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + var result = current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + + // + return result; + } + + /// + /// Generate a Revision Checksum for new Tokens + /// + /// + /// + /// + public static string GetRevisionChecksum ( + this XLoginResponse source, + string secret + ) { + return (source.AccessToken + + secret + + source.RefreshToken + + secret + + source.ExpiresAt.ToString () + ) + .ToMd5String (); + } + + /// + /// Generate a Revision Checksum for new Tokens + /// + /// + /// + /// + public static string GetRevisionChecksum ( + this XTokenResponse source, + string secret + ) { + return (source.AccessToken + + secret + + source.RefreshToken + + secret + + source.ExpiresAt.ToString () + ) + .ToMd5String (); + } + + /// + /// Check an XLoginResponse instance is Valid with it's revision + /// + /// + /// + /// + /// + public static bool ValidateRevisionChecksum ( + this XLoginResponse source, + string checksum, + string secret + ) { + // + var xChecksum = source.GetRevisionChecksum (secret); + return checksum == xChecksum; + } + + /// + /// Check an XTokenResponse instance is Valid with it's revision + /// + /// + /// + /// + /// + public static bool ValidateRevisionChecksum ( + this XTokenResponse source, + string checksum, + string secret + ) { + // + var xChecksum = source.GetRevisionChecksum (secret); + return checksum == xChecksum; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Interfaces\IXUserRoleHelper.cs + +using xIdentityModels.Models; + +namespace xIdentityModels.Interfaces { + public interface IXUserRoleHelper { + bool canReadCriticalData (XUserClaimsInfoDto userInfo); + bool canDoDangerousAction (XUserClaimsInfoDto userInfo); + bool isInRole (XUserClaimsInfoDto userInfo, string role); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XActionRequest.cs + +using System; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Navigations; + +namespace xIdentityModels.Models { + /// + /// represent a system wide actions + /// + public class XActionRequest { + /// + /// Client Device Locale + /// + /// user's client device local such as en-US + public string Lang { get; set; } = null; + /// + /// Requested Action Token + /// + /// a hashed content + public string ActionToken { get; set; } = null; + /// + /// user's device + /// + /// an instance of XDevice which represent user's device + public XDevice Device { get; set; } = null; + /// + /// FirstName + /// + /// users FirstName + public string FirstName { get; set; } = null; + /// + /// LastName + /// + /// user's LastName + public string LastName { get; set; } = null; + /// + /// an Optional Value which represetns User's DateOfBirth + /// + /// user's DateOfBirth + public DateTime? DateOfBirth { get; set; } = DateTime.MinValue; + /// + /// User's Id + /// + /// a GUID string which represent user's Id + public string UserId { get; set; } = null; + /// + /// UserName + /// + /// user's UserName + public string UserName { get; set; } = null; + /// + /// Email + /// + /// user's Email address + public string Email { get; set; } = null; + /// + /// MobileNumber / PhoneNumber + /// + /// user's PhoneNumber + public string MobileNumber { get; set; } = null; + /// + /// Password + /// + /// user's Password + public string Password { get; set; } = null; + /// + /// NewPassword + /// + /// a passwor string which used to Change/Reset password + public string NewPassword { get; set; } = null; + /// + /// EmailVerificationCode + /// + /// a Verification Code which assign to user's for confirm it's Email Address + public string EmailVerificationCode { get; set; } = null; + /// + /// MobileVerificationCode + /// + /// a Verification Code which assign to user's for confirm it's Phone Number + public string MobileVerificationCode { get; set; } = null; + /// + /// Thumbnail + /// + /// an instance of IFormFile which is used as user's avatar + public IFormFile Thumbnail { get; set; } + /// + /// ReturnUrl + /// + /// + /// a url string which used in many used cases for providing a way to lead users to right places. + /// + public string ReturnUrl { get; set; } = null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XActionRequestContext.cs + +using System; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Navigations; + +namespace xIdentityModels.Models { + public class XActionRequestContext { + // + public string Lang { get; set; } + + // + public string UserId { get; set; } + public string UserName { get; set; } + + // + public string MobileNumber { get; set; } + public string MobileVerificationCode { get; set; } + public bool MobileVerified { get; set; } + + // + public string Email { get; set; } + public string EmailVerificationCode { get; set; } + public bool EmailVerified { get; set; } + + // + public string FirstName { get; set; } + public string LastName { get; set; } + public DateTime DateOfBirth { get; set; } + + // + public XDevice Device { get; set; } + public IFormFile Thubmnail { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XActionRequestToken.cs + +using xCommons.Extensions; +using xIdentityModels.Constants; + +namespace xIdentityModels.Models { + public class XActionRequestToken { + public XAction Action { get; set; } + public XActionRequestContext Context { get; set; } + public string Token { get; set; } + + public void Prepare (string secret) { + Token = (Action.ToString () + + secret + + Context.ToJSON ()).ToMd5String (); + } + + public bool Validate (string secret) { + return (Action.ToString () + + secret + + Context.ToJSON ()).ToMd5String () == Token; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XActionResponse.cs + +using System; +using Newtonsoft.Json; + +namespace xIdentityModels.Models { + /// + /// represent a system wide action result + /// + public class XActionResponse { + /// + /// ActionToken + /// + /// a hashed string which carry required info for doing system actions. + [JsonProperty ( + "token", + Required = Required.Default, + NullValueHandling = NullValueHandling.Ignore)] + public string Token { get; set; } + + /// + /// ActionToken Lifetime + /// + /// an instance of DateTime which represent token expiration time + [JsonProperty ( + "expiration", + Required = Required.DisallowNull, + NullValueHandling = NullValueHandling.Ignore)] + public DateTime Expiration { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XApiKey.cs + +using System; +using xModels.Base; +using System.ComponentModel.DataAnnotations; + +namespace xIdentityModels.Models +{ + /// + /// an Specified Api Key ... + /// + public class XApiKey : XBaseGuidIDEntity + { + /// + /// Related Application Id ... + /// + [Required] + public int ApplicationId { get; set; } + + /// + /// ApiKey ... + /// + [Required] + public string Key { get; set; } + + /// + /// Hash ... + /// SHA256 of ApiKey ... + /// + [Required] + [StringLength(512)] + public string KeyHash { get; set; } + + /// + /// a Prefix ... + /// + [StringLength(100)] + public string KeyPrefix { get; set; } + + /// + /// Expiration Type ... + /// + [Required] + public DateTime ExpiresAt { get; set; } + + /// + /// Cration Time ... + /// + public DateTime CreatedOn { get; set; } + + /// + /// Last Usage Date ... + /// + public DateTime? LastUsedAt { get; set; } + + /// + /// Revoked Time ... + /// + public DateTime? RevokedAt { get; set; } + + /// + /// Revoker User Id ... + /// + public string RevokedBy { get; set; } + + /// + /// a Json Array String ... + /// XApiKeyScope ... + /// + [StringLength(1000)] + public string AllowedScopes { get; set; } + + /// + /// a Comma-separated List which Specified White List IP ... + /// + [StringLength(2000)] + public string AllowedIPs { get; set; } + + /// + /// Rate Limit Per Minute ... + /// + public int RateLimitPerMinute { get; set; } = 60; + + // + #region Navigation ... + /// + /// Related Application ... + /// + public virtual XApplication Application { get; set; } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XApiKeyUsage.cs + +using System; +using xModels.Base; + +namespace xIdentityModels.Models +{ + public class XApiKeyUsage : XBaseLongIDEntity + { + /// + /// Api Key Id ... + /// + public Guid ApiKeyId { get; set; } + + /// + /// Called Endpoint ... + /// + public string Endpoint { get; set; } + + /// + /// Called Http Method ... + /// + public string HttpMethod { get; set; } + + /// + /// Client IP Address ... + /// + public string ClientIP { get; set; } + + /// + /// Status Code ... + /// + public int StatusCode { get; set; } + + /// + /// Request Time ... + /// + public DateTime RequestedOn { get; set; } + + /// + /// Response Time in Milliseconds ... + /// + public long ResponseTimeMs { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XApiKeyValidationRequest.cs + +namespace xIdentityModels.Models +{ + public class XApiKeyValidationRequest + { + public string ApiKey { get; set; } + public string ClientIP { get; set; } + public string RequiredScope { get; set; } = null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XApiKeyValidationResult.cs + +using System.Collections.Generic; + +namespace xIdentityModels.Models +{ + public class XApiKeyValidationResult + { + public string OwnerId { get; set; } + public int ApplicationId { get; set; } + public IList Scopes {get; set;} = []; + public IList Errors { get; set; } = []; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XApplication.cs + +using System; +using xModels.Base; +using xIdentityModels.Constants; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace xIdentityModels.Models +{ + /// + /// an Application in xDashboard ... + /// + public class XApplication : XBaseIntIDEntity + { + /// + /// Name ... + /// + [Required] + [StringLength(255)] + public string Name { get; set; } + + /// + /// Descriptor ... + /// + [StringLength(1000)] + public string Description { get; set; } + + /// + /// Owner Id ... + /// + [Required] + [StringLength(255)] + public string OwnerId { get; set; } + + /// + /// Application Type ... + /// + public XApplicationType Type { get; set; } + + /// + /// Specified Application is Active or not ... + /// + public bool IsActive { get; set; } = true; + + /// + /// Application Create Time ... + /// + public DateTime CreatedOn { get; set; } + + /// + /// Application Update Type ... + /// + public DateTime UpdatedAt { get; set; } + + // + #region Navigation ... + /// + /// a Collection of ApiKeys which Related to this Application ... + /// + public virtual ICollection ApiKeys { get; set; } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XBannedDevice.cs + +using System; +using xModels.Base; +using xCommons.Extensions; +using xIdentityModels.Navigations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels.Models { + public class XBannedDevice : XBaseIntIDEntity { + public DateTime BannedOn { get; set; } + + public string DeviceStr { get; set; } + + [NotMapped] + public XDevice Device { + get { + return DeviceStr.FromJSON (); + } + set { + DeviceStr = value.ToJSON (); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XCreateApiKeyRequest.cs + +using System.Collections.Generic; + +namespace xIdentityModels.Models +{ + /// + /// Create Specified Api Key ... + /// + public class XCreateApiKeyRequest + { + public int? RateLimit {get; set;} = null; + public int? ExpirationMinutes {get; set;} = null; + public IEnumerable Scopes {get; set;} = null; + public IEnumerable AllowedIPs {get; set;} = null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XLoginRequest.cs + +using xIdentityModels.Navigations; +using System.ComponentModel.DataAnnotations; + +namespace xIdentityModels.Models { + /// + /// Represent Requirements for Authenticationg a User + /// + public class XLoginRequest { + /// + /// how select an specific User + /// + /// an string value which represent how system select user such as: UserName, Email Address or MobileNumber + [Required] + public string UserSelectBy { get; set; } + + /// + /// user's password + /// + /// an string which represent user's pasword + [Required] + public string Password { get; set; } + + /// + /// user's device + /// + /// an instance of XDevice which represent user's device + public XDevice Device { get; set; } + + /// + /// user's language + /// + /// an string which represent user's language and locale, such as: en-US + public string Language { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XLoginResponse.cs + +using xIdentityModels.Dtos; + +namespace xIdentityModels.Models { + public class XLoginResponse : XTokenResponse { + public XUserProfileDto Profile { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XProfileUpdateRequest.cs + +using System; +using xIdentityModels.Constants; +using System.Collections.Generic; + +namespace xIdentityModels.Models { + public class XProfileUpdateRequest { + public string Email { get; set; } + public bool? EmailConfirmed { get; set; } + public string PhoneNumber { get; set; } + public bool? PhoneNumberConfirmed { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Avatar { get; set; } + public DateTime? DateOfBirth { get; set; } + public DateTime? CreationDate { get; set; } + public DateTime? LastLogin { get; set; } + public XGender? Gender { get; set; } + public ICollection Roles { get; set; } = new HashSet (); + public bool? IsEnable { get; set; } + public bool? IsBanned { get; set; } + public string Bio { get; set; } + public string CoverImage { get; set; } + public bool OpenToSearch { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XRotateApiKeyRequest.cs + +using System; + +namespace xIdentityModels.Models +{ + /// + /// Rotate Specified Api Key ... + /// + public class XRotateApiKeyRequest + { + public TimeSpan? NewExpiration { get; set; } = null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XToken.cs + +using xModels.Base; +using xCommons.Extensions; +using xIdentityModels.Constants; +using xIdentityModels.Navigations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels.Models { + public class XToken : XBaseIntIDEntity { + public string Token { get; set; } + public string Hash { get; set; } + public string UserSelectByParam { get; set; } + public string DeviceStr { get; set; } + public XAction Type { get; set; } + + [NotMapped] + public XDevice Device { + get { + return DeviceStr.FromJSON (); + } + + set { + DeviceStr = value.ToJSON (); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XTokenResponse.cs + +using System.ComponentModel.DataAnnotations; + +namespace xIdentityModels.Models { + public class XTokenResponse { + [Required] + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + public long ExpiresAt { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XUserClaimsInfoDto.cs + +using System; +using System.Linq; +using xModels.Base; +using xCommons.Helpers; +using xCommons.Constants; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Security.Claims; +using xIdentityModels.Constants; +using System.Collections.Generic; + +namespace xIdentityModels.Models +{ + public class XUserClaimsInfoDto : XBaseDto + { + // + #region Properties ... + public string AccessToken { get; } + public string RefreshToken { get; } + public long ExpiresAt { get; } + public string UserId { get; } + public string UserName { get; } + public string FirstName { get; } + public string LastName { get; } + public string Picture { get; } + public string Email { get; } + public string PhoneNumber { get; } + public XGender Gender { get; } + public bool IsBanned { get; } + public bool IsEnabled { get; } + public bool EmailConfirmed { get; } + public bool PhoneNumberConfirmed { get; } + public string Bio { get; set; } + public string CoverImage { get; set; } + public bool OpenToSearch { get; set; } + public long? ExpiredOn { get; } + public long? AuthenticatedOn { get; } + public DateTime? BrithDate { get; } + public IEnumerable Issuers { get; } = new HashSet(); + public IEnumerable Audiences { get; } = new HashSet(); + public IEnumerable ClientIds { get; } = new HashSet(); + public IEnumerable Scopes { get; } = new HashSet(); + public IEnumerable Roles { get; } = new HashSet(); + #endregion + + // + public XUserClaimsInfoDto() { } + + public XUserClaimsInfoDto( + string accessToken, + string refreshToken, + long expiresAt, + IEnumerable claims + ) + { + // + // refreshToken.IsNullOrEmpty () + if (claims.IsNull() || + accessToken.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + // Attach XLogin Properties ... + AccessToken = accessToken; + RefreshToken = refreshToken; + ExpiresAt = expiresAt; + + // + if (!claims.IsNull()) + { + // + UserId = claims.FirstOrDefault(c => c.Type == XCustomClaims.UserId)?.Value ?? ""; + UserName = claims.FirstOrDefault(c => c.Type == XCustomClaims.UserName)?.Value ?? ""; + FirstName = claims.FirstOrDefault(c => c.Type == XCustomClaims.FirstName)?.Value ?? ""; + LastName = claims.FirstOrDefault(c => c.Type == XCustomClaims.LastName)?.Value ?? ""; + Picture = claims.FirstOrDefault(c => c.Type == XCustomClaims.Picture)?.Value ?? ""; + Email = claims.FirstOrDefault(c => c.Type == XCustomClaims.Email)?.Value ?? null; + PhoneNumber = claims.FirstOrDefault(c => c.Type == XCustomClaims.PhoneNumber)?.Value ?? null; + + // + Bio = claims.FirstOrDefault(c => c.Type == XCustomClaims.Bio)?.Value ?? null; + CoverImage = claims.FirstOrDefault(c => c.Type == XCustomClaims.CoverImage)?.Value ?? null; + + // + var openToSearchStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.OpenToSearch)?.Value ?? null; + if (openToSearchStr.IsNullOrEmpty()) + { + OpenToSearch = false; + } + else + { + // + var openToSearch = false; + bool.TryParse(openToSearchStr, out openToSearch); + + // + OpenToSearch = openToSearch; + } + + // + // Handle Gender ... + var genders = ObjectHelper.ToEnumerableKeys(); + var genderStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.Gender).Value ?? null; + if (genderStr.IsNullOrEmpty()) + { + Gender = XGender.Male; + } + else + { + Gender = genderStr.GetValue(); + } + + // + // Handle IsBanned ... + var isBannedStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.IsBanned).Value ?? null; + if (isBannedStr.IsNullOrEmpty()) + { + IsBanned = false; + } + else + { + // + var isBanned = false; + Boolean.TryParse(isBannedStr, out isBanned); + + // + IsBanned = isBanned; + } + + // + // Handle IsEnabled ... + var isEnabledStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.IsEnabled).Value ?? null; + if (isEnabledStr.IsNullOrEmpty()) + { + IsEnabled = false; + } + else + { + // + var isEnabled = false; + Boolean.TryParse(isEnabledStr, out isEnabled); + + // + IsEnabled = isEnabled; + } + + // + // Handle Email Confirmed ... + var emailConfirmedStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.EmailVerified).Value ?? null; + if (emailConfirmedStr.IsNullOrEmpty()) + { + EmailConfirmed = false; + } + else + { + // + var emailConfirmed = false; + Boolean.TryParse(emailConfirmedStr, out emailConfirmed); + + // + EmailConfirmed = emailConfirmed; + } + + // + // Handle PhoneNumber Confirmed ... + var phoneNumberConfirmedStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.PhoneNumberVerified).Value ?? null; + if (phoneNumberConfirmedStr.IsNullOrEmpty()) + { + PhoneNumberConfirmed = false; + } + else + { + // + var phoneNumberConfirmed = false; + Boolean.TryParse(phoneNumberConfirmedStr, out phoneNumberConfirmed); + + // + PhoneNumberConfirmed = phoneNumberConfirmed; + } + + // + // Handle ExpiredOn ... + var expiredOnStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.ExpiredOn).Value ?? null; + if (expiredOnStr.IsNullOrEmpty()) + { + ExpiredOn = null; + } + else + { + // + long expiredOn = 0; + long.TryParse(expiredOnStr, out expiredOn); + + // + ExpiredOn = expiredOn; + } + + // + // Handle AuthenticatedOn ... + var authenticatedOnStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.AuthenticatedOn).Value ?? null; + if (authenticatedOnStr.IsNullOrEmpty()) + { + AuthenticatedOn = null; + } + else + { + // + long authenticatedOn = 0; + long.TryParse(authenticatedOnStr, out authenticatedOn); + + // + AuthenticatedOn = authenticatedOn; + } + + // + // Handle BrithDate ... + var brithDateStr = claims.FirstOrDefault(c => c.Type == XCustomClaims.BrithDate)?.Value ?? ""; + if (brithDateStr.IsNullOrEmpty()) + { + // BrithDate = null; + } + else + { + // + DateTime bDate; + DateTime.TryParse(brithDateStr, out bDate); + + // + BrithDate = bDate; + } + + // + // Collections ... + Issuers = claims.Where(c => c.Type == XCustomClaims.Issuer)? + .Select(c => c.Value) ?? new HashSet(); + Audiences = claims.Where(c => c.Type == XCustomClaims.Audience)? + .Select(c => c.Value) ?? new HashSet(); + ClientIds = claims.Where(c => c.Type == XCustomClaims.ClientId)? + .Select(c => c.Value) ?? new HashSet(); + Scopes = claims.Where(c => c.Type == XCustomClaims.Scope)? + .Select(c => c.Value) ?? new HashSet(); + Roles = claims.Where(c => c.Type == XCustomClaims.Role)? + .Select(c => c.Value) ?? new HashSet(); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Models\XVerificationRequest.cs + +using System; +using xModels.Base; +using xCommons.Extensions; +using xIdentityModels.Navigations; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels.Models { + public class XVerificationRequest : XBaseIntIDEntity { + + [Required] + public string VerificationCode { get; set; } + + public int NumberOfTries { get; set; } + public DateTime LastTryOn { get; set; } + + [NotMapped] + public XActionRequestToken Request { + get { + return RequestStr.FromJSON (); + } + set { + RequestStr = value.ToJSON (); + } + } + + public string RequestStr { get; set; } + + [NotMapped] + public XDevice Device { + get { + return DeviceStr.FromJSON (); + } + set { + DeviceStr = value.ToJSON (); + } + } + + public string DeviceStr { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Navigations\XDevice.cs + +using xModels.Base; +using xCommons.Constants; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels.Navigations { + /// + /// Represent a Client Device which used by User + /// + public class XDevice : XBaseIntIDEntity { + /// + /// Operating System + /// + /// device operating system identifier + public string Os { get; set; } + /// + /// Operating System Version + /// + /// which version of operatin system is in use + public string OsVersion { get; set; } + /// + /// Device Browser + /// + /// the Browser identifier + public string Browser { get; set; } + /// + /// Browser Engine Agent + /// + /// browser engine Agent + public string UserAgent { get; set; } + /// + /// Type of client Device + /// + /// represent device type XDeviceType + public XDeviceType DeviceType { get; set; } + + // + #region User ... + /// + /// UserId + /// + /// Represent this Devie belong to which User Identifier + public string UserId { get; set; } + /// + /// User + /// + /// Represent Device User XUser + [ForeignKey (nameof (UserId))] + public virtual XUser User { get; set; } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Navigations\XFriendshipFollower.cs + +using xModels.Base; +using xIdentityModels.Constants; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels.Navigations { + /// + /// represent a Follower of specific User + /// + public class XFriendshipFollower : XBaseIntIDEntity { + /// + /// XFriendshipState + /// + /// state of Friendship at current moment XFriendshipState + public XFriendshipState State { get; set; } + + // + #region User ... + /// + /// UserId + /// + /// source User's Id + public string UserId { get; set; } + /// + /// User + /// + /// source User XUser + [ForeignKey (nameof (UserId))] + public virtual XUser User { get; set; } + #endregion + + // + #region Dest User ... + /// + /// DestId + /// + /// destination User's Id + public string DestId { get; set; } + /// + /// Dest + /// + /// dest User XUser + [ForeignKey (nameof (DestId))] + public virtual XUser Dest { get; set; } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Navigations\XFriendshipFollowing.cs + +using xModels.Base; +using xIdentityModels.Constants; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels.Navigations { + /// + /// represent a Following of specific User + /// + public class XFriendshipFollowing : XBaseIntIDEntity { + /// + /// XFriendshipState + /// + /// state of Friendship at current moment XFriendshipState + public XFriendshipState State { get; set; } + + // + #region User ... + /// + /// UserId + /// + /// source User's Id + public string UserId { get; set; } + /// + /// User + /// + /// source User XUser + [ForeignKey (nameof (UserId))] + public virtual XUser User { get; set; } + #endregion + + // + #region Dest User ... + /// + /// DestId + /// + /// destination User's Id + public string DestId { get; set; } + /// + /// Dest + /// + /// dest User XUser + [ForeignKey (nameof (DestId))] + public virtual XUser Dest { get; set; } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Navigations\XProfileImage.cs + +using System; +using xModels.Base; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace xIdentityModels.Navigations { + /// + /// Represent a User's Avatar Entity + /// + public class XProfileImage : XBaseIntIDEntity { + /// + /// UserId + /// + /// represent current Avatar belongs to which User + public string UserId { get; set; } + /// + /// User + /// + /// User instance XUser + [ForeignKey (nameof (UserId))] + public XUser User { get; set; } + /// + /// Name + /// + /// Avatar's file name + [Required] + [StringLength (255)] + public string Name { get; set; } + /// + /// Thumb + /// + /// Avatar's thumbnail file's name + [Required] + [StringLength (255)] + public string Thumb { get; set; } + /// + /// Path + /// + /// Avatar's Image path on Profile Server + [Required] + [StringLength (255)] + public string Path { get; set; } + /// + /// ThumbPath + /// + /// Avatar's Thumbnail Image path on Profile Service + [Required] + [StringLength (255)] + public string ThumbPath { get; set; } + /// + /// CreationDate + /// + /// Upload Time + public DateTime CreationDate { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityModels\Providers\XDefaultUserRoleHelper.cs + +using System.Linq; +using xCommons.Extensions; +using xIdentityModels.Models; +using xIdentityModels.Interfaces; + +namespace xIdentityModels.Providers { + public class XDefaultUserRoleHelper : IXUserRoleHelper { + public bool canReadCriticalData (XUserClaimsInfoDto userInfo) { + return isInRole (userInfo, "admin"); + } + + public bool canDoDangerousAction (XUserClaimsInfoDto userInfo) { + return isInRole (userInfo, "admin"); + } + + public bool isInRole (XUserClaimsInfoDto userInfo, string role) { + return userInfo.Roles + .Any (r => + r.ToNormalString () == role.ToNormalString () + ); + } + } +} diff --git a/Documents/GeneratedContext/xDashboard-xIdentityService-20261009_203609.txt b/Documents/GeneratedContext/xDashboard-xIdentityService-20261009_203609.txt new file mode 100644 index 0000000..fe0cd6a --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xIdentityService-20261009_203609.txt @@ -0,0 +1,8818 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Configuration\XIdentityServiceConfiguration.cs + +using xIdentityService.Constants; + +namespace xIdentityService.Configuration { + public partial class XIdentityServiceConfiguration { + public string Authority { get; set; } + public string ApiName { get; set; } + public string ApiSecret { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string XPoweredValue { get; set; } + public XTokenType TokenType { get; set; } + public string XRevisionSecretKey { get; set; } + public int DefaultClientTimeout { get; set; } = 360; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Configuration\XProxyConfiguration.cs + +using xModels.Base; +using System.Collections.Generic; + +namespace xIdentityService.Configuration { + /// + /// XProxy Middleware Configuration ... + /// + public class XProxyConfiguration { + /// + /// Proxy Host Address for Clients Which Use XProxy Server ... + /// + /// + public string Host { get; set; } + + /// + /// if EnableXPoweredByAthorization property setted to true, this property determines + /// the header must be this value ... + /// + /// + public string XPoweredValue { get; set; } + + /// + /// Determines Proxy Http Handler Check SSL or not ... + /// + /// + public bool DisableSSLCheck { get; set; } + + /// + /// Determines Http Client Handler Allow Auto Redirect or not ... + /// + /// + public bool AllowAutoRedirect { get; set; } + + /// + /// Determines Proxy Server Log Received Requests or not ... + /// + /// + public bool EnableLogging { get; set; } + + /// + /// DefineRoutes for Handling ... + /// + /// + public HashSet Routes { get; set; } + } + + /// + /// Describe a Proxy Route ... + /// + public class XProxyRouteDescriptor : XBaseDescriptor { + /// + /// Determines which route can handled for Proxyfing ... + /// + /// + public string Route { get; set; } + + /// + /// Determines Proxy Server Handle Athorization or not ... + /// + /// + public bool EnableAuthorization { get; set; } + + /// + /// Determines Which Scopes allowed to this route if authorizations enabled ... + /// + /// + public HashSet AllowedScopes { get; set; } + + /// + /// Determines Which Roles allowed to this route if authorizations enabled ... + /// + /// + public HashSet AllowedRoles { get; set; } + + /// + /// Determines XProxy Can Check XPowered By Authorization or not ... + /// + /// + public bool EnableXPoweredByAthorization { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\ConfigurationNodeNames.cs + +namespace xIdentityService.Constants { + public partial struct ConfigurationNodeNames { + public const string XPROXY_CONFIGURATION_NODE_NAME = "ProxyConfiguration"; + public const string IDENTITY_SERVICE_NODE_NAME = "IdentityServiceConfiguration"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\XApiAccountEndpoint.cs + +using xExceptions.Attributes; + +namespace xIdentityService.Constants +{ + public enum XApiAccountEndpoint + { + // + #region Authentication ... + [StringValue("Account/DiscoveryDocument")] + DiscoveryDocument, + + [StringValue("Account/RequestScopeAccessToken")] + RequestScopeAccessToken, + + [StringValue("Account/Authenticate")] + Authenticate, + + [StringValue("Account/Login")] + Login, + + [StringValue("Account/RefreshTokens")] + RefreshTokens, + + [StringValue("Account/ChangePassword")] + ChangePassword, + + [StringValue("Account/ResetPassword")] + ResetPassword, + #endregion + + // + #region Proile ... + [StringValue("Account/Profile/{ids}/GetNames")] + GetNames, + + [StringValue("Account/Profile/GetNameIds")] + GetNameIds, + + [StringValue("Account/Profile/{userSelectByParam}")] + GetProfile, + + [StringValue("Account/Profile/Query")] + QueryProfiles, + + [StringValue("Account/Profile/Avatars/Query/{userSelectByParam}")] + QueryAvatars, + + [StringValue("Account/Profile/Update/{userSelectByParam}")] + UpdateProfile, + + [StringValue("Account/Profile/{userSelectByParam}")] + FullUpdateProfile, + + [StringValue("Account/Profile/Avatar")] + AddAvatar, + + [StringValue("Account/Profile/Avatars")] + AddAvatars, + + [StringValue("Account/Profile/Avatars/{ids}")] + RemoveAvatars, + + [StringValue("Account/Profile/Avatars/{id}/Set")] + SetAvatar, + #endregion + + // + #region Search ... + [StringValue("Account/OpenToSearch/Query")] + QueryOpenToSearchProfiles, + #endregion + + // + #region Registration ... + [StringValue("Account/CanRegisterUserName/{userName}")] + CanRegisterUserName, + + [StringValue("Account/CanRegisterMobileNumber/{mobileNumber}")] + CanRegisterMobileNumber, + + [StringValue("Account/CanRegisterEmail/{email}")] + CanRegisterEmail, + + [StringValue("Account/IsConfirmedEmail")] + IsConfirmedEmail, + + [StringValue("Account/IsConfirmedMobile")] + IsConfirmedMobile, + + [StringValue("Account/ConfirmRegistration")] + ConfirmRegistration, + + [StringValue("Account/ConfirmMobileNumber")] + ConfirmMobileNumber, + + [StringValue("Account/ConfirmEmailAddress")] + ConfirmEmailAddress, + + [StringValue("Account/RequestConfirmMobile")] + RequestConfirmMobile, + + [StringValue("Account/RequestConfirmEmail")] + RequestConfirmEmail, + + [StringValue("Account/RequestResetPassword")] + RequestResetPassword, + + [StringValue("Account/RequestConfirmRegistration")] + RequestConfirmRegistration, + + [StringValue("Account/InviteUser")] + InviteUser, + + [StringValue("Account/RequestRegistration")] + RequestRegistration, + + [StringValue("Account/AddAccountInfo")] + AddAccountInfo, + + [StringValue("Account/AttachProfileImage")] + AttachProfileImage, + + [StringValue("Account/FinishRegistration")] + FinishRegistration, + #endregion + + // + #region Friendship ... + [StringValue("Account/Friendship/{destUser}/Follow")] + Follow, + + [StringValue("Account/Friendship/{destUser}/Cancel")] + Cancel, + + [StringValue("Account/Friendship/{destUser}/UnFollowFollower")] + UnFollowFollower, + + [StringValue("Account/Friendship/{destUser}/UnFollowFollowing")] + UnFollowFollowing, + + [StringValue("Account/Friendship/{destUser}/Block")] + Block, + + [StringValue("Account/Friendship/{destUser}/UnBlock")] + UnBlock, + + [StringValue("Account/Friendship/{destUser}/AcceptRequest")] + AcceptRequest, + + [StringValue("Account/Friendship/{destUser}/RejectRequest")] + RejectRequest, + + [StringValue("Account/Friendship/{destUser}/FriendshipInfo")] + FriendshipInfo, + + [StringValue("Account/Friendship/{destUser}/IsFollower")] + IsFollower, + + [StringValue("Account/Friendship/{destUser}/FollowerState")] + FollowerState, + + [StringValue("Account/Friendship/{destUser}/Follower")] + Follower, + + [StringValue("Account/Friendship/Followers")] + Followers, + + [StringValue("Account/Friendship/AllFollowers")] + AllFollowers, + + [StringValue("Account/Friendship/FollowersList")] + FollowersList, + + [StringValue("Account/Friendship/QueryFollowers")] + QueryFollowers, + + [StringValue("Account/Friendship/{destUser}/IsFollowing")] + IsFollowing, + + [StringValue("Account/Friendship/{destUser}/FollowingState")] + FollowingState, + + [StringValue("Account/Friendship/{destUser}/Following")] + Following, + + [StringValue("Account/Friendship/Followings")] + Followings, + + [StringValue("Account/Friendship/AllFollowings")] + AllFollowings, + + [StringValue("Account/Friendship/FollowingsList")] + FollowingsList, + + [StringValue("Account/Friendship/QueryFollowings")] + QueryFollowings, + #endregion + + // + #region Admin ... + [StringValue("Account/Ban")] + Ban, + + [StringValue("Account/UnBan")] + UnBan, + + [StringValue("Account/IsBanned/{userSelectByParam}")] + IsBanned, + #endregion + + // + #region Open Actions ... + [StringValue("Account/Open")] + Open, + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\XAuthenticationScheme.cs + +using xExceptions.Attributes; + +namespace xIdentityService.Constants { + public enum XAuthenticationScheme { + [StringValue ("token")] + XToken, + [StringValue ("introspection")] + XIntrospection, + [StringValue("XApiKey")] + XApiKey, + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\XHeader.cs + +using xExceptions.Attributes; + +namespace xIdentityService.Constants +{ + /// + /// All Available Headers ... + /// + public enum XHeader + { + /// + /// Hub Connection ID Header Keys ... + /// + [StringValue("requestID")] + ConnectionId, + + /// + /// Api Key Header Key ... + /// + [StringValue("X-Api-Key")] + ApiKey, + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\XParam.cs + +using xExceptions.Attributes; + +namespace xIdentityService.Constants { + public enum XParam { + [StringValue ("Content-Type")] + XContentType, + + [StringValue ("{boundary}")] + XBoundary, + + [StringValue ("{userSelectByParam}")] + XUserSelectByParam, + + [StringValue ("{destUser}")] + XDestUser, + + [StringValue ("{id}")] + XId, + + [StringValue ("{ids}")] + XIds, + + [StringValue ("Origin")] + XOrigin, + + [StringValue ("{userName}")] + XUserName, + + [StringValue ("{mobileNumber}")] + XMobileNumber, + + [StringValue ("{email}")] + XEmail, + + [StringValue ("role")] + XRole, + + [StringValue ("forceRole")] + XForceRole, + + [StringValue ("IsRefreshed")] + XIsRefreshed, + + [StringValue ("file")] + XFile, + + [StringValue ("files")] + XFiles, + + [StringValue ("actionToken")] + XActionToken, + + [StringValue ("request")] + XRequest, + + [StringValue ("scope")] + XScope, + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\XProxyConstants.cs + +namespace xIdentityService.Constants { + public struct XProxyConstants { + public const string XPROXY_HTTP_CLIENT = "XProxyHttpClient"; + public const string XPROXY_FORWARD_HEADER = "xForwardHeader"; + public const string XPROXY_ACCEPTED_HEADERS = "xAcceptedHeaders"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\XResponseContentTypes.cs + +namespace xIdentityService.Constants { + public struct XResponseContentTypes { + public const string HTML = "text/html"; + public const string JSON = "application/json"; + public const string JavaScript = "text/javascript"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Constants\XTokenType.cs + +namespace xIdentityService.Constants { + public enum XTokenType { + // + // Summary: + // Self-contained Json Web Token + Jwt, + // + // Summary: + // Reference token + Reference + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Controllers\XBaseIdentityApiController.cs + +using xCommons.Providers; +using xCommons.Attributes; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; + +namespace xIdentityService.Controllers +{ + /// + /// Base Identity Api Controller + /// + [ApiController] + [RequireXPowered(true)] + [Route("api/[controller]")] + [Produces("application/json")] + public abstract class XBaseIdentityApiController : XBaseIdentityController + { + protected XBaseIdentityApiController( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + identityProvider, + validationProvider + ) + { } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Controllers\XBaseIdentityApiV1Controller.cs + +using xCommons.Providers; +using xCommons.Attributes; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; + +namespace xIdentityService.Controllers +{ + /// + /// Base Identity Api V1 Controller + /// + [ApiController] + [ApiVersion("1.0")] + [RequireXPowered(true)] + [Route("api/v{version:apiVersion}/[controller]")] + public abstract class XBaseIdentityApiV1Controller : XBaseIdentityApiController + { + protected XBaseIdentityApiV1Controller( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + identityProvider, + validationProvider + ) + { } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Controllers\XBaseIdentityController.cs + +using xCommons.Constants; +using xCommons.Providers; +using xCommons.Extensions; +using xCommons.Controllers; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityService.Constants; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authentication; + +namespace xIdentityService.Controllers +{ + public abstract class XBaseIdentityController : XBaseController + { + // + public IXIdentityProvider IdentityProvider { get; } + + protected XBaseIdentityController( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + validationProvider + ) + { + IdentityProvider = identityProvider; + } + + // + #region NonActions ... + /// + /// Retrieve Connection ID for Hubs ... + /// + /// + [NonAction] + public string GetConnectionId() + { + // + var result = ""; + + // + var headerKey = XHeader.ConnectionId.GetStringValue(); + + // + // Check if Connection ID is Provided ... + if (Request.Headers.ContainsKey(headerKey)) + { + result = Request.Headers[headerKey]; + } + + // + return result; + } + + /// + /// Retrieve User Identifier Base on XActionRequest + /// + /// + /// + /// + /// + [NonAction] + public string GetUserSelectByParam( + XActionRequest model, + bool forceNotNull = true, + ICollection excludes = null + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(model); + + // + var result = IdentityProvider.GetUserSelectByParam( + model, + forceNotNull, + excludes + ); + + // + return result; + } + + /// + /// Retrieve Access Token + /// + /// + [NonAction] + public async Task GetAccessToken() + { + // + var accessToken = Request.Headers[XAuthorization.Header].ToString(); + if (accessToken.IsNullOrEmpty()) + { + // + accessToken = await HttpContext + .GetTokenAsync(XAuthorization.AccessToken); + } + + // + if (accessToken + .ToNormalString() + .Contains(XAuthorization.TokenIdentifier + .ToNormalString())) + { + // + accessToken = accessToken + .Remove(0, XAuthorization.TokenIdentifier.Length); + } + + // + return accessToken; + } + + /// + /// Retrieve Refresh Token + /// + /// + [NonAction] + public async Task GetRefreshToken() + { + // + var refreshToken = Request.Headers[XAuthorization.RefreshToken].ToString(); + if (refreshToken.IsNullOrEmpty()) + { + // + refreshToken = await HttpContext + .GetTokenAsync(XAuthorization.RefreshToken); + } + + // + return refreshToken; + } + + /// + /// Retrieve Access Token Expiration Date + /// + /// + [NonAction] + public async Task GetTokenExpiresAt() + { + // + var expiresAtStr = Request.Headers[XAuthorization.ExpiresAt].ToString(); + if (expiresAtStr.IsNullOrEmpty()) + { + // + expiresAtStr = await HttpContext + .GetTokenAsync(XAuthorization.ExpiresAt); + } + + // + var expiresAt = expiresAtStr.ConvertTo(); + + // + return expiresAt; + } + + /// + /// Retrieve All Required Tokens + /// + /// + [NonAction] + public async Task RetrieveTokensAsXLoginResponse() + { + // + var accessToken = await GetAccessToken(); + var refreshToken = await GetRefreshToken(); + var expiresAt = await GetTokenExpiresAt(); + + // + return new XLoginResponse + { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + + /// + /// Retrieve All Required Tokens + /// + /// + [NonAction] + public async Task RetrieveTokensAsXTokenResponse() + { + // + var accessToken = await GetAccessToken(); + var refreshToken = await GetRefreshToken(); + var expiresAt = await GetTokenExpiresAt(); + + // + return new XTokenResponse + { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + + /// + /// Retrive UserInfo + /// + /// + [NonAction] + public async Task GetUserInfo() + { + // + var claims = User.Claims ?? null; + if (!claims.HasChild()) + { + return null; + } + + var xTokens = await RetrieveTokensAsXTokenResponse(); + var result = new XUserClaimsInfoDto( + xTokens.AccessToken, + xTokens.RefreshToken, + xTokens.ExpiresAt, + User.Claims + ); + + // + return result; + } + + /// + /// Validate User Authenticated and Retrieve User Info + /// + /// + [NonAction] + public async Task ValidateAndGetUserInfo() + { + // + if (!User.Identity.IsAuthenticated) + { + XException.NotAuthorized.Throw(); + } + + // + var result = await GetUserInfo(); + if (result.IsNull()) + { + XException.NotAuthorized.Throw(); + } + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\DI\XDIHelperExtension.cs + +using System; +using xCommons.Extensions; +using xCommons.Authorization; +using Microsoft.AspNetCore.Http; +using xIdentityService.Security; +using xIdentityService.Constants; +using xIdentityService.Providers; +using xIdentityService.Interfaces; +using Microsoft.AspNetCore.Builder; +using xIdentityService.Configuration; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using IdentityModel.AspNetCore.OAuth2Introspection; + +namespace xIdentityService.DI { + public static partial class XDIHelperExtension { + /// + /// Extract Module Configuration Structure from IConfiguration interface + /// + /// + /// + public static XIdentityServiceConfiguration GetXIdentityServiceConfiguration (this IConfiguration config) { + // + var xIdentityServiceConfigSection = config.GetSection (ConfigurationNodeNames.IDENTITY_SERVICE_NODE_NAME); + return xIdentityServiceConfigSection.Get (); + } + + /// + /// Register Service and All it's Requirements + /// + /// + /// + /// + public static void AddXIdentityService ( + this IServiceCollection services, + IConfiguration configuration, + ServiceLifetime lifeTime = ServiceLifetime.Singleton + ) { + // + var identityConfiguration = configuration.GetXIdentityServiceConfiguration (); + AddIdentityService ( + services, + identityConfiguration, + lifeTime + ); + } + + /// + /// Register Service and All it's Requirements + /// + /// + /// + /// + public static void AddXIdentityService ( + this IServiceCollection services, + XIdentityServiceConfiguration configuration, + ServiceLifetime lifeTime = ServiceLifetime.Singleton + ) { + AddIdentityService (services, configuration, lifeTime); + } + + /// + /// Register Custome Token Provider + /// + /// + /// + /// + public static void AddXTokenProvider ( + this IServiceCollection services, + ServiceLifetime lifeTime = ServiceLifetime.Singleton) + where T : IXTokenProvider { + // + Console.WriteLine ("IdentityService: Add Custom Token Provider ..."); + services.Add (new ServiceDescriptor (typeof (IXTokenProvider), typeof (T), lifeTime)); + } + + /// + /// Use Authentication + /// + /// + public static void UseXIdentityService (this IApplicationBuilder app) { + app.UseAuthentication (); + } + + // + #region Private ... + private static void AddIdentityService ( + this IServiceCollection services, + XIdentityServiceConfiguration config, + ServiceLifetime lifeTime = ServiceLifetime.Singleton + ) { + // + services.AddSingleton (config); + + // + var builder = services + .AddAuthentication (XAuthenticationScheme.XToken.GetStringValue ()); + + // + // Add JWT Handler ... + builder.AddJwtBearer ( + XAuthenticationScheme.XToken.GetStringValue (), options => { + // + options.Authority = config.Authority; + options.TokenValidationParameters.ValidateAudience = false; + options.TokenValidationParameters.ValidTypes = new [] { "at+jwt" }; + + // + options.ForwardDefaultSelector = context => { + // + var path = context.Request.Path.Value; + + // + var fromHeader = TokenRetrieval.FromAuthorizationHeader (); + var fromQuery = TokenRetrieval.FromQueryString (); + var bearerToken = fromHeader (context.Request) ?? fromQuery (context.Request); + + // + var result = XAuthenticationScheme.XToken.GetStringValue (); + if (!bearerToken.IsNull () && !bearerToken.Contains (".")) { + result = XAuthenticationScheme.XIntrospection.GetStringValue (); + } + + // + return result; + }; + }); + + // + // Add OAuthIntrospect ... + builder.AddOAuth2Introspection ( + XAuthenticationScheme.XIntrospection.GetStringValue (), options => { + // + options.Authority = config.Authority; + options.ClientId = config.ApiName; + options.ClientSecret = config.ClientSecret; + + // + options.TokenRetriever = new Func (req => { + // + var fromHeader = TokenRetrieval.FromAuthorizationHeader (); + var fromQuery = TokenRetrieval.FromQueryString (); + + // + var bearerToken = fromHeader (req) ?? fromQuery (req); + + // + return bearerToken; + }); + }); + + // + // Register Authorization Handler ... + services.AddSingleton (); + Console.WriteLine ("IdentityService: register Authorization Handlers ..."); + + // + // Register Identity Provider ... + services.Add (new ServiceDescriptor (typeof (IXIdentityProvider), typeof (XIdentityProvider), lifeTime)); + Console.WriteLine ("IdentityService: Register IdentityProvider ..."); + + // + // Try To Register InMemoryTokenProvier if it's not Provided ... + IXTokenProvider xTokenProvider = null; + try { + xTokenProvider = services.GetRegisteredService (); + } catch { } + if (xTokenProvider.IsNull ()) { + // + Console.WriteLine ("IdentityService: there is no Custom Token Provider, Adding InMemoryTokenProvider instead ..."); + services.Add (new ServiceDescriptor (typeof (IXTokenProvider), typeof (XInMemoryTokenProvider), lifeTime)); + } + + // + // Adding Security Provider ... + services.Add (new ServiceDescriptor (typeof (IXSecurityProvider), typeof (XSecurityProvider), lifeTime)); + Console.WriteLine ("IdentityService: Register Security Provider ..."); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Extensions\AuthorizationPolicyBuilderExtensions.cs + +using IdentityModel; +using Microsoft.AspNetCore.Authorization; + +namespace xIdentityService.Extensions { + public static class AuthorizationPolicyBuilderExtensions { + /// + /// Adds a policy to check for required scopes. + /// + /// + /// List of any required scopes. The token must contain at least one of the listed scopes. + /// + public static AuthorizationPolicyBuilder RequireScope (this AuthorizationPolicyBuilder builder, params string[] scope) { + return builder.RequireClaim (JwtClaimTypes.Scope, scope); + } + } + + /// + /// Helper for creating scope-related policies + /// + public static class ScopePolicy { + /// + /// Creates a policy to check for required scopes. + /// + /// List of any required scopes. The token must contain at least one of the listed scopes. + /// + public static AuthorizationPolicy Create (params string[] scopes) { + return new AuthorizationPolicyBuilder () + .RequireScope (scopes) + .Build (); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Extensions\HttpContextExtensions.cs + +using xCommons.Constants; +using xCommons.Extensions; +using System.Threading.Tasks; +using xIdentityModels.Models; +using Microsoft.AspNetCore.Http; +using xIdentityService.Constants; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; + +namespace xIdentityService.Extensions { + public static class HttpContextExtensions { + /// + /// Retrieve Authentication Service ... + /// + /// + /// + public static IAuthenticationService GetAuthenticationService (this HttpContext context) { + // + var result = context.RequestServices + .GetRequiredService (); + + // + return result; + } + + /// + /// Retrieve Specific Token from HttpContext ... + /// + /// + /// + /// + /// + public static async Task GetXTokenAsync ( + this HttpContext context, + string tokenName, + XAuthenticationScheme scheme = XAuthenticationScheme.XToken + ) { + // + // Validate Args ... + if (tokenName.IsNullOrEmpty ()) { + return null; + } + + // + var authService = context.GetAuthenticationService (); + if (authService.IsNull ()) { + return null; + } + + // + var authResult = await authService + .AuthenticateAsync ( + context, + scheme + .GetStringValue () + ); + + // + var result = authResult?.Properties? + .GetTokenValue (tokenName); + return result; + } + + /// + /// Retrieve Access Token + /// + /// + public static async Task GetAccessToken (this HttpContext context) { + // + var accessToken = context.Request.Headers[XAuthorization.Header] + .ToString (); + if (accessToken.IsNullOrEmpty ()) { + accessToken = await context + .GetTokenAsync (XAuthorization.AccessToken); + } + + // + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier + .ToNormalString ())) { + accessToken = accessToken + .Remove (0, XAuthorization.TokenIdentifier.Length); + } + + // + return accessToken; + } + + /// + /// Retrieve Refresh Token + /// + /// + public static async Task GetRefreshToken (this HttpContext context) { + // + var refreshToken = context.Request.Headers[XAuthorization.RefreshToken] + .ToString (); + if (refreshToken.IsNullOrEmpty ()) { + refreshToken = await context + .GetTokenAsync (XAuthorization.RefreshToken); + } + + // + return refreshToken; + } + + /// + /// Retrieve Access Token Expiration Date + /// + /// + public static async Task GetTokenExpiresAt (this HttpContext context) { + // + var expiresAtStr = context.Request.Headers[XAuthorization.ExpiresAt] + .ToString (); + if (expiresAtStr.IsNullOrEmpty ()) { + expiresAtStr = await context + .GetTokenAsync (XAuthorization.ExpiresAt); + } + + // + var expiresAt = expiresAtStr.ConvertTo (); + + // + return expiresAt; + } + + /// + /// Retrieve All Required Tokens + /// + /// + public static async Task RetrieveTokensAsXTokenResponse (HttpContext context) { + // + var accessToken = await GetAccessToken (context); + var refreshToken = await GetRefreshToken (context); + var expiresAt = await GetTokenExpiresAt (context); + + // + return new XTokenResponse { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Extensions\StreamExtensions.cs + +using System.IO; +using System.Linq; +using System.Threading; +using xCommons.Extensions; +using System.Collections.Generic; + +namespace xIdentityService.Extensions +{ + public static partial class StreamExtensions + { + /// + /// Converts an Stream to Async Enumerable ... + /// + /// + /// + /// + /// + public static async IAsyncEnumerable ToAsyncEnumerable( + this Stream stream, + int bufferSize = 8192, + [System.Runtime.CompilerServices.EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + var buffer = new byte[bufferSize]; + while (true) + { + // + int read = await stream + .ReadAsync( + buffer, + 0, + buffer.Length, + cancellationToken + ); + + // + if (read == 0) + { + yield break; + } + + // + yield return buffer.Take(read).ToArray(); + } + } + + /// + /// Converts an Stream to Async Enumerable ... + /// + /// + /// + /// + /// + public static async IAsyncEnumerable ToAsyncEnumerable( + this Stream stream, + int bufferSize = 8192, + [System.Runtime.CompilerServices.EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + var buffer = new byte[bufferSize]; + while (true) + { + // + int read = await stream + .ReadAsync( + buffer, + 0, + buffer.Length, + cancellationToken + ); + if (read == 0) + { + yield break; + } + + // + T chunk = default(T); + var readedBuffer = buffer.Take(read).ToArray(); + var str = readedBuffer.FromBytes(); + if (str.IsNullOrEmpty()) + { + yield return chunk; + } + try + { + chunk = str.FromJSON(); + } + catch + { } + + // + yield return chunk; + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Extensions\XModelExtensions.cs + +using xModels.Dtos; +using xIdentityModels.Dtos; + +namespace xIdentityService.Extensions +{ + public static class XModelExtensions + { + public static XPersonDto ToXPersonDto(this XOpenActionUserInfoDto source) + { + // + var result = new XPersonDto + { + Avatar = source.Avatar, + Lastname = source.Lastname, + Username = source.Username, + Firstname = source.Firstname, + }; + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Extensions\XRequestExtensions.cs + +using xCommons.Extensions; +using Microsoft.AspNetCore.Http; +using xIdentityService.Constants; + +namespace xIdentityService.Extensions +{ + public static class XRequestExtensions + { + /// + /// Extract ApiKey from Request Headers ... + /// + /// + /// + public static string ExtractApiKey(HttpRequest source) + { + // + var key = XHeader.ApiKey.GetStringValue(); + var result = source?.Headers?[key] ?? string.Empty; + return result; + } + + /// + /// Extract Client Id of Specified Request .... + /// + /// + /// + public static string ExtractClientIP(HttpRequest source) + { + // + var result = source?.HttpContext.Connection.RemoteIpAddress?.ToString(); + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Extensions\XTokenExtensions.cs + +using System; +using System.Net.Http; +using xCommons.Constants; +using xExceptions.Models; +using xCommons.Extensions; +using IdentityModel.Client; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using xIdentityService.Interfaces; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Authentication; + +public static partial class XTokenExtensions { + /// + /// Retrieve Tokens From Headers + /// + /// + /// + public static XLoginResponse RetrieveTokens (this HttpRequest source) { + // + var accessToken = source.Headers[XAuthorization.Header].ToString (); + var refreshToken = source.Headers[XAuthorization.RefreshToken].ToString (); + + // + var expiresAtStr = source.Headers[XAuthorization.ExpiresAt].ToString (); + long expiresAt = expiresAtStr.ConvertTo (); + + // + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + // + return new XLoginResponse { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + + /// + /// Retrieve Tokens From HttpContext + /// + /// + /// + public static async Task RetrieveTokens (this HttpContext source) { + // + var accessToken = await source.GetTokenAsync (XAuthorization.AccessToken); + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + var refreshToken = await source.GetTokenAsync (XAuthorization.RefreshToken); + var expiresAtStr = await source.GetTokenAsync (XAuthorization.ExpiresAt); + long expiresAt = expiresAtStr.ConvertTo (); + + // + var headerRequestTokens = source.Request.RetrieveTokens (); + + // + var result = new XLoginResponse { + AccessToken = accessToken ?? headerRequestTokens.AccessToken, + RefreshToken = refreshToken ?? headerRequestTokens.RefreshToken, + ExpiresAt = expiresAt != 0 ? expiresAt : headerRequestTokens.ExpiresAt + }; + + // + return result; + } + + /// + /// Retrieve Tokens From ActionExecutingContext + /// + /// + /// + public static async Task RetrieveTokens (this ActionExecutingContext source) { + // + var result = await source.HttpContext.RetrieveTokens (); + return result; + } + + /// + /// Retrieve Tokens From XUserInfo instance + /// + /// + /// + public static XTokenResponse RetrieveTokens (this XUserClaimsInfoDto source) { + // + var result = new XLoginResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = source.ExpiresAt + }; + + // + return result; + } + + /// + /// Retrieve Tokens From IHeader Dictionary + /// + /// + /// + public static XLoginResponse RetrieveTokens (this IHeaderDictionary source) { + // + var accessToken = source[XAuthorization.Header].ToString () ?? ""; + if (accessToken + .ToNormalString () + .Contains (XAuthorization.TokenIdentifier.ToNormalString ())) { + accessToken = accessToken.Remove (0, XAuthorization.TokenIdentifier.Length); + } + + // + var refreshToken = source[XAuthorization.RefreshToken].ToString () ?? ""; + + // + var expiresAtStr = source[XAuthorization.ExpiresAt].ToString () ?? ""; + var expiresAt = expiresAtStr.ConvertTo (); + + // + var result = new XLoginResponse { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Retrieve IXIdentityProvider from HttpContext + /// + /// + /// + public static IXIdentityProvider GetXApiIdentityProvider (this FilterContext source) { + // + var services = source.HttpContext.RequestServices; + return (IXIdentityProvider) services + .GetService (typeof (IXIdentityProvider)); + } + + /// + /// Generate XLoginResponse instance based on TokenResponse + /// + /// + /// + public static XLoginResponse CreateXLoginResponse (this TokenResponse source) { + // + if (source.IsNull () || + source.IsError) { + XException.InvalidToken.Throw (); + } + + // + var expiresAt = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds () + + source.ExpiresIn; + + // + var result = new XLoginResponse { + AccessToken = source.AccessToken, + RefreshToken = source.RefreshToken, + ExpiresAt = expiresAt + }; + + // + return result; + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + /// + public static void SetXAuthenticationTokens ( + this IHeaderDictionary source, + XLoginResponse model, + string secret = null + ) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + + // + if (!secret.IsNullOrEmpty ()) { + var revisionChecksum = model.GetRevisionChecksum (secret); + source.Add (XAuthorization.RevisionChecksum, revisionChecksum); + } + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific IHeaderDictionary instance + /// + /// + /// + public static void SetXAuthenticationTokens (this HttpRequestHeaders source, XLoginResponse model) { + // + source.Remove (XAuthorization.Header); + source.Remove (XAuthorization.RefreshToken); + source.Remove (XAuthorization.ExpiresAt); + source.Remove (XAuthorization.RevisionChecksum); + + // + source.Add (XAuthorization.Header, $"{XAuthorization.TokenIdentifier}{model.AccessToken}"); + source.Add (XAuthorization.RefreshToken, $"{model.RefreshToken}"); + source.Add (XAuthorization.ExpiresAt, $"{model.ExpiresAt}"); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens (this HttpRequest source, XLoginResponse model) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add Authorization Tokens in Headers of + /// Specific HttpRequest instance + /// + /// + /// + public static void SetXAuthenticationTokens (this HttpRequestMessage source, XLoginResponse model) { + source.Headers.SetXAuthenticationTokens (model); + } + + /// + /// Add ReNewed Authorization Tokens in Headers of + /// Specific Response. + /// Later in HttpInterceptors we can Look for AccessToken-ReNewed + /// header Value and Renew User Tokens based on it + /// + /// + /// + /// + public static void SetXAuthenticationTokens ( + this HttpResponse source, + XLoginResponse model, + string secret + ) { + source.Headers.SetXAuthenticationTokens (model, secret); + } + + /// + /// Determines a Token Can Refrsh or not + /// + /// + /// + public static bool IsRefreshable (this XLoginResponse source) { + // + var containsTokenData = !source.IsNull () && + !source.AccessToken.IsNullOrEmpty () && + !source.RefreshToken.IsNullOrEmpty (); + if (!containsTokenData) { + return false; + } + + // + var current = new DateTimeOffset (DateTime.UtcNow) + .ToUnixTimeSeconds (); + + // + // var result = current > source.ExpiresAt || current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + var result = current > source.ExpiresAt - XAuthorization.ThresholdBeforeTokenExpiration; + + // + return result; + } + + /// + /// Generate a Revision Checksum for new Tokens + /// + /// + /// + /// + public static string GetRevisionChecksum (this XLoginResponse source, string secret) { + return (source.AccessToken + + secret + + source.RefreshToken + + secret + + source.ExpiresAt.ToString () + ) + .ToMd5String (); + } + + /// + /// Generate a Revision Checksum for new Tokens + /// + /// + /// + /// + public static string GetRevisionChecksum (this XTokenResponse source, string secret) { + return (source.AccessToken + + secret + + source.RefreshToken + + secret + + source.ExpiresAt.ToString () + ) + .ToMd5String (); + } + + /// + /// Check an XLoginResponse instance is Valid with it's revision + /// + /// + /// + /// + /// + public static bool ValidateRevisionChecksum (this XLoginResponse source, string checksum, string secret) { + // + var xChecksum = source.GetRevisionChecksum (secret); + return checksum == xChecksum; + } + + /// + /// Check an XTokenResponse instance is Valid with it's revision + /// + /// + /// + /// + /// + public static bool ValidateRevisionChecksum (this XTokenResponse source, string checksum, string secret) { + // + var xChecksum = source.GetRevisionChecksum (secret); + return checksum == xChecksum; + } + + /// + /// Retrieve Exception Details if Fails + /// + /// + /// + public static Exception GetException (this TokenResponse source) { + // + // Validate Args ... + if (source.IsNull () || !source.IsError) { + return XException.ActionFailed.ToException (); + } + + // + XError error = source.ErrorDescription.FromJSON (); + return error.ToException (); + } + + /// + /// Convert XTokenResponse to Header Dictionary + /// + /// + /// + public static IDictionary ToHeaderDictionary (this XTokenResponse source) { + // + var result = new Dictionary { { XAuthorization.RefreshToken, source.RefreshToken }, + { XAuthorization.ExpiresAt, source.ExpiresAt.ToString () } + }; + + // + return result; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Extensions\XUserClaimsInfoDtoExtensions.cs + +using xCommons.Extensions; +using xIdentityModels.Models; +using xIdentityModels.Constants; + +namespace xIdentityService.Extensions { + public static class XUserClaimsInfoDtoExtensions { + public static string GetUserSelectByParam ( + this XUserClaimsInfoDto source, + XUserSelectBy userSelectBy = XUserSelectBy.Username + ) { + // + var result = ""; + + // + switch (userSelectBy) { + // + case XUserSelectBy.ID: + result = source.UserId; + break; + + // + case XUserSelectBy.Username: + result = source.UserName; + break; + + // + case XUserSelectBy.Email: + result = source.Email; + break; + + // + case XUserSelectBy.MobileNumber: + result = source.PhoneNumber; + break; + } + + // + if (result.IsNullOrEmpty ()) { + // + if (!source.UserId.IsNullOrEmpty ()) { + result = source.UserId; + } else if (!source.UserName.IsNullOrEmpty ()) { + result = source.UserName; + } else if (!source.Email.IsNullOrEmpty ()) { + result = source.Email; + } else if (!source.PhoneNumber.IsNullOrEmpty ()) { + result = source.PhoneNumber; + } + } + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXBaseIdentityHttpProvider.cs + +using System; +using System.IO; +using RestSharp; +using System.Net.Http; +using System.Threading; +using xCommons.Providers; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using xIdentityService.Models; +using xIdentityModels.Constants; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace xIdentityService.Interfaces +{ + /// + /// an interface for Identity Provided Http Based Services ... + /// + public interface IXBaseIdentityHttpProvider + { + /// + /// Client Base Url ... + /// + /// + string BaseUrl { get; } + + /// + /// Logger ... + /// + /// + ILogger Logger { get; } + + /// + /// Powered Value ... + /// + /// + string XPoweredValue { get; } + + /// + /// Default Client Timeout ... + /// + /// + int DefaultClientTimeout { get; } + + /// + /// Default Buffering Chunk Size ... + /// + /// + int DefaultBufferingChunckSize { get; } + + /// + /// Validation Provider ... + /// + /// + XValidationProvider ValidationProvider { get; } + + /// + /// Get an Instance of Http Client + /// + /// + HttpClient GetHttpClient(XTokenResponse tokens = null); + HttpClient GetHttpClient(XUserClaimsInfoDto userInfo = null); + + /// + /// Get an Instance of RestClient + /// + /// + RestClient GetRestClient(XTokenResponse tokens = null); + RestClient GetRestClient(XUserClaimsInfoDto userInfo = null); + + /// + /// Get an Instance of RestRequest + /// + /// + /// + /// + /// + /// + /// + RestRequest GetRestRequet( + Enum endpoint, + bool addXPoweredValue = true, + IDictionary @params = null, + IDictionary headers = null, + IDictionary queryStrings = null + ); + + /// + /// Handle Call Specific Request and retrieve Response + /// + /// + /// + /// + /// + /// + /// + /// + Task RunRestRequest( + RestClient client, + RestRequest request, + XHttpMethod method, + bool supportRefreshingTokens = true, + CancellationToken cancellationToken = default + ); + + /// + /// Access a Get Request Stream as AsyncEnumerable ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task StreamData( + Enum endpoint, + int bufferLength = 8194, + bool addXPoweredValue = true, + XTokenResponse tokens = null, + IDictionary @params = null, + IDictionary headers = null, + IDictionary queryStrings = null, + CancellationToken cancellationToken = default + ); + + /// + /// Handle Call Specified Request and Retrieve XBaseRestResponse ... + /// + /// + /// + /// + /// + /// + /// + /// an instance of XBaseRestResponse + Task> RunRestRequestByResponse( + RestClient client, + RestRequest request, + XHttpMethod method, + bool supportRefreshingTokens = true, + CancellationToken cancellationToken = default + ); + + /// + /// Get User SelectBy Param + /// + /// + /// + /// + /// + string GetUserSelectByParam( + XActionRequest model, + bool forceNotNull = true, + ICollection excludes = null + ); + + /// + /// Converts UserInfo to TokenResponse model ... + /// + /// + /// + XTokenResponse ToTokenResponse(XUserClaimsInfoDto userInfo = null); + + /// + /// Prepare Url Address of Specified Enum by Filling Params + /// and Query Strings Attach ... + /// + /// + /// + /// + /// + /// + string PrepareUrl( + Enum endpoint, + bool attachBaseUrl = false, + IDictionary @params = null, + IDictionary queryStrings = null + ); + + /// + /// Prepare Url Address by Filling Params + /// and Query Strings Attach ... + /// + /// + /// + /// + /// + /// + string PrepareUrl( + string url, + bool attachBaseUrl = false, + IDictionary @params = null, + IDictionary queryStrings = null + ); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXBaseReferenced.cs + +using xModels.Base; +using System.Threading; +using xDataService.Models; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Collections.Generic; + +namespace xIdentityService.Interfaces +{ + /// + /// a Referenced Service is a Service which can Referenced to other Items ... + /// + public interface IXBaseReferenced + where TEntity : XBaseEntity + { + /// + /// Get Reference Identifier for Specified Provider and Specified Item ... + /// + /// + /// + /// + string GetIdentifier( + string providedFor, + TReferenceKey forProvidedId + ); + + /// + /// Check Specified Item has Refernce to Provider ... + /// + /// + /// + /// + /// + /// + Task IsProvidedFor( + string providedFor, + TReferenceKey forProvidedId, + TKey id, + CancellationToken cancellationToken = default + ); + + /// + /// Add Specified Reference to Specified Item ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task AddReference( + string providedFor, + TReferenceKey forProvidedId, + TKey id, + int forIndex = 0, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ); + + /// + /// Remove Specified Reference from Specified Item ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task RemoveReference( + string providedFor, + TReferenceKey forProvidedId, + TKey id, + int forIndex = 0, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ); + + /// + /// Get Specified References ... + /// + /// + /// + /// + /// + Task>> GetReferences( + string providedFor, + TKey id, + CancellationToken cancellationToken = default + ); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXBaseReferencedController.cs + +using xModels.Base; +using System.Threading; +using xDataService.Models; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; + +namespace xIdentityService.Interfaces +{ + public interface IXBaseReferencedController + where TEntity : XBaseEntity + { + /// + /// Check an Item Has Reference to Specified ProvideFor and Specified forProvidedId ... + /// + /// + /// + /// + /// + /// + [HttpGet("{id}/Reference/IsProvidedFor")] + Task> IsProvidedFor( + [FromRoute] TKey id, + [FromQuery] string providedFor, + [FromQuery] object forProvidedId, + CancellationToken cancellationToken = default + ); + + /// + /// Add a Reference to Item for Specified ProvideFor and Specified forProvidedId ... + /// + /// + /// + /// + /// + /// + /// + [HttpPost("{id}/Reference")] + Task AddReference( + [FromRoute] TKey id, + [FromQuery] string providedFor, + [FromQuery] object forProvidedId, + [FromQuery] int forIndex = 0, + CancellationToken cancellationToken = default + ); + + /// + /// Remove a Reference from Item for Specified ProvideFor and Specified forProvidedId ... + /// + /// + /// + /// + /// + /// + /// + [HttpDelete("{id}/Reference")] + Task RemoveReference( + [FromRoute] TKey id, + [FromQuery] string providedFor, + [FromQuery] object forProvidedId, + [FromQuery] int forIndex = 0, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve References of Specified Itemfor providedFor ... + /// + /// + /// + /// + /// + [HttpGet("{id}/References")] + Task>>> GetReferences( + [FromRoute] TKey id, + [FromQuery] string providedFor, + CancellationToken cancellationToken = default + ); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXIdentityBaseReferencedProvider.cs + +using xModels.Dtos; +using xDataService.Models; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Collections.Generic; + +namespace xIdentityService.Interfaces +{ + /// + /// in Some Cases we have Entities Which Can Referenced for Specified + /// Provider and Types ... + /// this interface Globaly introduce all available Actions for those ... + /// + /// TKey: type of Provider Models Keys ... + /// TModel: type of Dto which Referenced ... + /// + public interface IXIdentityBaseReferencedProvider + { + // + #region Props ... + /// + /// Provider Identifier ... + /// + string Provider { get; } + #endregion + + // + #region Referenced Actions ... + /// + /// Get Specified Indexed Reference for Specific Provided ID ... + /// + /// + /// + /// + Task GetReference( + TKey providedForId, + int forIndex = 0 + ); + + /// + /// Retrieve all Exists References of Specific Provided ID ... + /// + /// + /// + Task> GetAllReferences(TKey providedForId); + + /// + /// Extract all Referencing Models for Specified Key ... + /// + /// + /// + Task>> GetAllReferencings(TKey providedForId); + + /// + /// Count References ... + /// + /// + /// + Task CountReferences(TKey providedForId); + + /// + /// Retrieve References of Specific Provided ID as Query ... + /// + /// + /// + /// + Task> QueryReferences( + XQuery query, + TKey providedForId + ); + + /// + /// Add Reference a Model to Specific Provided ID ... + /// + /// + /// + /// + /// + /// + Task AddReference( + TModel model, + TKey providedForId, + string connectionId = null, + XUserClaimsInfoDto userInfo = null + ); + + /// + /// Add Reference a Model to Specific XRefence ... + /// + /// + /// + /// + /// + /// + Task AddReference( + TModel model, + XReference reference, + string connectionId = null, + XUserClaimsInfoDto userInfo = null + ); + + /// + /// Remove Reference a Model for Specific Provided ID ... + /// + /// + /// + /// + /// + /// + Task RemoveReference( + TModel model, + TKey providedForId, + string connectionId = null, + XUserClaimsInfoDto userInfo = null + ); + + /// + /// Remove Reference a Model for Specific XRefence ... + /// + /// + /// + /// + /// + /// + Task RemoveReference( + TModel model, + XReference reference, + string connectionId = null, + XUserClaimsInfoDto userInfo = null + ); + + /// + /// Remove All References to Specified Key ... + /// + /// + /// + /// + /// + Task RemoveReferences( + TKey providedForId, + string connectionId = null, + XUserClaimsInfoDto userInfo = null + ); + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXIdentityProvider.cs + +using RestSharp; +using xModels.Dtos; +using IdentityModel.Client; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityModels.Navigations; + +namespace xIdentityService.Interfaces +{ + public partial interface IXIdentityProvider + { + // + string BaseUrl { get; } + string RevisionSecretKey { get; } + IXSecurityProvider SecurityProvider { get; } + + // + #region Tools ... + string GetUserSelectByParam( + XActionRequest model, + bool forceNotNull = true, + ICollection excludes = null + ); + #endregion + + // + #region Tools ... + Task RunRestRequest( + RestClient client, + RestRequest request, + XHttpMethod method, + bool supportRefreshingTokens = false + ); + #endregion + + // + #region Authentication ... + Task RequestDiscoveryDocument(); + Task RequestScopeAccessToken(string scope); + Task Introspection(XTokenResponse model); + Task Login(XLoginRequest model); + Task Logout(XTokenResponse model); + Task Authenticate(XLoginRequest model); + Task RefreshTokens(XTokenResponse model); + + Task ChangePassword( + XTokenResponse tokens, + XActionRequest model + ); + #endregion + + // + #region Profile ... + Task> GetUserNames( + XTokenResponse tokens, + string userIds + ); + + Task> GetUserNameIds( + XTokenResponse tokens, + XUserNameIdRequest model + ); + + Task GetUserProfile( + XTokenResponse tokens, + string userSelectByParam + ); + + Task> QueryUsers( + XTokenResponse tokens, + XQuery query + ); + + Task> QueryInRoleUsers( + XTokenResponse tokens, + string role, + XQuery query, + bool forceRole = false + ); + + Task> QueryAvatars( + XTokenResponse tokens, + string userSelectByParam, + XQuery query + ); + + Task ProfileUpdateAsync( + XTokenResponse tokens, + string userSelectByParam, + XProfileUpdateRequest model + ); + + Task FullProfileUpdateAsync( + XTokenResponse tokens, + string userSelectByParam, + XProfileUpdateRequest request + ); + + Task AddAvatar( + XTokenResponse tokens, + IFormFile file + ); + + Task AddAvatars( + XTokenResponse tokens, + IFormFileCollection files + ); + + Task SetAvatar( + XTokenResponse tokens, + int avatarId + ); + + Task RemoveAvatars( + XTokenResponse tokens, + string avatarIds + ); + #endregion + + // + #region Search ... + /// + /// Query Open To Search User Profiles ... + /// + /// requested user's identifiers + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XUserProfileDto + Task> QueryOpenToSearchUsers( + XTokenResponse tokens, + XQuery query + ); + #endregion + + // + #region Registration ... + Task CanRegister(string userSelectByParam); + + Task InviteUser( + XTokenResponse tokens, + XActionRequest model + ); + + Task RequestRegistration( + XActionRequest model + ); + + Task AddAccountInfo( + XActionRequest model + ); + + Task AttachProfileImage( + string actionToken, + IFormFile file + ); + + Task FinishRegistration(XActionRequest model); + + Task IsConfirmedEmail( + XTokenResponse tokens, + string userSelectByParam + ); + + Task IsConfirmedMobile( + XTokenResponse tokens, + string userSelectByParam + ); + + Task RequestConfirmRegistration(XActionRequest model); + + Task ConfirmRegistration( + XActionRequest model + ); + + Task RequestConfirmMobile(XActionRequest model); + + Task ConfirmMobileNumber(XActionRequest model); + + Task RequestConfirmEmail(XActionRequest model); + + Task ConfirmEmailAddress(XActionRequest model); + + Task RequestResetPassword(XActionRequest model); + + Task ResetPassword(XActionRequest model); + #endregion + + // + #region Friendship ... + Task Follow( + XTokenResponse tokens, + string destUser + ); + + Task Cancel( + XTokenResponse tokens, + string destUser + ); + + Task UnFollowFollower( + XTokenResponse tokens, + string destUser + ); + + Task UnFollowFollowing( + XTokenResponse tokens, + string destUser + ); + + Task Block( + XTokenResponse tokens, + string destUser + ); + + Task UnBlock( + XTokenResponse tokens, + string destUser + ); + + Task AcceptRequest( + XTokenResponse tokens, + string destUser + ); + + Task RejectRequest( + XTokenResponse tokens, + string destUser + ); + + Task GetFriendshipInfo( + XTokenResponse tokens, + string destUser + ); + + Task IsFollower( + XTokenResponse tokens, + string destUser + ); + + Task GetFollower( + XTokenResponse tokens, + string destUser + ); + + Task GetFollowerState( + XTokenResponse tokens, + string destUser + ); + + Task> GetFollowers(XTokenResponse tokens); + + Task> GetAllFollowers(XTokenResponse tokens); + + Task> GetFollowingList(XTokenResponse tokens); + + Task> QueryFollowers( + XQuery query, + string destUser, + XTokenResponse tokens + ); + + Task IsFollowing( + XTokenResponse tokens, + string destUser + ); + + Task GetFollowing( + XTokenResponse tokens, + string destUser + ); + + Task GetFollowingState( + XTokenResponse tokens, + string destUser + ); + + Task> GetFollowings(XTokenResponse tokens); + + Task> GetAllFollowings(XTokenResponse tokens); + + Task> GetFollowersList(XTokenResponse tokens); + + Task> QueryFollowings( + XQuery query, + string destUser, + XTokenResponse tokens + ); + #endregion + + // + #region Admin ... + Task> Ban( + XTokenResponse tokens, + XUserNameIdRequest model + ); + + Task> UnBan( + XTokenResponse tokens, + XUserNameIdRequest model + ); + + Task IsBanned( + XTokenResponse tokens, + string userSelectByParam + ); + #endregion + + // + #region Open Actions ... + // + XDeviceDto GetDevice(); + + // + Task OpenGet( + XOpenActionInnerDto model + ); + + // + Task GetUserInfo( + XDeviceDto device, + string userSelectByParam + ); + Task GetUserInfo( + XOpenActionInnerDto dto + ); + Task GetUserInfo( + string token, + string request + ); + + // + Task> GetUserInfos( + XDeviceDto device, + string ids + ); + Task> GetUserInfos( + XOpenActionInnerDto dto + ); + Task> GetUserInfos( + string token, + string request + ); + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXProxyHelper.cs + +using System; +using System.Linq.Expressions; +using System.Collections.Generic; +using xIdentityService.Configuration; + +namespace xIdentityService.Interfaces { + public interface IXProxyHelper { + /// + /// Proxy Configuration ... + /// + /// + XProxyConfiguration Configuration { get; } + + /// + /// retrie Normalize Host ... + /// + /// + string NormalizeHost (); + + /// + /// Validate XProxy Configuration for Client ... + /// + /// + bool ValidateConfiguration (); + + /// + /// Retrieve all Prepared url of Routes ... + /// + /// + IEnumerable GetRouteUrls (); + + /// + /// retrieve all registred Routes ... + /// + /// + IEnumerable GetRoutes (); + + /// + /// Prepare a request url for a Proxy Route ... + /// + /// + /// + /// + string PrepareActionUrlForXProxyRoute ( + string url, + XProxyRouteDescriptor descriptor + ); + + /// + /// Prepare a request url for a Selected Proxy Route ... + /// + /// + /// + /// + string PrepareActionUrlForXProxyRoute ( + string url, + Expression> whereClause + ); + + /// + /// Find an Specific Route ... + /// + /// + /// + XProxyRouteDescriptor FindRoute (Expression> whereClause); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXSecurityProvider.cs + +namespace xIdentityService.Interfaces +{ + public partial interface IXSecurityProvider + { + string Encrypt(string plainText); + string Decrypt(string cipherText); + string EncryptFromBytes(byte[] textBytes); + string DecryptFromBytes(byte[] cipherBytes); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Interfaces\IXTokenProvider.cs + +using System.Threading.Tasks; +using xIdentityModels.Models; + +namespace xIdentityService.Interfaces { + public partial interface IXTokenProvider { + Task IsTokenExists (string accessToken); + Task IsTokenExists (XTokenResponse tokens); + Task RemoveToken (string accessToken); + Task RemoveToken (XTokenResponse tokens); + Task RetrieveToken (string accessToken); + Task AddToken (XTokenResponse tokens); + Task UpdateToken (XTokenResponse tokens); + Task AddOrUpdateToken (XTokenResponse tokens); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Middlewares\XIdentityTokenRefresher.cs + +using System; +using xCommons.Constants; +using xCommons.Extensions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Extensions; +using xIdentityService.Interfaces; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Logging; + +namespace xIdentityService.Middlewares { + public partial class XIdentityTokenRefresherMiddleware { + // + #region Properties ... + private readonly ILogger logger; + private readonly RequestDelegate next; + private readonly IXTokenProvider tokenProvider; + private readonly IXIdentityProvider identityProvider; + #endregion + + // + #region Constructor ... + public XIdentityTokenRefresherMiddleware ( + RequestDelegate next, + ILoggerFactory loggerFactory, + IXTokenProvider tokenProvider, + IXIdentityProvider identityProvider + ) { + // + this.next = next; + this.tokenProvider = tokenProvider; + this.identityProvider = identityProvider; + this.logger = loggerFactory + .CreateLogger (); + } + #endregion + + // + #region Actions ... + public async Task Invoke (HttpContext context) { + // + // Handle Request ... + await HandleRequest (context); + + // + // Do Main Task ... + try { + await next.Invoke (context); + } catch (Exception ex) { + logger.LogError ($"Error: {ex.Message}"); + } + + // + // Handle Response ... + await HandleResponse (context); + } + #endregion + + // + #region Private ... + private async Task HandleRequest (HttpContext context) { + // + // Log ... + logger.LogInformation ($"Start Process Request ..."); + + // + // Check Access Token ... + var hasAccessToken = HasAccessToken (context); + if (!hasAccessToken) { + return; + } + + // + // Check Access Token ... + var accessToken = GetAccessToken (context); + var isExists = await tokenProvider + .IsTokenExists (accessToken); + if (!isExists) { + return; + } + + // + // Try Check is Refreshable ... + var tokens = await tokenProvider + .RetrieveToken (accessToken); + if (tokens.IsNull ()) { + return; + } + + // + // Check is Expired or Refreshable ... + var isRefreshable = tokens.IsRefreshable (); + if (!isRefreshable) { + return; + } + + // + // Refresh Token ... + var refreshedTokens = await identityProvider + .RefreshTokens (tokens); + if (refreshedTokens.IsNull ()) { + return; + } + + // + // Set Refreshed AccessToken to Request ... + context + .Request + .Headers[XAuthorization.Header] = + $"{XAuthorization.TokenIdentifier}{refreshedTokens.AccessToken}"; + } + + private async Task HandleResponse (HttpContext context) { + // + // Log ... + logger.LogInformation ($"Start Process Response ..."); + + // + await Task.Run (() => { }); + } + + private bool HasAccessToken (HttpContext context) { + // + // Check Headers ... + if (context.IsNull () || + context.Request.IsNull () || + context.Request.Headers.IsNull ()) { + return false; + } + + // + var result = context + .Request + .Headers + .ContainsKey ( + XAuthorization.Header + ); + + // + return result; + } + + private string GetAccessToken (HttpContext context) { + // + var hasAccessToken = HasAccessToken (context); + if (!hasAccessToken) { + return string.Empty; + } + + // + // Get Result ... + var result = context + .Request + .Headers[ + XAuthorization.Header + ]; + result = result + .ToString () + .Replace ( + XAuthorization.TokenIdentifier, + "" + ); + + // + return result; + } + #endregion + } + + /// + /// Provide all DI Requirements for XIdentityTokenRefresher Middleware + /// + public static class XIdentityTokenRefresherDIHelper { + /// + /// Use XTokenValidator Middleware + /// + /// + public static void UseXIdentityTokenRefresher (this IApplicationBuilder app) { + app.UseMiddleware (); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Middlewares\XProxyMiddleware.cs + +using System; +using System.Net; +using System.Web; +using System.Linq; +using System.Text; +using xModels.Base; +using IdentityModel; +using System.Net.Http; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using xIdentityService.Constants; +using xIdentityService.Providers; +using xIdentityService.Interfaces; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Logging; +using xIdentityService.Configuration; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace xIdentityService.Middlewares { + /// + /// Proxy Server Middleware ... + /// + public class XProxyMiddleware : XBaseClass { + // + #region Pros ... + private readonly HttpClient HTTP_CLIENT; + private readonly RequestDelegate NEXT_MIDDLEWARE; + private readonly XProxyConfiguration CONFIGURATION; + #endregion + + // + #region Constructor ... + public XProxyMiddleware ( + ILoggerFactory loggerFactory, + RequestDelegate nextMiddleware, + XProxyConfiguration configuration, + IHttpClientFactory httpClientFactory + ) : base (loggerFactory) { + // + this.CONFIGURATION = configuration; + this.NEXT_MIDDLEWARE = nextMiddleware; + + // + // Create Http Client ... + this.HTTP_CLIENT = httpClientFactory + .CreateClient (XProxyConstants.XPROXY_HTTP_CLIENT); + + // + // Logging Server Configuration ... + LogXProxyServerConfig (); + } + #endregion + + // + #region Public ... + public async Task InvokeAsync (HttpContext context) { + // + #region Bussiness Logic ... + // + var routeDescriptor = GetRouteDescriptor (context.Request); + if (!routeDescriptor.IsNull ()) { + // + #region Handle Authorization ... + // + try { + // + // Handle XPowered ... + if (routeDescriptor.EnableXPoweredByAthorization) { + // + var isPoweredByPass = context.Request.Headers + .Any (h => h.Key + .ToNormalString () == xCommons.Constants.XAuthorization.XPoweredBy + .ToNormalString () && + h.Value + .ToString () + .ToNormalString () == CONFIGURATION.XPoweredValue + .ToNormalString () + ); + + // + if (!isPoweredByPass) { + ThrowNotAuthorizedException (); + } + } + + // + // Handle EndableAuthorization ... + if (routeDescriptor.EnableAuthorization) { + // + // Retrieve User Info for Authentication ... + var userName = context.User.Identity.Name; + var isUserAuthenticated = !userName.IsNull () && + context.User.Identity.IsAuthenticated; + if (!isUserAuthenticated && + !routeDescriptor.AllowedScopes.HasChild () + ) { + ThrowNotAuthorizedException (); + } + + // + // Check Allowed Scopes Authorization ... + if (routeDescriptor.AllowedScopes.HasChild ()) { + // + var scopeClaims = context.User.Claims + .Where (c => c.Type == JwtClaimTypes.Scope) + .Select (c => c.Value); + var isScopePassed = scopeClaims.HasChild () && + scopeClaims.Any (c => routeDescriptor.AllowedScopes + .Any (allowedScope => allowedScope + .ToNormalString () == c + .ToNormalString () + ) + ); + if (!isScopePassed) { + ThrowNotAuthorizedException (); + } + } + + // + // Check Allowed Roles Authorization ... + if ( + isUserAuthenticated && + routeDescriptor.AllowedRoles.HasChild () + ) { + // + var roleClaim = context.User.Claims + .FirstOrDefault (c => c.Type == JwtClaimTypes.Role); + var isRolePassed = !roleClaim.IsNull () && + routeDescriptor.AllowedRoles + .Any (role => role + .ToNormalString () == roleClaim.Value + .ToNormalString ()); + if (!isRolePassed) { + ThrowNotAuthorizedException (); + } + } + } + } catch { + context.Response.StatusCode = (int) HttpStatusCode.Unauthorized; + return; + } + #endregion + + // + // Extract Request Uri from Received Request ... + var requestUri = GetUri (context.Request); + if (!requestUri.IsNull ()) { + // + // Retrieve Request based on current ... + var request = GetRequestMessage ( + uri: requestUri, + context: context + ); + + // + SendLog ($"Send Request to Recieve Response ...", LogLevel.Information); + + // + // Send Request and Retrieve Response ... + try { + // + var response = await HTTP_CLIENT.SendAsync ( + request, + HttpCompletionOption.ResponseContentRead + ); + + // + // Set Current Response Status Code ... + context.Response.StatusCode = (int) response.StatusCode; + + // + // Retrieve Response Headers and Set To Current Response ... + HandleResponseHeaders (context, response); + + // + // Processing Response Content ... + await ProcessResponseContent (context, response); + } catch (Exception ex) { + // + LogMessage ($"Exception: {ex.Message}", LogLevel.Error); + + // + context.Response.StatusCode = (int) HttpStatusCode.BadRequest; + } + + // + return; + } + } + #endregion + + // + await NEXT_MIDDLEWARE (context); + } + #endregion + + // + #region Private ... + /// + /// Throw Required Exception ... + /// + private void ThrowNotAuthorizedException () { + throw XException.NotAuthorized.ToException (); + } + + /// + /// Show propper logs if Enabled ... + /// + /// + /// + private void SendLog (string message, LogLevel logLevel = LogLevel.Information) { + // + if (!CONFIGURATION.EnableLogging) { + return; + } + + // + LogMessage ( + message: message, + logLevel: logLevel + ); + } + + /// + /// Log Proxy Server Configuration ... + /// + private void LogXProxyServerConfig () { + // + // Retrieve XProxyConfiguration class from Configurations and Validate them ... + if (CONFIGURATION.IsNull ()) { + throw XException.InvalidConfiguration.ToException (); + } + + // + // Try to Log Proxy Configuration ... + ConsoleMessage ("==============================="); + ConsoleMessage ("= Proxy Server Configurations: "); + ConsoleMessage ("==============================="); + + // + ConsoleMessage ($"Host: {CONFIGURATION.Host}"); + ConsoleMessage ($"XPoweredValue: {CONFIGURATION.XPoweredValue}"); + ConsoleMessage ($"EnableLogging: {CONFIGURATION.EnableLogging}"); + ConsoleMessage ($"DisableSSLCheck: {CONFIGURATION.DisableSSLCheck}"); + ConsoleMessage ($"AllowAutoRedirect: {CONFIGURATION.AllowAutoRedirect}"); + ConsoleMessage ($" "); + ConsoleMessage ($"Routes: "); + ConsoleMessage ($" "); + + // + CONFIGURATION.Routes + .ToList () + .ForEach (routeDescriptor => { + ConsoleMessage ("==============================="); + ConsoleMessage ($"= Rote => {routeDescriptor.Route}"); + ConsoleMessage ("==============================="); + ConsoleMessage ($"AllowedRoles: {routeDescriptor.AllowedRoles.ToJSON()}"); + ConsoleMessage ($"AllowedScopes: {routeDescriptor.AllowedScopes.ToJSON()}"); + ConsoleMessage ($"EnableAuthorization: {routeDescriptor.EnableAuthorization}"); + ConsoleMessage ($"EnableXPoweredByAthorization: {routeDescriptor.EnableXPoweredByAthorization}"); + ConsoleMessage ($" "); + }); + } + + /// + /// Retrieve Destination Request Path based on Current Request ... + /// + /// + /// + private Uri GetUri (HttpRequest request) { + // + // Create temp result ... + Uri result = null; + var requestPath = ""; + + // + SendLog ($"Receive Request: {request.Path} ...", LogLevel.Information); + + // + // Try to Findout Route Descriptor ... + var routeDescriptor = GetRouteDescriptor (request); + + // + // Route Descriptor Exists && Path for Delegating ... + if (!routeDescriptor.IsNull () && + request.Path + .StartsWithSegments (routeDescriptor.Route) + ) { + // + // Prepare Request Path by Cleaning Starter Path Segment ... + requestPath = request.Path + .ToString () + .Replace (routeDescriptor.Route, ""); + if (requestPath.StartsWith ("/")) { + requestPath = requestPath.Substring (1); + } + + // + // Decoding URL ... + requestPath = HttpUtility.UrlDecode (requestPath); + + // + var colonIndex = requestPath.IndexOf (":"); + if (colonIndex > -1) { + // + var doubleSlash = requestPath.Substring (colonIndex + 1, 2); + var isDoubleSlash = doubleSlash == "//"; + + // + if (!isDoubleSlash) { + // + var protocol = requestPath + .Substring (0, colonIndex); + var section = requestPath + .Substring (colonIndex + 2, requestPath.Length - colonIndex - 2); + + // + requestPath = $"{protocol}://{section}"; + } + } + } + + // + // Validate Request Path ... + if (!requestPath.IsNullOrEmpty ()) { + // + result = new Uri (requestPath); + + // + SendLog ($"Process Request: {requestPath} ...", LogLevel.Information); + } + + // + return result; + } + + /// + /// Retrieve Propper Request Message based on HttpContext and uri ...F + /// + /// + /// + /// + private HttpRequestMessage GetRequestMessage ( + HttpContext context, + Uri uri + ) { + // + var result = new HttpRequestMessage (); + FillRequest (context, result); + + // + // Handle Queries ... + SendLog ($"Start Processing Queries: "); + foreach (var query in context.Request.Query) { + // + var key = query.Key; + var value = HttpUtility.UrlDecode (query.Value); + + // + uri = new Uri ( + QueryHelpers.AddQueryString ( + uri.OriginalString, + new Dictionary { + [key] = value + } + ) + ); + + // + SendLog ($"Query : {key}:{value} was Processed ..."); + } + + // + result.RequestUri = uri; + result.Headers.Host = uri.Host; + + // + // Retrieve Request Method ... + result.Method = GetMethod (context.Request.Method); + + // + return result; + } + + /// + /// Fill Request Message by provide HttpContext ... + /// + /// + /// + private void FillRequest ( + HttpContext context, + HttpRequestMessage message + ) { + // + var requestMethod = context.Request.Method; + + // + if (!HttpMethods.IsGet (requestMethod) && + !HttpMethods.IsHead (requestMethod) && + !HttpMethods.IsTrace (requestMethod) && + !HttpMethods.IsDelete (requestMethod) + ) { + // + var streamContent = new StreamContent (context.Request.Body); + message.Content = streamContent; + } + + // + // Handle Base Headers ... + var regularHeaders = context.Request.Headers + .Where (hr => hr.Key != XProxyConstants.XPROXY_ACCEPTED_HEADERS && + !hr.Key + .Contains ( + XProxyConstants.XPROXY_FORWARD_HEADER + ) + ); + + // + // Handle Aceepted Headers ... + IEnumerable acceptedHeaders = null; + var acceptedHeadersKeyValue = context.Request.Headers + .FirstOrDefault (hr => + hr.Key == XProxyConstants.XPROXY_ACCEPTED_HEADERS + ); + if (!acceptedHeadersKeyValue.IsNull ()) { + // + acceptedHeaders = acceptedHeadersKeyValue.Value + .ToString () + .ParseListString (); + + // + // Filter Regular Headers ... + if ( + regularHeaders.HasChild () && + acceptedHeaders.HasChild () + ) { + // + regularHeaders = regularHeaders + .Where (rhr => acceptedHeaders + .Contains (rhr.Key) + ); + } + } + foreach (var header in regularHeaders) { + // + SendLog ($"Adding Header: {header.Key}:{header.Value} ..."); + + // + message.Headers.Add ( + header.Key, + header.Value + .ToString () + ); + } + + // + // Handle ForWarded Headers ... + // Since Forward Headers is Custom kind, this means user actually need this + // Header, so there is no need to put them on AcceptedHeaders ... + var forwardHeaders = context.Request.Headers + .Where (hr => hr.Key + .Contains ( + XProxyConstants.XPROXY_FORWARD_HEADER + ) + ); + foreach (var header in forwardHeaders) { + // + var key = header.Key + .Replace ( + XProxyConstants.XPROXY_FORWARD_HEADER, + string.Empty + ); + + // + SendLog ($"Forwarding Header: {header.Key} to {key} ..."); + + // + message.Headers + .Add ( + key, + header.Value + .ToString () + ); + } + } + + /// + /// Retreive and Extract Method Type of HttpRequest ... + /// + /// + /// + private static HttpMethod GetMethod (string method) { + // + if (HttpMethods.IsDelete (method)) { + return HttpMethod.Delete; + } else if (HttpMethods.IsGet (method)) { + return HttpMethod.Get; + } else if (HttpMethods.IsHead (method)) { + return HttpMethod.Head; + } else if (HttpMethods.IsOptions (method)) { + return HttpMethod.Options; + } else if (HttpMethods.IsPost (method)) { + return HttpMethod.Post; + } else if (HttpMethods.IsPut (method)) { + return HttpMethod.Put; + } else if (HttpMethods.IsTrace (method)) { + return HttpMethod.Trace; + } + + // + return new HttpMethod (method); + } + + /// + /// add all headers exists in HttpResponse MEssage to Current Context Response Headers ... + /// + /// + /// + private void HandleResponseHeaders ( + HttpContext context, + HttpResponseMessage message + ) { + // + // Handle Message Headers ... + foreach (var header in message.Headers) { + context.Response.Headers[header.Key] = header.Value + .ToArray (); + } + + // + // Handle Message Content Headers ... + foreach (var header in message.Content.Headers) { + context.Response.Headers[header.Key] = header.Value + .ToArray (); + } + + // + context.Response.Headers.Remove ("transfer-encoding"); + } + + /// + /// Processing Response Content and Handle it on HttpContextResponse ... + /// + /// + /// + /// + private async Task ProcessResponseContent ( + HttpContext context, + HttpResponseMessage message + ) { + // + // Reading Content as Byte Array ... + var contentBytes = await message.Content + .ReadAsByteArrayAsync (); + + // + // Check ContentType of Response ... + if ( + IsContentOfType (message, XResponseContentTypes.HTML) || + IsContentOfType (message, XResponseContentTypes.JSON) || + IsContentOfType (message, XResponseContentTypes.JavaScript) + ) { + // + // If their String Content ... + var stringContent = Encoding.UTF8.GetString (contentBytes); + + // + // TODO: here we can change String Contents ... + + // + // Writing new Content to Response ... + await context.Response + .WriteAsync ( + stringContent, + Encoding.UTF8 + ); + } else { + // + // if they have non string content ... + await context.Response.Body + .WriteAsync ( + buffer: contentBytes, + offset: 0, + count: contentBytes.Length + ); + } + + // + SendLog ($"Response Content Processed ...", LogLevel.Information); + } + + /// + /// Check Content Type of Specific Response ... + /// + /// + /// + /// + private bool IsContentOfType ( + HttpResponseMessage response, + string type + ) { + // + // Create EMpty Result ... + var result = false; + + // + // Retrieve and Check Content Type of Response ... + if (response.Content?.Headers?.ContentType != null) { + result = response.Content.Headers.ContentType.MediaType == type; + } + + // + return result; + } + + /// + /// Retrieve and Extract Route Descriptor ... + /// + /// + /// + private XProxyRouteDescriptor GetRouteDescriptor (HttpRequest request) { + // + // Validate Args ... + if ( + CONFIGURATION.IsNull () || + !CONFIGURATION.Routes.HasChild () + ) { + return null; + } + + // + var result = CONFIGURATION.Routes + .FirstOrDefault (rd => !rd.Route.IsNullOrEmpty () && + request.Path + .StartsWithSegments (rd.Route + .ToNormalString () + ) + ); + + // + return result; + } + #endregion + } + + /// + /// Provide all requirements for Dependency Injection Handler ... + /// + public static class XDIHelperExtension { + /// + /// Retrieve XProxy Middelware Configuration from Configuration ... + /// + /// + /// + public static XProxyConfiguration GetXProxyConfiguration (this IConfiguration config) { + // + var xProxyConfiguration = config.GetSection (ConfigurationNodeNames.XPROXY_CONFIGURATION_NODE_NAME); + return xProxyConfiguration.Get (); + } + + /// + /// Register HTTP Handler for XProxy Server ... + /// + /// + /// + /// + public static void AddXProxyHttpHandler ( + this IServiceCollection services, + IConfiguration configuration, + HttpClientHandler httpHandler = null + ) { + // + var proxyConfig = configuration.GetXProxyConfiguration (); + if (proxyConfig.IsNull ()) { + throw XException.InvalidConfiguration.ToException (); + } + + // + services.AddXProxyHttpHandler ( + httpHandler: httpHandler, + configuration: proxyConfig + ); + } + + /// + /// Register HTTP Handler for XProxy Server ... + /// + /// + /// + /// + public static void AddXProxyHttpHandler ( + this IServiceCollection services, + XProxyConfiguration configuration, + HttpClientHandler httpHandler = null + ) { + // + if (configuration.IsNull ()) { + throw XException.InvalidConfiguration.ToException (); + } + + // + if (httpHandler.IsNull ()) { + httpHandler = new HttpClientHandler (); + } + + // + httpHandler.AllowAutoRedirect = configuration.AllowAutoRedirect; + + // + if (configuration.DisableSSLCheck) { + httpHandler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + } + + // + services + .AddHttpClient (XProxyConstants.XPROXY_HTTP_CLIENT) + .ConfigurePrimaryHttpMessageHandler (() => { + return httpHandler; + }); + } + + /// + /// Register XProxy On Server ... + /// + /// + /// + /// + public static void AddXProxyServer ( + this IServiceCollection services, + IConfiguration configuration, + HttpClientHandler httpHandler = null + ) { + // + var proxyConfig = configuration.GetXProxyConfiguration (); + + // + services.AddXProxyServer ( + httpHandler: httpHandler, + configuration: proxyConfig + ); + } + + /// + /// Register XProxy On Server ... + /// + /// + /// + /// + public static void AddXProxyServer ( + this IServiceCollection services, + XProxyConfiguration configuration, + HttpClientHandler httpHandler = null + ) { + // + // Validate Args ... + if (configuration.IsNull ()) { + throw XException.InvalidConfiguration.ToException (); + } + + // + // Register Proxy Configuration ... + services.AddSingleton (configuration); + + // + // Regiter Proxy Handler ... + services.AddXProxyHttpHandler ( + httpHandler: httpHandler, + configuration: configuration + ); + } + + /// + /// Register XProxy On Server ... + /// + /// + /// + public static void AddXProxyClient ( + this IServiceCollection services, + IConfiguration configuration + ) { + // + var proxyConfig = configuration.GetXProxyConfiguration (); + + // + services.AddXProxyClient ( + configuration: proxyConfig + ); + } + + /// + /// Register XProxy On Client ... + /// + /// + /// + public static void AddXProxyClient ( + this IServiceCollection services, + XProxyConfiguration configuration + ) { + // + // Validate Args ... + if (configuration.IsNull ()) { + throw XException.InvalidConfiguration.ToException (); + } + + // + // Register Proxy Configuration ... + services.AddSingleton (configuration); + + // + // Register IXProxyHelper ... + services.AddSingleton (); + } + + /// + /// Adding XProxy Middleware to Applications ... + /// + /// + public static void UseXProxy (this IApplicationBuilder app) { + app.UseMiddleware (); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Models\XBaseRestResponse.cs + +using RestSharp; + +namespace xIdentityService.Models +{ + public class XBaseRestResponse + { + public T Model { get; set; } + public IRestResponse Response { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Partial\XIdentityProvider+Admin.cs + +using xCommons.Extensions; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using System.Collections.Generic; +using xIdentityService.Constants; + +namespace xIdentityService.Providers { + public partial class XIdentityProvider { + // + #region Admin ... + /// + /// Ban Specific Users + /// + /// Authentication Tokens + /// an instance of XUserNameIdRequest which represents user identifier list to Ban + /// a list of banned users identifiers + public async Task> Ban ( + XTokenResponse tokens, + XUserNameIdRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull ( + tokens, + model + ) + .AddNotEmpty (tokens.AccessToken) + .AddNotZeroChilds (model.Ids) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.Ban); + request.AddJsonBody (model); + + // + var response = await RunRestRequest> ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// UnBann Specific Users + /// + /// Authentication Tokens + /// an instance of XUserNameIdRequest which represents user identifier list to Ban + /// a list of unbanned users identifiers + public async Task> UnBan ( + XTokenResponse tokens, + XUserNameIdRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull ( + tokens, + model + ) + .AddNotEmpty (tokens.AccessToken) + .AddNotZeroChilds (model.Ids) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.UnBan); + request.AddJsonBody (model); + + // + var response = await RunRestRequest> ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Check Specific User is Banned or not + /// + /// Authentication Tokens + /// specified user's identifier + /// a boolean value which represent user banned or not + public async Task IsBanned ( + XTokenResponse tokens, + string userSelectByParam + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotEmpty ( + userSelectByParam, + tokens.AccessToken + ) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.IsBanned, + @params : new Dictionary { { XParam.XUserSelectByParam.GetStringValue (), userSelectByParam } } + ); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.GET + ); + return response; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Partial\XIdentityProvider+Authentication.cs + +using System; +using xCommons.Extensions; +using IdentityModel.Client; +using xExceptions.Constants; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using xIdentityService.Constants; + +namespace xIdentityService.Providers { + public partial class XIdentityProvider { + // + #region Authentication Handlers ... + /// + /// Request for Discovery Document + /// + /// an instance of DiscoveryDocumentResponse + public async Task RequestDiscoveryDocument () { + // + var httpClient = GetHttpClient (); + + // + try { + // + var result = httpClient + .GetDiscoveryDocumentAsync (identityConfiguration.Authority) + .ContinueWith (docTask => { + // + httpClient.Dispose (); + return docTask.Result; + }); + + // + return await result; + } catch { + throw XException.ActionFailed.ToException (); + } + } + + /// + /// Request AccessToken for Specific XApiScope + /// + /// a member of XApiScope + /// an instance of XTokenResponse + public async Task RequestScopeAccessToken (string scope) { + // + // Validate Args ... + var scopeName = string.Empty; + try { + scopeName = scope; // scope.GetStringValue (); + } catch { } + if (scopeName.IsNullOrEmpty ()) { + XException.InvalidArgs.Throw (); + } + + // + var client = GetRestClient (); + var request = GetRestRequet ( + XApiAccountEndpoint.RequestScopeAccessToken, + headers : new Dictionary { { XParam.XScope.GetStringValue (), scope.ToString () } }); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + + // + // Save Token ... + if (!response.IsNull ()) { + await tokenProvider.AddOrUpdateToken (response); + } + + // + return response; + } + + /// + /// Validate a token + /// + /// Authentication Tokens + /// an instance of TokenIntrospectionResponse + public async Task Introspection (XTokenResponse model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (model) + .AddNotEmpty (model.AccessToken) + .ValidateGroupAsync (); + + // + var httpClient = GetHttpClient (); + + // + try { + // + var discoDoc = await RequestDiscoveryDocument (); + if (discoDoc.IsError) { + XException.ActionFailed.Throw (); + } + + // + var response = await httpClient.IntrospectTokenAsync (new TokenIntrospectionRequest { + // + Address = discoDoc.IntrospectionEndpoint, + ClientId = identityConfiguration.ClientId, + ClientSecret = identityConfiguration.ClientSecret, + + // + Token = model.AccessToken + }); + if (response.IsError) { + throw new Exception (response.Error); + } + + // + return response; + } catch { + throw XException.ActionFailed.ToException (); + } + } + + /// + /// Authenticate a User + /// + /// User Login Required Info, an instance of XLoginRequest + /// an instance of XTokenResponse + public async Task Authenticate (XLoginRequest model) { + // + // Validate Args ... + validationProvider + .GroupValidationBuilder () + .AddNotNull (model) + .AddNotEmpty ( + model.UserSelectBy, + model.Password + ) + .ValidateGroup (); + + // + var client = GetRestClient (); + var request = GetRestRequet (XApiAccountEndpoint.Authenticate); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + + // + // Save Token ... + if (!response.IsNull ()) { + await tokenProvider.AddOrUpdateToken (response); + } + + // + return response; + } + + /// + /// Do Login based on XLoginRequest + /// + /// User Login Required Info, an instance of XLoginRequest + /// an instance of XTokenResponse + public async Task Login (XLoginRequest model) { + // + // Validate Args ... + validationProvider + .GroupValidationBuilder () + .AddNotNull (model, model.Device) + .AddNotEmpty ( + model.UserSelectBy, + model.Password + ) + .ValidateGroup (); + + // + var client = GetRestClient (); + var request = GetRestRequet (XApiAccountEndpoint.Login); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + + // + if (!response.IsNull ()) { + // + var tokens = response.ToXTokenResponse (); + await tokenProvider.AddOrUpdateToken (tokens); + } + + // + return response; + } + + /// + /// Logout from System + /// + /// + /// + public async Task Logout (XTokenResponse model) { + // + await validationProvider + .GroupValidationBuilder () + .AddNotNull (model) + .AddNotEmpty (model.AccessToken) + .ValidateGroupAsync (); + + // + var isExists = await tokenProvider + .IsTokenExists (model.AccessToken); + if (isExists) { + await tokenProvider + .RemoveToken (model.AccessToken); + } + } + + /// + /// Refresh Tokens + /// + /// Authentication Tokens + /// an instance of XTokenResponse + public async Task RefreshTokens (XTokenResponse model) { + // + // Validate Args ... + validationProvider + .GroupValidationBuilder () + .AddNotNull (model) + .AddNotEmpty ( + model.AccessToken, + model.RefreshToken + ) + .ValidateGroup (); + + // + var client = GetRestClient (model); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.RefreshTokens, + headers : model.ToHeaderDictionary ()); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + if (!response.IsNull ()) { + await tokenProvider.UpdateToken (response); + } + + // + return response; + } + #endregion + + // + #region Password Actions Handlers ... + /// + /// Change Password + /// + /// Authentication Tokens + /// an instance of XActionRequest class which provider requirement for action + /// + public async Task ChangePassword ( + XTokenResponse tokens, + XActionRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens, model) + .AddNotEmpty ( + model.Lang, + model.Password, + model.NewPassword, + model.ReturnUrl) + .AddNotNull (model.Device) + .ValidateGroupAsync (); + + // + // Validate User Select By Param ... + var userSelectByParam = GetUserSelectByParam ( + model, + forceNotNull : true, + excludes : null); + + // + var client = GetRestClient (tokens); + var request = GetRestRequet ( + XApiAccountEndpoint.ChangePassword, + headers : tokens.ToHeaderDictionary ()); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Partial\XIdentityProvider+Friendship.cs + +using xModels.Dtos; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using xHttpService.Extensions; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityService.Constants; +using xIdentityModels.Navigations; + +namespace xIdentityService.Providers +{ + public partial class XIdentityProvider + { + // + #region Friendship ... + /// + /// Follow a User + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipFollowing + public async Task Follow( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.Follow, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Cancel Following Request + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// a boolean value + public async Task Cancel( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.Cancel, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Unfollow a Follower + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// + public async Task UnFollowFollower( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.UnFollowFollower, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + } + + /// + /// Unfollow Following + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// + public async Task UnFollowFollowing( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.UnFollowFollowing, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + } + + /// + /// Block a Follower + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipFollowing + public async Task Block( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.Block, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Unblock a Blocked User + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipFollowing + public async Task UnBlock( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.UnBlock, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Accept a Following Request + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipFollower + public async Task AcceptRequest( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.AcceptRequest, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Reject a Following Request + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipFollower + public async Task RejectRequest( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.RejectRequest, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Get Friendship Info Model between Specific User and Current User + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipInfoDto + public async Task GetFriendshipInfo( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.FriendshipInfo, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Check a User IsFollower of Requested User + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// a boolean value + public async Task IsFollower( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.IsFollower, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get Specific Follower + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipFollower + public async Task GetFollower( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.Follower, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get Follower State of a User + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipState + public async Task GetFollowerState( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.FollowerState, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get Followers List Of Current User + /// + /// Authentication Tokens + /// a collection of XFriendshipFollower + public async Task> GetFollowers( + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.Followers); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get All Followers List Includes Blocked, Requested and etc + /// of Current User + /// + /// Authentication Tokens + /// a collection of XFriendshipFollower + public async Task> GetAllFollowers( + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.AllFollowers); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Return Followers UserName's List of Current User + /// + /// Authentication Tokens + /// a collection of UserNames + public async Task> GetFollowersList( + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.FollowersList); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Retrieve All User's Followers based on Query Model ... + /// + /// Query Model Descriptor + /// Dest User for Reading Followers + /// Requester User's Descriptor + /// + public async Task> QueryFollowers( + XQuery query, + string destUser, + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(destUser) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.QueryFollowers); + + // + // Attach XQuery to Request ... + request = request.AttachXQuery(query); + request = request.AttachDestUser(destUser); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Check a User is in Followings of Current User + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// a boolean value + public async Task IsFollowing( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.IsFollowing, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get Specific Following Model + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipFollowing + public async Task GetFollowing( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.Following, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get Following State Relation between Specific User + /// and Current User + /// + /// Authentication Tokens + /// a user identifier which represent destination user + /// an instance of XFriendshipState + public async Task GetFollowingState( + XTokenResponse tokens, + string destUser + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty( + destUser, + tokens.AccessToken + ) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet( + XApiAccountEndpoint.FollowingState, + @params: new Dictionary { { XParam.XDestUser.GetStringValue (), destUser } + } + ); + + // + var response = await RunRestRequest( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get Followings of Current User + /// + /// Authentication Tokens + /// a collection of XFriendshipFollowing + public async Task> GetFollowings( + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.Followings); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Get All Following List Includes Blocked, Requested and etc + /// of Current User + /// + /// Authentication Tokens + /// a collection of XFriendshipFollowing + public async Task> GetAllFollowings( + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.AllFollowings); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Return Following UserName's List of Current User + /// + /// Authentication Tokens + /// a collection of UserNames + public async Task> GetFollowingList( + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.FollowingsList); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Retrieve All User's Followings based on Query Model ... + /// + /// Query Model Descriptor + /// Dest User for Reading Followers + /// Requester User's Descriptor + /// + public async Task> QueryFollowings( + XQuery query, + string destUser, + XTokenResponse tokens + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens) + .AddNotEmpty(destUser) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.QueryFollowings); + + // + // Attach XQuery to Request ... + request = request.AttachXQuery(query); + request = request.AttachDestUser(destUser); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Partial\XIdentityProvider+Open.cs + +using System; +using System.Linq; +using MongoDB.Bson; +using xModels.Dtos; +using xCommons.Constants; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using xHttpService.Constants; +using System.Collections.Generic; +using xIdentityService.Constants; +using System.Runtime.InteropServices; + +namespace xIdentityService.Providers +{ + public partial class XIdentityProvider + { + /// + /// Prepare API Based Device to Open Actions ... + /// + /// + public XDeviceDto GetDevice() + { + // + var os = "XSaherElmOS"; + bool isOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + if (isOSX) + { + os = "MacOs"; + } + else if (isLinux) + { + os = "Linux"; + } + else if (isWindows) + { + os = "Windows"; + } + var agent = RuntimeInformation.FrameworkDescription; + if (agent.IsNullOrEmpty()) + { + agent = "XSaherElmOSAgent"; + } + var version = RuntimeInformation.OSArchitecture + ", " + RuntimeInformation.OSDescription; + if (version.IsNullOrEmpty()) + { + version = "XSaherElmOSVersion"; + } + var browser = "XSaherElmBrowser_" + RevisionSecretKey.ToMd5String(); + + // + var result = new XDeviceDto + { + Os = os, + Browser = browser, + UserAgent = agent, + OsVersion = version, + DeviceType = XDeviceType.Desktop, + }; + + // + return result; + } + + // + #region OpenGet ... + public async Task OpenGet( + XOpenActionInnerDto model + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(model) + .AddNotEmpty(model.Token) + .AddNotEmpty(model.Payload) + .ValidateGroupAsync(); + + // + var result = await OpenGet( + token: model.Token, + actionRequest: model.Payload + ); + + // + return result; + } + public async Task OpenGet( + string token, + string actionRequest + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotEmpty(token) + .AddNotEmpty(actionRequest) + .ValidateGroupAsync(); + + // + // Retrieve Rest Client ... + var client = GetRestClient(); + + // + // Prepare Rest Request ... + var request = GetRestRequet( + XApiAccountEndpoint.Open, + headers: new Dictionary + { + { XAuthentication.TOKEN, token} + }, + queryStrings: new Dictionary + { + { XParam.XRequest.GetStringValue(), actionRequest } + } + ); + + // + // Run Request and Retrieve Response ... + var response = await RunRestRequestByResponse( + client, + request, + XHttpMethod.GET + ); + + // + // Now we Have to Validate the recieved response ... + + // + // Read token from Headers ... + var responseToken = response.Response.Headers + .First(h => h.Name == XAuthentication.TOKEN).Value.ToString(); + if (responseToken.IsNullOrEmpty()) + { + XException.ActionFailed.Throw(); + } + + // + // Decript Recieve Response ... + var responseJson = response.Model; // SecurityProvider.Decrypt(response.Model); + if (responseJson.IsNullOrEmpty()) + { + XException.ActionFailed.Throw(); + } + + // + var result = new XOpenActionInnerDto + { + Token = responseToken, + Payload = responseJson + }; + + // + // Retrieve Model and Validate it ... + var responseModel = responseJson.FromJSON(); + if (responseModel.IsNull() || + responseModel.Payload.IsNullOrEmpty() || + responseToken != responseModel.Checksum || + responseModel.Timestamp.FromTimestamp().IsExPired()) + { + XException.ActionFailed.Throw(); + } + + // + return result; + } + #endregion + + // + #region GetUserInfo ... + public async Task GetUserInfo( + XDeviceDto device, + string userSelectByParam + ) + { + // + // Validate Args ... + validationProvider.NotNull(device); + validationProvider.NotEmpty(userSelectByParam); + + // + // Prepare Open Action Model ... + var opaModel = new XOpenActionRequestDto + { + Device = device, + Payload = userSelectByParam, + Action = XOpenActions.GetUserInfo, + Timestamp = DateTime.UtcNow.AddMinutes(5).ToTimestamp().ToString() + }; + if (!opaModel.Validate()) + { + XException.InvalidArgs.Throw(); + } + + // + // Fill Checksum Filed ... + var token = opaModel.ToOpenActionToken(); + opaModel.Checksum = token; + + // + // Prepare Request ... + var opaJson = opaModel.ToJson(); + var requestString = SecurityProvider.Encrypt(opaJson); + + // + return await GetUserInfo( + token: token, + request: requestString + ); + } + + public async Task GetUserInfo( + XOpenActionInnerDto dto + ) + { + // + return await GetUserInfo( + token: dto.Token, + request: dto.Payload + ); + } + + public async Task GetUserInfo( + string token, + string request + ) + { + // + // Validate Args ... + validationProvider.NotEmpty(token); + validationProvider.NotEmpty(request); + + // + // Retrieve and Validate Response ... + var response = await OpenGet( + token: token, + actionRequest: request + ); + if (response.IsNull() || + response.Token.IsNullOrEmpty() || + response.Payload.IsNullOrEmpty()) + { + XException.ActionFailed.Throw(); + } + + // + // Now we Have to Converts Repose Payload to Result Model ... + // Note that the respose Model's payload here is Decrypted and + // actually is Json Representation of Result Model ... + var requestModel = response.Payload.FromJSON(); + if (requestModel.IsNull()) + { + XException.ActionFailed.Throw(); + } + + // + var resultJson = SecurityProvider.Decrypt(requestModel.Payload); + var result = resultJson.FromJSON(); + if (result.IsNull()) + { + XException.NotFound.Throw(); + } + + // + return result; + } + #endregion + + // + #region GetUserInfos ... + public async Task> GetUserInfos( + XDeviceDto device, + string ids + ) + { + // + // Validate Args ... + validationProvider.NotNull(device); + validationProvider.NotEmpty(ids); + + // + // Prepare Open Action Model ... + var opaModel = new XOpenActionRequestDto + { + Payload = ids, + Device = device, + Action = XOpenActions.GetUserInfos, + Timestamp = DateTime.UtcNow.AddMinutes(5).ToTimestamp().ToString() + }; + if (!opaModel.Validate()) + { + XException.InvalidArgs.Throw(); + } + + // + // Fill Checksum Filed ... + var token = opaModel.ToOpenActionToken(); + opaModel.Checksum = token; + + // + // Prepare Request ... + var opaJson = opaModel.ToJson(); + var requestString = SecurityProvider.Encrypt(opaJson); + + // + return await GetUserInfos( + token: token, + request: requestString + ); + } + + public async Task> GetUserInfos( + XOpenActionInnerDto dto + ) + { + // + return await GetUserInfos( + token: dto.Token, + request: dto.Payload + ); + } + + public async Task> GetUserInfos( + string token, + string request + ) + { + // + // Validate Args ... + validationProvider.NotEmpty(token); + validationProvider.NotEmpty(request); + + // + // Retrieve and Validate Response ... + var response = await OpenGet( + token: token, + actionRequest: request + ); + if (response.IsNull() || + response.Token.IsNullOrEmpty() || + response.Payload.IsNullOrEmpty()) + { + XException.ActionFailed.Throw(); + } + + // + // Now we Have to Converts Repose Payload to Result Model ... + // Note that the respose Model's payload here is Decrypted and + // actually is Json Representation of Result Model ... + var requestModel = response.Payload.FromJSON(); + if (requestModel.IsNull()) + { + XException.ActionFailed.Throw(); + } + + // + var resultJson = SecurityProvider.Decrypt(requestModel.Payload); + var result = resultJson.FromJSON>(); + if (result.IsNull()) + { + XException.NotFound.Throw(); + } + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Partial\XIdentityProvider+Profile.cs + +using xModels.Dtos; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using xHttpService.Extensions; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using xIdentityService.Constants; +using xIdentityModels.Navigations; + +namespace xIdentityService.Providers { + public partial class XIdentityProvider { + // + #region User Actions ... + /// + /// Retrieve User Names based on UserIds + /// + /// Authentication Tokens + /// a comma seperated list of UserIds + /// a collection of UserNames + public async Task> GetUserNames ( + XTokenResponse tokens, + string ids + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotEmpty (ids, tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + var request = GetRestRequet ( + XApiAccountEndpoint.GetNames, + @params : new Dictionary { { XParam.XIds.GetStringValue (), ids } } + ); + + // + var response = await RunRestRequest> ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// get user ids and retrieve corresponding user names + /// + /// Authentication Tokens + /// an instance of XUserNameIdRequest which represent required UserIds collection + /// a collection of XUserNameIdResponse instance + public async Task> GetUserNameIds ( + XTokenResponse tokens, + XUserNameIdRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (model, tokens) + .AddNotZeroChilds (model.Ids) + .AddNotEmpty (tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + var request = GetRestRequet (XApiAccountEndpoint.GetNameIds); + request.AddJsonBody (model); + + // + var response = await RunRestRequest> ( + client, + request, + XHttpMethod.POST + ); + return response; + } + #endregion + + // + #region Profile Actions ... + /// + /// Get Profile Object of Specific User + /// + /// Authentication Tokens + /// determine's which user profile must be retrieved + /// an instance of XUserProfileDto + public async Task GetUserProfile ( + XTokenResponse tokens, + string userSelectByParam + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotEmpty (userSelectByParam, tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.GetProfile, + @params : new Dictionary { { XParam.XUserSelectByParam.GetStringValue (), userSelectByParam } } + ); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Retrieve Users Based On Query + /// + /// Authentication Tokens + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XUserProfileDto + public async Task> QueryUsers ( + XTokenResponse tokens, + XQuery query + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens, query) + .AddNotEmpty (tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.QueryProfiles); + + // + // Attach XQuery to Request ... + request = request.AttachXQuery (query); + + // + var response = await RunRestRequest> ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Retrieve Users Based On Query and Specified role + /// + /// Authentication Tokens + /// an string which represent user role + /// how to filter results based on XQuery structure + /// if it's true the user must has exact role, otherwise top level users also listed + /// an instance of XQueryResult of XUserProfileDto + public async Task> QueryInRoleUsers ( + XTokenResponse tokens, + string role, + XQuery query, + bool forceRole = false + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens, query) + .AddNotEmpty (tokens.AccessToken, role) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.QueryProfiles, + headers : new Dictionary { { XParam.XRole.GetStringValue (), role }, + { XParam.XForceRole.GetStringValue (), forceRole.AsHttpParamString () } + } + ); + + // + // Attach XQuery to Request ... + request = request.AttachXQuery (query); + + // + var response = await RunRestRequest> ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Query Specified User's Profile Images + /// + /// Authentication Tokens + /// determine's which user profile must be retrieved + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XProfileImage + public async Task> QueryAvatars ( + XTokenResponse tokens, + string userSelectByParam, + XQuery query + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens, query) + .AddNotEmpty (tokens.AccessToken, userSelectByParam) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.QueryAvatars, + @params : new Dictionary { { XParam.XUserSelectByParam.GetStringValue (), userSelectByParam } + } + ); + + // + // Attach XQuery to Request ... + request = request.AttachXQuery (query); + + // + var response = await RunRestRequest> ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Update User Profile based on XProfileUpdateRequest (FirstName/LastName/DateOfBirth) + /// + /// Authentication Tokens + /// determine's which user profile must be retrieved + /// user update info, an instance of XProfileUpdateRequest + /// an instance of XUserProfileDto + public async Task ProfileUpdateAsync ( + XTokenResponse tokens, + string userSelectByParam, + XProfileUpdateRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens, model) + .AddNotEmpty (tokens.AccessToken, userSelectByParam) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.UpdateProfile, + @params : new Dictionary { { XParam.XUserSelectByParam.GetStringValue (), userSelectByParam } + } + ); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Update a User Profile based on XProfileUpdateRequest Full + /// + /// Authentication Tokens + /// determine's which user profile must be retrieved + /// user update info, an instance of XProfileUpdateRequest + /// an instance of XUserProfileDto + public async Task FullProfileUpdateAsync ( + XTokenResponse tokens, + string userSelectByParam, + XProfileUpdateRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens, model) + .AddNotEmpty (tokens.AccessToken, userSelectByParam) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.FullUpdateProfile, + @params : new Dictionary { { XParam.XUserSelectByParam.GetStringValue (), userSelectByParam } + } + ); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Add a New Profile Image + /// + /// Authentication Tokens + /// an specific File to upload, IFormFile + /// an instance of XUserProfileDto + public async Task AddAvatar ( + XTokenResponse tokens, + IFormFile file + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens, file) + .AddNotEmpty (tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.AddAvatar); + request.AlwaysMultipartFormData = true; + request.AddFileBytes ( + XParam.XFile.GetStringValue (), + file.ToByteArray (), + file.FileName, + file.ContentType + ); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Add a New Collection of Profile Images + /// + /// Authentication Tokens + /// a collection of Files to upload, IFormFileCollection + /// an instance of XUserProfileDto + public async Task AddAvatars ( + XTokenResponse tokens, + IFormFileCollection files + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotZeroChilds (files) + .AddNotEmpty (tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.AddAvatars); + request.AlwaysMultipartFormData = true; + + // + // Add Files ... + foreach (var file in files) { + // + // Add Single file ... + request.AddFileBytes ( + XParam.XFiles.GetStringValue (), + file.ToByteArray (), + file.FileName, + file.ContentType + ); + } + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// set Specified Profile Image as Avatar + /// + /// Authentication Tokens + /// an integer which reperesent AvatarId to set as current Avatar + /// an instance of XUserProfileDto + public async Task SetAvatar ( + XTokenResponse tokens, + int id + ) { + // + // Validate Args ... + id.ValidateIntId (); + + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotEmpty (tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.SetAvatar, + @params : new Dictionary { { XParam.XId.GetStringValue (), id.ToString () } + } + ); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Remove Profile Images + /// + /// Authentication Tokens + /// a comma seperated list of avatarIds to remove + /// an instance of XUserProfileDto + public async Task RemoveAvatars ( + XTokenResponse tokens, + string avatarIds + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotEmpty (avatarIds, tokens.AccessToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet ( + XApiAccountEndpoint.RemoveAvatars, + @params : new Dictionary { { XParam.XIds.GetStringValue (), avatarIds } + } + ); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.DELETE + ); + return response; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Partial\XIdentityProvider+Registration.cs + +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using xIdentityService.Constants; + +namespace xIdentityService.Providers { + public partial class XIdentityProvider { + // + #region Registration ... + /// + /// Check a User Selector is Available for Registration or Not + /// + /// an string value which reperesent a user + /// a boolean value + public async Task CanRegister (string userSelectByParam) { + // + // Validate ARgs ... + validationProvider.NotEmpty (userSelectByParam); + + // + var client = GetRestClient (); + + // + // Check Endpoint ... + var endpoint = XApiAccountEndpoint.CanRegisterUserName; + var @params = new Dictionary { { XParam.XUserName.GetStringValue (), userSelectByParam } }; + + // + // Check UserSelectByType ... + var type = userSelectByParam.GetUserSelectByType (); + switch (type) { + // + case XUserSelectBy.Username: + break; + + // + case XUserSelectBy.MobileNumber: + // + endpoint = XApiAccountEndpoint.CanRegisterMobileNumber; + @params = new Dictionary { { XParam.XMobileNumber.GetStringValue (), userSelectByParam } }; + break; + + // + case XUserSelectBy.Email: + // + endpoint = XApiAccountEndpoint.CanRegisterEmail; + @params = new Dictionary { { XParam.XEmail.GetStringValue (), userSelectByParam } }; + break; + + // + default: + throw XException.InvalidData.ToException (); + } + + // + var request = GetRestRequet ( + endpoint, + @params: @params + ); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Invite a User to Register on Dashboard + /// + /// Authentication Tokens + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task InviteUser ( + XTokenResponse tokens, + XActionRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull ( + tokens, + model, + model.Device) + .AddNotEmpty ( + tokens.AccessToken, + model.Lang, + model.Email, + model.ReturnUrl) + .AddEmailAddress (model.Email) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.InviteUser); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Recieve some Basic Informations and Start Registration Proccess + /// if they Valid + /// + /// Registration Proccess Starts with Invoking this Action + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task RequestRegistration ( + XActionRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull ( + model, + model.Device, + model.DateOfBirth) + .AddNotEmpty ( + model.Lang, + model.FirstName, + model.LastName) + .ValidateGroupAsync (); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.RequestRegistration); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Add User Account Informations + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task AddAccountInfo ( + XActionRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull ( + model, + model.Device) + .AddNotEmpty ( + model.Lang, + model.UserName, + model.Password, + model.ActionToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.AddAccountInfo); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Upload and Attach a Profile Image for User. this is an Optional Step + /// + /// user's requested action token + /// an instance of IFormFile for user's Avatar + /// an instance of XActionResponse + public async Task AttachProfileImage ( + string actionToken, + IFormFile file + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (file) + .AddNotEmpty (actionToken) + .ValidateGroupAsync (); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.AttachProfileImage); + + // + // Prepare Request Data ... + request.AddParameter (XParam.XActionToken.GetStringValue (), actionToken); + request.AddFileBytes ( + XParam.XFile.GetStringValue (), + file.ToByteArray (), + file.FileName, + file.ContentType + ); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Finish Registrationn Proccess + /// + /// an instance of XActionRequest class which provider requirement for action + /// + public async Task FinishRegistration (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.Password, + model.ReturnUrl) + .AddNotNull (model.Device) + .AddUrl (model.ReturnUrl) + .ValidateGroupAsync (); + + // + // Create Http Client ... + var client = GetRestClient (); + + // + // Create Request ... + var request = GetRestRequet (XApiAccountEndpoint.FinishRegistration); + + // + // Prepare Request Data ... + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + } + + /// + /// Check User is Confirmed Email or not + /// + /// Authentication Tokens + /// an string value which reperesent a user + /// a boolean value + public async Task IsConfirmedEmail ( + XTokenResponse tokens, + string userSelectByParam + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotEmpty ( + tokens.AccessToken, + userSelectByParam) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.IsConfirmedEmail); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Check User is Confirmed Mobile or not + /// + /// Authentication Tokens + /// an string value which reperesent a user + /// a boolean value + public async Task IsConfirmedMobile ( + XTokenResponse tokens, + string userSelectByParam + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (tokens) + .AddNotEmpty ( + tokens.AccessToken, + userSelectByParam) + .ValidateGroupAsync (); + + // + var client = GetRestClient (tokens); + + // + var request = GetRestRequet (XApiAccountEndpoint.IsConfirmedMobile); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.GET + ); + return response; + } + + /// + /// Request Registration Confirm + /// + /// an instance of XActionRequest class which provider requirement for action + /// + public async Task RequestConfirmRegistration (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.Password, + model.ReturnUrl) + .AddNotNull (model.Device) + .AddUrl (model.ReturnUrl) + .ValidateGroupAsync (); + + // + // Get UserSelect By Param ... + var userSelectByParam = model.GetUserSelectByParam (); + validationProvider.NotEmpty (userSelectByParam); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.RequestConfirmRegistration); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + } + + /// + /// Confirm Registration + /// + /// an instance of XActionRequest class which provider requirement for action + /// + public async Task ConfirmRegistration ( + XActionRequest model + ) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.ActionToken, + model.ReturnUrl + ) + .AddNotNull (model.Device) + .ValidateGroupAsync (); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.ConfirmRegistration); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + } + + /// + /// Request For Mobile Number Change/Confirm + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task RequestConfirmMobile (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.Password, + model.MobileNumber + ) + .AddNotNull (model.Device) + .AddMobileNumber (model.MobileNumber) + .ValidateGroupAsync (); + + // + // Get UserSelect By Param ... + var userSelectByParam = GetUserSelectByParam ( + model, + excludes : new List { + XUserSelectBy.Email, + XUserSelectBy.MobileNumber + }); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.RequestConfirmMobile); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Confirm Requested Mobile Number + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task ConfirmMobileNumber (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.ActionToken, + model.MobileVerificationCode + ) + .AddNotNull (model.Device) + .ValidateGroupAsync (); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.ConfirmMobileNumber); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Request For Email Address Change/Confirm + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task RequestConfirmEmail (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.Email, + model.Password) + .AddNotNull (model.Device) + .AddEmailAddress (model.Email) + .ValidateGroupAsync (); + + // + // Get UserSelect By Param ... + var userSelectByParam = GetUserSelectByParam ( + model, + excludes : new List { + XUserSelectBy.Email, + XUserSelectBy.MobileNumber + }); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.RequestConfirmEmail); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Confirm Requested Email Address + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task ConfirmEmailAddress (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.ActionToken, + model.EmailVerificationCode + ) + .AddNotNull (model.Device) + .ValidateGroupAsync (); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.ConfirmEmailAddress); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Request Reset Password Action + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + public async Task RequestResetPassword (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotEmpty ( + model.Lang, + model.ReturnUrl) + .AddNotNull (model.Device) + .AddUrl (model.ReturnUrl) + .ValidateGroupAsync (); + + // + // Get UserSelect By Param ... + var userSelectByParam = GetUserSelectByParam ( + model, + excludes : new List { + XUserSelectBy.Email, + XUserSelectBy.MobileNumber + }); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.RequestResetPassword); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + return response; + } + + /// + /// Reset a User's Password + /// + /// an instance of XActionRequest class which provider requirement for action + /// + public async Task ResetPassword (XActionRequest model) { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder () + .AddNotNull (model) + .AddNotEmpty ( + model.Lang, + model.ActionToken, + model.NewPassword, + model.ReturnUrl) + .AddNotNull (model.Device) + .ValidateGroupAsync (); + + // + var client = GetRestClient (); + + // + var request = GetRestRequet (XApiAccountEndpoint.ResetPassword); + request.AddJsonBody (model); + + // + var response = await RunRestRequest ( + client, + request, + XHttpMethod.POST + ); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Partial\XIdentityProvider+Search.cs + +using xModels.Dtos; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using xHttpService.Extensions; +using xIdentityService.Constants; + +namespace xIdentityService.Providers +{ + public partial class XIdentityProvider + { + // + #region Actions ... + /// + /// Query Open To Search User Profiles ... + /// + /// requested user's identifiers + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XUserProfileDto + public async Task> QueryOpenToSearchUsers( + XTokenResponse tokens, + XQuery query + ) + { + // + // Validate Args ... + await validationProvider + .GroupValidationBuilder() + .AddNotNull(tokens, query) + .AddNotEmpty(tokens.AccessToken) + .ValidateGroupAsync(); + + // + var client = GetRestClient(tokens); + + // + var request = GetRestRequet(XApiAccountEndpoint.QueryOpenToSearchProfiles); + + // + // Attach XQuery to Request ... + request = request.AttachXQuery(query); + + // + var response = await RunRestRequest>( + client, + request, + XHttpMethod.GET + ); + return response; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Providers\XBaseIdentityHttpProvider.cs + +using System; +using System.IO; +using RestSharp; +using System.Linq; +using System.Net.Http; +using System.Threading; +using xCommons.Constants; +using xCommons.Providers; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using xIdentityService.Models; +using RestSharp.Authenticators; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using xIdentityService.Constants; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; + +namespace xIdentityService.Providers +{ + /// + /// an abstraction Implementation for Identity Provided Http Based Services ... + /// + public abstract class XBaseIdentityHttpProvider : IXBaseIdentityHttpProvider + { + /// + /// Client Base Url ... + /// + /// + public string BaseUrl { get; } + + /// + /// Logger ... + /// + /// + public ILogger Logger { get; } + + /// + /// Powered Value ... + /// + /// + public string XPoweredValue { get; } + + /// + /// Default Client Timeout ... + /// + /// + public int DefaultClientTimeout { get; } + + /// + /// Default Buffering Chunk Size ... + /// + /// + public int DefaultBufferingChunckSize { get; } + + /// + /// Validation Provider ... + /// + /// + public XValidationProvider ValidationProvider { get; } + + /// + /// Token Provider ... + /// + private readonly IXTokenProvider tokenProvider; + + /// + /// Identity Provider ... + /// + private readonly IXIdentityProvider identityProvider; + + public XBaseIdentityHttpProvider( + string baseUrl, + ILogger logger, + string poweredValue, + int defaultClientTimeout, + IXTokenProvider tokenProvider, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider, + int defaultBufferingChunckSize = 8192 + ) + { + // + Logger = logger; + BaseUrl = baseUrl; + XPoweredValue = poweredValue; + this.tokenProvider = tokenProvider; + ValidationProvider = validationProvider; + this.identityProvider = identityProvider; + DefaultClientTimeout = defaultClientTimeout; + DefaultBufferingChunckSize = defaultBufferingChunckSize; + } + + // + #region Tools ... + /// + /// Get an Instance of Http Client + /// + /// + public HttpClient GetHttpClient(XTokenResponse tokens = null) + { + // + HttpClient httpClient = null; + + // + // httpClient = new HttpClient (); + var httpClientHandler = new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => + { + // + Logger.LogInformation( + $"XSSL 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); + + // + // Set Authenticator ... + if (!tokens.IsNull() && + !tokens.AccessToken.IsNullOrEmpty()) + { + // + httpClient.DefaultRequestHeaders + .Add( + XAuthorization.Header, + $"{XAuthentication.BEARER} {tokens.AccessToken}"); + } + + // + // Refresh Token ... + if (!tokens.IsNull() && + !tokens.RefreshToken.IsNullOrEmpty()) + { + // + httpClient.DefaultRequestHeaders + .Add( + XAuthorization.RefreshToken, + tokens.RefreshToken); + } + + // + // ExpiresAt ... + if (!tokens.IsNull() && + tokens.ExpiresAt > 0) + { + // + httpClient.DefaultRequestHeaders + .Add( + XAuthorization.ExpiresAt, + tokens.ExpiresAt.AsHttpParamString()); + } + + // + // Set Default Timeout ... + httpClient.Timeout = TimeSpan.FromSeconds(DefaultClientTimeout == -1 ? 10000 : DefaultClientTimeout); + + // + return httpClient; + } + public HttpClient GetHttpClient(XUserClaimsInfoDto userInfo = null) + { + // + XTokenResponse tokens = ToTokenResponse(userInfo); + + // + return GetHttpClient(tokens); + } + + /// + /// Get an Instance of RestClient + /// + /// + public RestClient GetRestClient(XTokenResponse tokens = null) + { + // + var client = new RestClient(BaseUrl); + + // + // Set Authenticator ... + if (!tokens.IsNull() && + !tokens.AccessToken.IsNullOrEmpty()) + { + client.Authenticator = new JwtAuthenticator(tokens.AccessToken); + } + + // + // Refresh Token ... + if (!tokens.IsNull() && + !tokens.RefreshToken.IsNullOrEmpty()) + { + client.AddDefaultHeader(XAuthorization.RefreshToken, tokens.RefreshToken); + } + + // + // ExpiresAt ... + if (!tokens.IsNull() && + tokens.ExpiresAt > 0) + { + client.AddDefaultHeader(XAuthorization.ExpiresAt, tokens.ExpiresAt.AsHttpParamString()); + } + + // + // Set Default Timeout ... + client.Timeout = DefaultClientTimeout; + + // + // SSL Validation Handler ... + client.RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => + { + // + Logger.LogInformation( + $"XSSL Handler: {Environment.NewLine}, sender: {sender}, {Environment.NewLine} cert: {cert}, {Environment.NewLine} chain: {chain}, {Environment.NewLine} sslPolicyErrors: {sslPolicyErrors}" + ); + + // + return true; + }; + + // + return client; + } + public RestClient GetRestClient(XUserClaimsInfoDto userInfo = null) + { + // + XTokenResponse tokens = ToTokenResponse(userInfo); + + // + return GetRestClient(tokens); + } + + /// + /// Get an Instance of RestRequest + /// + /// + /// + /// + /// + /// + /// + public RestRequest GetRestRequet( + Enum endpoint, + bool addXPoweredValue = true, + IDictionary @params = null, + IDictionary headers = null, + IDictionary queryStrings = null + ) + { + // + var url = PrepareUrl( + @params: @params, + endpoint: endpoint, + queryStrings: queryStrings + ); + + // + // Create request ... + var request = new RestRequest(url, DataFormat.Json); + + // + if (addXPoweredValue) + { + request.AddHeader(XAuthorization.XPoweredBy, XPoweredValue); + } + + // + // Add Headers to request ... + if (!headers.IsNull() && headers.Count > 0) + { + // + var enumerator = headers.GetEnumerator(); + while (enumerator.MoveNext()) + { + // + var item = enumerator.Current; + request.AddHeader(item.Key, item.Value); + } + } + + // + return request; + } + + /// + /// Handle Call Specific Request and retrieve Response + /// + /// + /// + /// + /// + /// + /// + /// + public async Task RunRestRequest( + RestClient client, + RestRequest request, + XHttpMethod method, + bool supportRefreshingTokens = true, + CancellationToken cancellationToken = default + ) + { + // + var response = await Task.Run(async () => + { + // + IRestResponse resp = null; + + // + // Call Endpoint Service using Specified Method ... + switch (method) + { + // + // Get ... + case XHttpMethod.GET: + resp = client.Get(request); + break; + + // + // Put ... + case XHttpMethod.PUT: + resp = client.Put(request); + break; + + // + // Post ... + case XHttpMethod.POST: + resp = client.Post(request); + break; + + // + // Delete ... + case XHttpMethod.DELETE: + resp = client.Delete(request); + break; + } + + // + if (!resp.IsSuccessful) + { + // + // Log Exception ... + Logger.LogError($"Error: {resp.ErrorMessage}"); + Logger.LogError($"Exception: {resp.ErrorException.ToJSON()}"); + if (!resp.ErrorMessage.IsNullOrEmpty() && + resp.ErrorMessage.Contains("timed out")) + { + XException.Timeout.Throw(); + } + + // + // TODO: Check UnAuthorized Status here for + // Refresh Tokens or Issue Correct Exception ... + if (supportRefreshingTokens && + resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + // + var isRefreshed = request.Parameters.Any(rp => + rp.Name == XParam.XIsRefreshed.GetStringValue() && + rp.Value.ToString().ToNormalString() == true.ToJSON()); + + // + Logger.LogInformation($"isRefreshed: {isRefreshed}"); + + // + // Get Access Token ... + XTokenResponse tokens = null; + var accessToken = request.Parameters.FirstOrDefault(rp => + rp.Name.ToNormalString() == XAuthorization.Header.ToNormalString()).Value.ToString(); + if (!accessToken.IsNullOrEmpty()) + { + // + accessToken = accessToken.Replace(XAuthorization.TokenIdentifier, ""); + + // + if (!accessToken.IsNullOrEmpty()) + { + tokens = await tokenProvider.RetrieveToken(accessToken); + } + } + + // + // Refresh Token and then re do request ... + if (!isRefreshed && + !accessToken.IsNullOrEmpty() && + !tokens.IsNull() && + tokens.IsRefreshable()) + { + // + request.AddHeader(XParam.XIsRefreshed.GetStringValue(), true.ToJSON()); + + // + // Refreshe Tokens ... + var refreshedTokens = await identityProvider + .RefreshTokens(tokens); + + // + // Renew Rest Client with Refreshed Tokens ... + var timeout = client.Timeout; + client = GetRestClient(refreshedTokens); + client.Timeout = Timeout.Infinite; + + // + // Do Request Again using renew Client ... + return await RunRestRequest( + client: client, + request: request, + method: method); + } + } + + // + var ex = resp.Content.ToXError(); + if (!ex.IsNull()) + { + throw ex.ToException(); + } + + // + // UnAuthorized ... + if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + XException.NotAuthorized.Throw(); + } + + // + // NotFound ... + if (resp.StatusCode == System.Net.HttpStatusCode.NotFound) + { + XException.NotFound.Throw(); + } + + // + XException.ActionFailed.Throw(); + } + + // + var result = resp.Content.FromJSON(); + return result; + }, + cancellationToken: cancellationToken); + + // + return response; + } + + /// + /// Access a Get Request Stream as AsyncEnumerable ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task StreamData( + Enum endpoint, + int bufferLength = 8194, + bool addXPoweredValue = true, + XTokenResponse tokens = null, + IDictionary @params = null, + IDictionary headers = null, + IDictionary queryStrings = null, + [System.Runtime.CompilerServices.EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + var url = PrepareUrl( + @params: @params, + endpoint: endpoint, + attachBaseUrl: true, + queryStrings: queryStrings + ); + var client = GetHttpClient(tokens); + if (addXPoweredValue) + { + // + client.DefaultRequestHeaders + .Add( + XAuthorization.XPoweredBy, + XPoweredValue + ); + } + + // + // For SSE, you might want to set the Accept header, though not strictly necessary for just streaming + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept + .Add( + new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream")); + + // + var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + response.EnsureSuccessStatusCode(); + + // + var stream = await response.Content.ReadAsStreamAsync(); + + // + return stream; + + // using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) + // { + // // + // int readedBuffers = 0; + // response.EnsureSuccessStatusCode(); + // var buffer = new byte[bufferLength]; + // var stream = await response.Content.ReadAsStreamAsync(); + + // // + // while ((readedBuffers = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + // { + // // + // var content = buffer.FromBytes(); + // Console.WriteLine(content); + // } + + // // await foreach (var message in stream) { + // // Console.WriteLine(message); + // // } + + // // await foreach (var item in stream.ToAsyncEnumerable(cancellationToken: cancellationToken)) + // // { + // // // + // // Console.WriteLine(item); + + // // // T value = item.IsNull() + // // // ? default(T) + // // // : item.FromBytes().FromJSON(); + + // // // // + // // // yield return value; + // // } + // } + + /// + /// + /// + /// + + // + // var eventSource = new EventSource(url); + // // eventSource. + + // // var client = GetHttpClient(tokens); + // // if (addXPoweredValue) + // // { + // // // + // // client.DefaultRequestHeaders + // // .Add( + // // XAuthorization.XPoweredBy, + // // XPoweredValue); + // // } + + // // // + // // // For SSE, you might want to set the Accept header, though not strictly necessary for just streaming + // // client.DefaultRequestHeaders.Accept.Clear(); + // // client.DefaultRequestHeaders.Accept + // // .Add( + // // new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream")); + + // // // + // // using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) + // // { + // // // + // // response.EnsureSuccessStatusCode(); + // // var stream = await response.Content.ReadAsStreamAsync(); + // // await foreach (var item in stream.ToAsyncEnumerable(cancellationToken: cancellationToken)) + // // { + // // // + // // T value = item.IsNull() + // // ? default(T) + // // : item.FromBytes().FromJSON(); + + // // // + // // yield return value; + // // } + // // } + } + + /// + /// Handle Call Specified Request and Retrieve XBaseRestResponse ... + /// + /// + /// + /// + /// + /// + /// + /// an instance of XBaseRestResponse + public async Task> RunRestRequestByResponse( + RestClient client, + RestRequest request, + XHttpMethod method, + bool supportRefreshingTokens = true, + CancellationToken cancellationToken = default + ) + { + // + var response = await Task.Run(async () => + { + // + IRestResponse resp = null; + + // + // Call Endpoint Service using Specified Method ... + switch (method) + { + // + // Get ... + case XHttpMethod.GET: + resp = client.Get(request); + break; + + // + // Put ... + case XHttpMethod.PUT: + resp = client.Put(request); + break; + + // + // Post ... + case XHttpMethod.POST: + resp = client.Post(request); + break; + + // + // Delete ... + case XHttpMethod.DELETE: + resp = client.Delete(request); + break; + } + + // + if (!resp.IsSuccessful) + { + // + // Log Exception ... + Logger.LogError($"Error: {resp.ErrorMessage}"); + Logger.LogError($"Exception: {resp.ErrorException.ToJSON()}"); + if (!resp.ErrorMessage.IsNullOrEmpty() && + resp.ErrorMessage.Contains("timed out")) + { + XException.Timeout.Throw(); + } + + // + // TODO: Check UnAuthorized Status here for + // Refresh Tokens or Issue Correct Exception ... + if (supportRefreshingTokens && + resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + // + var isRefreshed = request.Parameters.Any(rp => + rp.Name == XParam.XIsRefreshed.GetStringValue() && + rp.Value.ToString().ToNormalString() == true.ToJSON()); + + // + Logger.LogInformation($"isRefreshed: {isRefreshed}"); + + // + // Get Access Token ... + XTokenResponse tokens = null; + var accessToken = request.Parameters.FirstOrDefault(rp => + rp.Name.ToNormalString() == XAuthorization.Header.ToNormalString()).Value.ToString(); + if (!accessToken.IsNullOrEmpty()) + { + // + accessToken = accessToken.Replace(XAuthorization.TokenIdentifier, ""); + + // + if (!accessToken.IsNullOrEmpty()) + { + tokens = await tokenProvider.RetrieveToken(accessToken); + } + } + + // + // Refresh Token and then re do request ... + if (!isRefreshed && + !accessToken.IsNullOrEmpty() && + !tokens.IsNull() && + tokens.IsRefreshable()) + { + // + request.AddHeader(XParam.XIsRefreshed.GetStringValue(), true.ToJSON()); + + // + // Refreshe Tokens ... + var refreshedTokens = await identityProvider + .RefreshTokens(tokens); + + // + // Renew Rest Client with Refreshed Tokens ... + var timeout = client.Timeout; + client = GetRestClient(refreshedTokens); + client.Timeout = Timeout.Infinite; + + // + // Do Request Again using renew Client ... + return await RunRestRequestByResponse( + client: client, + request: request, + method: method + ); + } + } + + // + var ex = resp.Content.ToXError(); + if (!ex.IsNull()) + { + throw ex.ToException(); + } + + // + // UnAuthorized ... + if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + XException.NotAuthorized.Throw(); + } + + // + // NotFound ... + if (resp.StatusCode == System.Net.HttpStatusCode.NotFound) + { + XException.NotFound.Throw(); + } + + // + XException.ActionFailed.Throw(); + } + + // + var result = new XBaseRestResponse + { + Response = resp, + Model = resp.Content.FromJSON() + }; + + // + return result; + }, + cancellationToken: cancellationToken); + + // + return response; + } + + /// + /// Get User SelectBy Param + /// + /// + /// + /// + /// + public string GetUserSelectByParam( + XActionRequest model, + bool forceNotNull = true, + ICollection 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; + } + + /// + /// Prepare Url Address of Specified Enum by Filling Params + /// and Query Strings Attach ... + /// + /// + /// + /// + /// + /// + public string PrepareUrl( + Enum endpoint, + bool attachBaseUrl = false, + IDictionary @params = null, + IDictionary queryStrings = null + ) + { + // + // Retrieving Url Template ... + var url = endpoint.GetStringValue(); + + // + // Filling Parmas and Query Strings of Url ... + url = PrepareUrl( + url: url, + @params: @params, + queryStrings: queryStrings, + attachBaseUrl: attachBaseUrl + ); + + // + return url; + } + + /// + /// Prepare Url Address by Filling Params + /// and Query Strings Attach ... + /// + /// + /// + /// + /// + /// + public string PrepareUrl( + string url, + bool attachBaseUrl = false, + IDictionary @params = null, + IDictionary queryStrings = null + ) + { + // + if (attachBaseUrl) + { + // + var baseUrl = BaseUrl; + if (!baseUrl.EndsWith("/")) + { + baseUrl = $"{baseUrl}/"; + } + + // + url = $"{baseUrl}{url}"; + } + + // + // Add Route Payloads ... + if (!@params.IsNull() && @params.Count > 0) + { + // + var enumerator = @params.GetEnumerator(); + while (enumerator.MoveNext()) + { + // + var item = enumerator.Current; + + // + url = url.Replace(item.Key, item.Value.ToUrlEncoded()); + } + } + + // + // Add Query Strings ... + if (!queryStrings.IsNull() && queryStrings.Count > 0) + { + // + url = $"{url}?"; + var enumerator = queryStrings.GetEnumerator(); + while (enumerator.MoveNext()) + { + // + var item = enumerator.Current; + + // + url += $"{item.Key}={item.Value.ToUrlEncoded()}&"; + } + + // + // remove lates & ... + if (url.EndsWith("&")) + { + url = url.Substring(0, url.Length - 1); + } + } + + // + return url; + } + + /// + /// Converts UserInfo to TokenResponse model ... + /// + /// + /// + public XTokenResponse ToTokenResponse(XUserClaimsInfoDto userInfo = null) + { + // + XTokenResponse result = null; + + // + if (!userInfo.IsNull()) + { + // + result = new XTokenResponse + { + AccessToken = userInfo.AccessToken.IsNullOrEmpty() + ? "" + : userInfo.AccessToken, + RefreshToken = userInfo.RefreshToken.IsNullOrEmpty() + ? "" + : userInfo.RefreshToken, + ExpiresAt = userInfo.ExpiresAt, + }; + + // + if (userInfo.AccessToken.IsNullOrEmpty()) + { + result = null; + } + } + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Providers\XIdentityProvider.cs + +using System; +using RestSharp; +using System.Linq; +using System.Net.Http; +using Newtonsoft.Json; +using System.Threading; +using xCommons.Constants; +using xCommons.Providers; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using xHttpService.Constants; +using xIdentityModels.Models; +using xIdentityService.Models; +using RestSharp.Authenticators; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using xIdentityService.Constants; +using xIdentityService.Interfaces; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Serialization; +using xIdentityService.Configuration; + +namespace xIdentityService.Providers { + public partial class XIdentityProvider : IXIdentityProvider { + private readonly IXTokenProvider tokenProvider; + private readonly IHostingEnvironment environment; + private JsonSerializerSettings jsonSerializerSettings; + private readonly XValidationProvider validationProvider; + private readonly XIdentityServiceConfiguration identityConfiguration; + + public ILogger Logger { get; } + public IXSecurityProvider SecurityProvider { get; } + + public string BaseUrl { get; } + public string RevisionSecretKey { get; } + public bool ReadResponseAsString { get; set; } + + public XIdentityProvider ( + IXTokenProvider tokenProvider, + IHostingEnvironment environment, + ILogger logger, + IXSecurityProvider securityProvider, + XIdentityServiceConfiguration identityConfiguration, + XValidationProvider validationProvider + ) { + // + BaseUrl = identityConfiguration.Authority; + RevisionSecretKey = identityConfiguration.XRevisionSecretKey; + + // + this.environment = environment; + Logger = logger; + SecurityProvider = securityProvider; + + // + this.identityConfiguration = identityConfiguration; + this.validationProvider = validationProvider; + this.jsonSerializerSettings = new JsonSerializerSettings { + ContractResolver = new CamelCasePropertyNamesContractResolver () + }; + + // + this.tokenProvider = tokenProvider; + } + + // + #region Tools ... + /// + /// Get an Instance of Http Client + /// + /// + public HttpClient GetHttpClient () { + // + HttpClient httpClient = null; + + // + // httpClient = new HttpClient (); + var httpClientHandler = new HttpClientHandler () { + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { + // + Logger.LogInformation ( + $"XSSL 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); + + // + // Set Timeout ... + // TODO: Fix this ... + // httpClient.Timeout = TimeSpan + // .FromSeconds (identityConfiguration.DefaultClientTimeout); + + // + return httpClient; + } + + /// + /// Get an Instance of RestClient + /// + /// + public RestClient GetRestClient (XTokenResponse tokens = null) { + // + var client = new RestClient (BaseUrl); + + // + // Set Authenticator ... + if (!tokens.IsNull () && + !tokens.AccessToken.IsNullOrEmpty ()) { + client.Authenticator = new JwtAuthenticator (tokens.AccessToken); + } + + // + // Refresh Token ... + if (!tokens.IsNull () && + !tokens.RefreshToken.IsNullOrEmpty ()) { + client.AddDefaultHeader (XAuthorization.RefreshToken, tokens.RefreshToken); + } + + // + // ExpiresAt ... + if (!tokens.IsNull () && + tokens.ExpiresAt > 0) { + client.AddDefaultHeader (XAuthorization.ExpiresAt, tokens.ExpiresAt.AsHttpParamString ()); + } + + // + // Set Default Timeout ... + client.Timeout = identityConfiguration.DefaultClientTimeout; + + // + // SSL Validation Handler ... + client.RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => { + // + Logger.LogInformation ( + $"XSSL Handler: {Environment.NewLine}, sender: {sender}, {Environment.NewLine} cert: {cert}, {Environment.NewLine} chain: {chain}, {Environment.NewLine} sslPolicyErrors: {sslPolicyErrors}" + ); + + // + return true; + }; + + // + return client; + } + + /// + /// Get an Instance of RestRequest + /// + public RestRequest GetRestRequet ( + XApiAccountEndpoint endpoint, + bool addXPoweredValue = true, + IDictionary @params = null, + IDictionary headers = null, + IDictionary queryStrings = null + ) { + // + var url = endpoint.GetStringValue (); + + // + // Add Route Payloads ... + if (!@params.IsNull () && @params.Count > 0) { + // + var enumerator = @params.GetEnumerator (); + while (enumerator.MoveNext ()) { + // + var item = enumerator.Current; + + // + url = url.Replace (item.Key, item.Value.ToUrlEncoded ()); + } + } + + // + // Add Query Strings ... + if (!queryStrings.IsNull () && queryStrings.Count > 0) { + // + url = $"{url}?"; + var enumerator = queryStrings.GetEnumerator (); + while (enumerator.MoveNext ()) { + // + var item = enumerator.Current; + + // + url += $"{item.Key}={item.Value.ToUrlEncoded ()}&"; + } + + // + // remove lates & ... + if (url.EndsWith ("&")) { + url = url.Substring (0, url.Length - 1); + } + } + + // + // Create request ... + var request = new RestRequest (url, DataFormat.Json); + + // + if (addXPoweredValue) { + request.AddHeader (XAuthorization.XPoweredBy, identityConfiguration.XPoweredValue); + } + + // + // Add Headers to request ... + if (!headers.IsNull () && headers.Count > 0) { + // + var enumerator = headers.GetEnumerator (); + while (enumerator.MoveNext ()) { + // + var item = enumerator.Current; + request.AddHeader (item.Key, item.Value); + } + } + + // + return request; + } + + /// + /// Handle Call Specific Request and retrieve Response + /// + /// + /// + /// + /// + /// + /// + public async Task RunRestRequest ( + RestClient client, + RestRequest request, + XHttpMethod method, + bool supportRefreshingTokens = true + ) { + // + var response = await Task.Run (async () => { + // + IRestResponse resp = null; + + // + // Call Endpoint Service using Specified Method ... + switch (method) { + // + // Get ... + case XHttpMethod.GET: + resp = client.Get (request); + break; + + // + // Put ... + case XHttpMethod.PUT: + resp = client.Put (request); + break; + + // + // Post ... + case XHttpMethod.POST: + resp = client.Post (request); + break; + + // + // Delete ... + case XHttpMethod.DELETE: + resp = client.Delete (request); + break; + } + + // + if (!resp.IsSuccessful) { + // + // Log Exception ... + Logger.LogError ($"Error: {resp.ErrorMessage}"); + Logger.LogError ($"Exception: {resp.ErrorException.ToJSON()}"); + if (!resp.ErrorMessage.IsNullOrEmpty () && + resp.ErrorMessage.Contains ("timed out")) { + XException.Timeout.Throw (); + } + + // + // TODO: Check UnAuthorized Status here for + // Refresh Tokens or Issue Correct Exception ... + if (supportRefreshingTokens && + resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { + // + var isRefreshed = request.Parameters.Any (rp => + rp.Name == XParam.XIsRefreshed.GetStringValue () && + rp.Value.ToString ().ToNormalString () == true.ToJSON ()); + + // + Logger.LogInformation ($"isRefreshed: {isRefreshed}"); + + // + // Get Access Token ... + XTokenResponse tokens = null; + var accessToken = request.Parameters.FirstOrDefault (rp => + rp.Name.ToNormalString () == XAuthorization.Header.ToNormalString ()).Value.ToString (); + if (!accessToken.IsNullOrEmpty ()) { + // + accessToken = accessToken.Replace (XAuthorization.TokenIdentifier, ""); + + // + if (!accessToken.IsNullOrEmpty ()) { + tokens = await tokenProvider.RetrieveToken (accessToken); + } + } + + // + // Refresh Token and then re do request ... + if (!isRefreshed && + !accessToken.IsNullOrEmpty () && + !tokens.IsNull () && + tokens.IsRefreshable ()) { + // + request.AddHeader (XParam.XIsRefreshed.GetStringValue (), true.ToJSON ()); + + // + // Refreshe Tokens ... + var refreshedTokens = await RefreshTokens (tokens); + + // + // Renew Rest Client with Refreshed Tokens ... + var timeout = client.Timeout; + client = GetRestClient (refreshedTokens); + client.Timeout = Timeout.Infinite; + + // + // Do Request Again using renew Client ... + return await RunRestRequest ( + client: client, + request: request, + method: method); + } + } + + // + var ex = resp.Content.ToXError (); + if (!ex.IsNull ()) { + throw ex.ToException (); + } + + // + // UnAuthorized ... + if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { + XException.NotAuthorized.Throw (); + } + + // + // NotFound ... + if (resp.StatusCode == System.Net.HttpStatusCode.NotFound) { + XException.NotFound.Throw (); + } + + // + XException.ActionFailed.Throw (); + } + + // + var result = resp.Content.FromJSON (); + return result; + }); + + // + return response; + } + public async Task> RunRestRequestByResponse ( + RestClient client, + RestRequest request, + XHttpMethod method, + bool supportRefreshingTokens = true + ) { + // + var response = await Task.Run (async () => { + // + IRestResponse resp = null; + + // + // Call Endpoint Service using Specified Method ... + switch (method) { + // + // Get ... + case XHttpMethod.GET: + resp = client.Get (request); + break; + + // + // Put ... + case XHttpMethod.PUT: + resp = client.Put (request); + break; + + // + // Post ... + case XHttpMethod.POST: + resp = client.Post (request); + break; + + // + // Delete ... + case XHttpMethod.DELETE: + resp = client.Delete (request); + break; + } + + // + if (!resp.IsSuccessful) { + // + // Log Exception ... + Logger.LogError ($"Error: {resp.ErrorMessage}"); + Logger.LogError ($"Exception: {resp.ErrorException.ToJSON()}"); + if (!resp.ErrorMessage.IsNullOrEmpty () && + resp.ErrorMessage.Contains ("timed out")) { + XException.Timeout.Throw (); + } + + // + // TODO: Check UnAuthorized Status here for + // Refresh Tokens or Issue Correct Exception ... + if (supportRefreshingTokens && + resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { + // + var isRefreshed = request.Parameters.Any (rp => + rp.Name == XParam.XIsRefreshed.GetStringValue () && + rp.Value.ToString ().ToNormalString () == true.ToJSON ()); + + // + Logger.LogInformation ($"isRefreshed: {isRefreshed}"); + + // + // Get Access Token ... + XTokenResponse tokens = null; + var accessToken = request.Parameters.FirstOrDefault (rp => + rp.Name.ToNormalString () == XAuthorization.Header.ToNormalString ()).Value.ToString (); + if (!accessToken.IsNullOrEmpty ()) { + // + accessToken = accessToken.Replace (XAuthorization.TokenIdentifier, ""); + + // + if (!accessToken.IsNullOrEmpty ()) { + tokens = await tokenProvider.RetrieveToken (accessToken); + } + } + + // + // Refresh Token and then re do request ... + if (!isRefreshed && + !accessToken.IsNullOrEmpty () && + !tokens.IsNull () && + tokens.IsRefreshable ()) { + // + request.AddHeader (XParam.XIsRefreshed.GetStringValue (), true.ToJSON ()); + + // + // Refreshe Tokens ... + var refreshedTokens = await RefreshTokens (tokens); + + // + // Renew Rest Client with Refreshed Tokens ... + var timeout = client.Timeout; + client = GetRestClient (refreshedTokens); + client.Timeout = Timeout.Infinite; + + // + // Do Request Again using renew Client ... + return await RunRestRequestByResponse ( + client: client, + request: request, + method: method + ); + } + } + + // + var ex = resp.Content.ToXError (); + if (!ex.IsNull ()) { + throw ex.ToException (); + } + + // + // UnAuthorized ... + if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { + XException.NotAuthorized.Throw (); + } + + // + // NotFound ... + if (resp.StatusCode == System.Net.HttpStatusCode.NotFound) { + XException.NotFound.Throw (); + } + + // + XException.ActionFailed.Throw (); + } + + // + var result = new XBaseRestResponse + { + Response = resp, + Model = resp.Content.FromJSON () + }; + + // + return result; + }); + + // + return response; + } + + /// + /// Get User SelectBy Param + /// + /// + /// + /// + /// + public string GetUserSelectByParam ( + XActionRequest model, + bool forceNotNull = true, + ICollection 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Providers\XInMemoryTokenProvider.cs + +using System.Linq; +using xCommons.Extensions; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Collections.Generic; +using xIdentityService.Interfaces; + +namespace xIdentityService.Providers { + public partial class XInMemoryTokenProvider : IXTokenProvider { + private IDictionary DbContext; + + public XInMemoryTokenProvider () { + // + this.DbContext = new Dictionary (); + } + + public async Task IsTokenExists (string accessToken) { + // + // Validate Args ... + if (accessToken.IsNullOrEmpty ()) { + return false; + } + + // + // Process Result ... + var result = DbContext.Keys.Any (k => k == accessToken); + return await Task.FromResult (result); + } + + public async Task IsTokenExists (XTokenResponse tokens) { + // + // Validate Args ... + if (tokens.IsNull () || + tokens.AccessToken.IsNullOrEmpty () || + !await IsTokenExists (tokens.AccessToken)) { + return false; + }; + + // + // Process Result ... + var result = await IsTokenExists (tokens.AccessToken); + return result; + } + + public async Task RemoveToken (string accessToken) { + // + // Validate Args ... + if (accessToken.IsNullOrEmpty ()) { + return false; + } + + // + // Process Result ... + var result = DbContext.Remove (accessToken); + return await Task.FromResult (result); + } + + public async Task RemoveToken (XTokenResponse tokens) { + // + // Validate Args ... + if (tokens.IsNull () || + tokens.AccessToken.IsNullOrEmpty () || + !await IsTokenExists (tokens.AccessToken)) { + return false; + }; + + // + // Process Result ... + var result = await RemoveToken (tokens.AccessToken); + return result; + } + + public async Task RetrieveToken (string accessToken) { + // + // Validate Args ... + if (accessToken.IsNullOrEmpty () || + !await IsTokenExists (accessToken)) { + return null; + } + + // + // Process Result ... + var result = DbContext[accessToken]; + return result; + } + + public async Task AddToken (XTokenResponse tokens) { + // + // Validate Args ... + if (tokens.IsNull () || + tokens.AccessToken.IsNullOrEmpty () || + await IsTokenExists (tokens.AccessToken)) { + return false; + }; + + // + // Process Result ... + try { + // + DbContext.Add (tokens.AccessToken, tokens); + return true; + } catch { + return false; + } + } + + public async Task UpdateToken (XTokenResponse tokens) { + // + // Validate Args ... + if (tokens.IsNull () || + tokens.AccessToken.IsNullOrEmpty () || + !await IsTokenExists (tokens.AccessToken)) { + return false; + }; + + // + // Process Result ... + try { + // + DbContext[tokens.AccessToken] = tokens; + return true; + } catch { + return false; + } + } + + public async Task AddOrUpdateToken (XTokenResponse tokens) { + // + // Validate Args ... + if (tokens.IsNull () || + tokens.AccessToken.IsNullOrEmpty () + ) { + return false; + }; + + // + // Process Result ... + try { + // + var isExists = await IsTokenExists (tokens); + if (isExists) { + return await UpdateToken (tokens); + } else { + return await AddToken (tokens); + } + } catch { + return false; + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Providers\XProxyHelper.cs + +using System; +using System.Web; +using System.Linq; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Linq.Expressions; +using System.Collections.Generic; +using xIdentityService.Interfaces; +using xIdentityService.Configuration; + +namespace xIdentityService.Providers { + public class XProxyHelper : IXProxyHelper { + // + #region Props ... + public XProxyConfiguration Configuration { get; } + #endregion + + // + #region Constructor ... + public XProxyHelper (XProxyConfiguration configuration) { + this.Configuration = configuration; + } + #endregion + + // + #region Actions ... + /// + /// Validate XProxy Configuration for Client ... + /// + /// + public bool ValidateConfiguration () { + // + var result = !Configuration.IsNull () && + !Configuration.Host.IsNullOrEmpty () && + Configuration.Host.IsValidUrl () && + Configuration.Routes.HasChild (); + + // + return result; + } + + /// + /// retrie Normalize Host ... + /// + /// + public string NormalizeHost () { + // + var result = string.Empty; + + // + if (!Configuration.IsNull () && + !Configuration.Host.IsNullOrEmpty () && + Configuration.Host.IsValidUrl () + ) { + // + result = Configuration.Host + .ToNormalString (); + + // + if (result.EndsWith ("/")) { + result = result.Substring (0, result.Length - 1); + } + } + + // + return result; + } + + /// + /// retrieve all registred Routes ... + /// + /// + public IEnumerable GetRoutes () { + // + // Validate Args ... + if (Configuration.IsNull ()) { + return null; + } + + // + return Configuration.Routes; + } + + /// + /// Retrieve all Prepared url of Routes ... + /// + /// + public IEnumerable GetRouteUrls () { + // + var normalHost = NormalizeHost (); + if (normalHost.IsNullOrEmpty ()) { + return null; + } + + // + var result = GetRoutes () + .Select (r => $"{normalHost}{r.Route}"); + + // + return result; + } + + /// + /// Find an Specific Route ... + /// + /// + /// + public XProxyRouteDescriptor FindRoute (Expression> whereClause) { + // + // Validate Arg ... + if (whereClause.IsNull ()) { + return null; + } + + // + var whereFunc = whereClause + .Compile (); + + // + var result = GetRoutes () + .FirstOrDefault (whereFunc); + + // + return result; + } + + /// + /// Prepare a request url for a Proxy Route ... + /// + /// + /// + /// + public string PrepareActionUrlForXProxyRoute ( + string url, + XProxyRouteDescriptor descriptor + ) { + // + if (!url.IsValidUrl () || + url.IsNullOrEmpty () || + !ValidateConfiguration () + ) { + throw XException.InvalidArgs.ToException (); + } + + // + // find Route ... + var routeDescriptor = GetRoutes () + .FirstOrDefault (r => r + .IsSameContent (descriptor) + ); + + // + // Validate Route Descriptor ... + if ( + routeDescriptor.IsNull () || + routeDescriptor.Route.IsNullOrEmpty () + ) { + throw XException.InvalidArgs.ToException (); + } + + // + // Retrieve and Validate Host Address ... + var host = NormalizeHost (); + if (!host.IsValidUrl () || + host.IsNullOrEmpty () + ) { + throw XException.InvalidArgs.ToException (); + } + + // + url = $"{host}{routeDescriptor.Route}/{HttpUtility.UrlEncode(url)}"; + + // + return url; + } + + /// + /// Prepare a request url for a Selected Proxy Route ... + /// + /// + /// + /// + public string PrepareActionUrlForXProxyRoute ( + string url, + Expression> whereClause + ) { + // + if (!url.IsValidUrl () || + url.IsNullOrEmpty () || + whereClause.IsNull () || + !ValidateConfiguration () + ) { + throw XException.InvalidArgs.ToException (); + } + + // + var descriptor = FindRoute (whereClause); + + // + // find Route ... + var result = PrepareActionUrlForXProxyRoute ( + url: url, + descriptor: descriptor + ); + + // + return result; + } + #endregion + + // + #region Private ... + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xIdentityService\Security\XSecurityProvider.cs + +using System; +using System.IO; +using System.Text; +using xCommons.Providers; +using xCommons.Extensions; +using xIdentityService.Interfaces; +using System.Security.Cryptography; +using xIdentityService.Configuration; + +namespace xIdentityService.Security { + public partial class XSecurityProvider : IXSecurityProvider { + private readonly string secretKey; + private readonly byte[] iv; + private readonly byte[] key; + private readonly XValidationProvider validationProvider; + + public XSecurityProvider ( + XIdentityServiceConfiguration 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); + } + + 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; + } + } +} diff --git a/Documents/GeneratedContext/xDashboard-xIds-20261009_203539.txt b/Documents/GeneratedContext/xDashboard-xIds-20261009_203539.txt new file mode 100644 index 0000000..bed56fa --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xIds-20261009_203539.txt @@ -0,0 +1,29230 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Program.cs + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace xIds +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Startup.cs + +using System; +using xIds.DI; +using System.IO; +using xIds.Constants; +using xIds.Providers; +using Newtonsoft.Json; +using xIds.Interfaces; +using System.Reflection; +using xStorageService.DI; +using xCommons.Extensions; +using xCommons.Configurations; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace xIds +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + // + // Register Validation Provider and all XCommons Module Services ... + services.AddXCommons(); + + // + // Register App Configuration ... + services.AddXAppConfiguration(Configuration); + var appConfiguration = services.GetRegisteredService(); + + // + // Register XDataService Configuration ... + services.AddXDataServiceConfiguration(Configuration, ConnectionStringNames.IDENTITY_CONNECTION_NAME); + + // + // Register Allowed Origins ... + services.AddXCors(appConfiguration.AllowedOrigins); + + // + // Register Swagger ... + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + services.AddXSwagger(Configuration, xmlFilePath: xmlPath); + + // + // Register IdentityServer to DI ... + // services.AddInMemoryXIdentityServer (Configuration); + services.AddEfSupportXIdentityServer(Configuration); + + // + // Services Application and Api Key Management Services ... + services.AddXApplicationProvider(Configuration); + + // + services.AddControllers() + .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore); + + // + // Register Authentication ... + services.AddXIdentityServerAuthentication(Configuration); + + // + // Register Authorization ... + services.AddXAuthorization(); + + // + // Register Security Provider ... + services.AddSingleton(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) + { + // + if (env.IsDevelopment()) + { + // + app.UseDeveloperExceptionPage(); + + // + // app.UseXRequestLogger(); + } + + // + // Use Swagger Middleware ... + app.UseXSwagger(); + + // + app.UseHttpsRedirection(); + app.UseXForwardOptions(); + + // + // Using Cors ... + app.UseXCors(); + + // + // Force App to Use IdentityServer ... + app.UseXIdentityServer(logger); + + // + // Force app to Use Storage Service ... + app.UseXStorageService(); + + // + app.UseRouting(); + + // + app.UseAuthentication(); + app.UseAuthorization(); + + // + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + }); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Configurations\CertificatesConfigurations.cs + +namespace xIds.Configurations +{ + public class CertificatesConfigurations + { + public struct CertificateNames + { + public const string IdentityServerSigning = "IdentityServerSigning"; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Configurations\XApiKeyConfiguration.cs + +namespace xIds.Configurations +{ + /// + /// Configuration settings for API Key management, including expiration, rate limiting, and audit logging. + /// + public class XApiKeyConfiguration + { + /// + /// Generated Api Keys Prefix ... + /// + public string ApiKeyPrefix { get; set; } + + /// + /// Default expiration in minutes (e.g., 43200 = 30 days) + /// + public int DefaultExpirationMinutes { get; set; } = 43200; + + /// + /// Maximum allowed expiration in minutes (e.g., 525600 = 1 year) + /// + public int MaxExpirationMinutes { get; set; } = 525600; + + /// + /// Maximum ApiKeys per Application + /// + public int MaxKeysPerApplication { get; set; } = 10; + + /// + /// Enable audit logging + /// + public bool EnableAuditLog { get; set; } = false; + + /// + /// Default rate limit (requests per minute) + /// + public int DefaultRateLimit { get; set; } = 60; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Configurations\XDataServiceConfiguration.cs + +using xIds.Constants; + +namespace xIds.Configurations +{ + /// + /// Represent Configurations of DataService Module ... + /// + public partial class XDataServiceConfiguration + { + /// + /// Provider Type ... + /// + /// + public XDbProviders Provider { get; set; } + + /// + /// Connection String which provide Requires Data to Connect to Db Provider ... + /// + /// + public string ConnectionString { get; set; } + + /// + /// Enable Tracking of Entities ... + /// Only Used on EFCore ... + /// + /// + public bool EnableTracking { get; set; } = false; + + /// + /// Enable Logging Details of Errors ... + /// Only Used on EFCore ... + /// + /// + public bool EnableDetailedErrors { get; set; } = false; + + /// + /// Enable Logging Sensitive Data ... + /// Only Used on EFCore ... + /// + /// + public bool EnableSensitiveDataLogging { get; set; } = false; + + /// + /// this is a way to provide Default Pagination Data on XQuery based requests ... + /// + /// + public PagingConfiguration PagingConfiguration { get; set; } = new PagingConfiguration(); + } + + /// + /// this is a way to provide Default Pagination Data on XQuery based requests ... + /// + public partial class PagingConfiguration + { + /// + /// Default Page Size ... + /// + /// + public int DefaultPageSize { get; set; } = XDataServiceConstants.DEFAULT_PAGE_SIZE; + + /// + /// restrict Maximum Page Size ... + /// + /// + public int MaxAvailablePageSize { get; set; } = XDataServiceConstants.MAX_AVAILABLE_PAGE_SIZE; + + /// + /// restrice Minimum Page Size ... + /// + /// + public int MinAvailablePageSize { get; set; } = XDataServiceConstants.MIN_AVAILABLE_PAGE_SIZE; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Configurations\XIdentityResourceConfiguration.cs + +namespace xIds.Configurations +{ + /// + /// determines a resource and it's connection info + /// + public partial class XIdentityResourceConfiguration + { + public string Authority { get; set; } + public string ApiName { get; set; } + public string ApiSecret { get; set; } + + public string ClientId { get; set; } + public string ClientSecret { get; set; } + + public string XPoweredValue { get; set; } + public string XRevisionSecretKey { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Constants\ConfigurationNodeNames.cs + +namespace xIds.Constants +{ + public struct ConfigurationNodeNames + { + public const string DB_SEED_NODE_NAME = "DbSeeder"; + public const string PROVIDER_NODE_NAME = "DbProvider"; + public const string APIKEY_NODE_NAME = "ApiKeyConfiguration"; + public const string IDENTITY_NODE_NAME = "IdentityConfiguration"; + public const string DATA_SERVICE_NODE_NAME = "DataServiceConfiguration"; + public const string IDENTITY_RESOURCE_NODE_NAME = "IdentityResourceConfiguration"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Constants\ConnectionStringNames.cs + +namespace xIds.Constants +{ + public struct ConnectionStringNames + { + public const string IDENTITY_CONNECTION_NAME = "IdentityDb"; + public const string CONFIGURATION_CONNECTION_NAME = "ConfigurationDb"; + public const string PRESISTED_GRANTS_CONNECTION_NAME = "PersistedGrantDb"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Constants\IdentityMessagesKeys.cs + +namespace xIds.Constants +{ + public struct IdentityMessagesKeys + { + public const string Terms = "terms"; + public const string InviteMsg = "user_invite_msg"; + public const string RegistrationApproveMsg = "registration_approve_msg"; + public const string RegisteredMsg = "registered_msg"; + public const string VerificationCodeMsg = "verification_code_msg"; + public const string ChangePasswordMsg = "change_password_msg"; + public const string PasswordChangedMsg = "password_changed_msg"; + public const string NewDeviceLoggedInMsg = "new_device_logged_in_msg"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Constants\XDataServiceConstants.cs + +namespace xIds.Constants +{ + /// + /// /// Default Pagination Values ... + /// + public partial struct XDataServiceConstants + { + public static int DEFAULT_PAGE_SIZE = 50; + public static int MAX_AVAILABLE_PAGE_SIZE = 500; + public static int MIN_AVAILABLE_PAGE_SIZE = 20; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Constants\XDbProviderConfigurations.cs + +namespace xIds.Constants +{ + public partial class XDbProviderConfigurations + { + /// + /// Default ConnectionString Name ... + /// + public const string DEFAULT_CONNECTION_NAME = "DataConnection"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Constants\XDbProviders.cs + +namespace xIds.Constants +{ + /// + /// /// Represent Supported DBMS for Managing Data ... + /// + public enum XDbProviders + { + None, + MySQL, + SQLite, + SQLServer, + } + + /// + /// Represent Supported DBMS for Managing Data ... + /// + public partial struct ProviderType + { + public const string MySQL = "MYSQL"; + public const string SQLite = "SQLITE"; + public const string SQLServer = "SQLSERVER"; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\AccountController.cs + +using xIds.Interfaces; +using xCommons.Providers; +using xIds.Controllers.Base; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace xIds.Controllers +{ + /// + /// Provide all available tools for manipulating users and accounts + /// + [ApiController] + public partial class AccountController : XIBaseController + { + + public AccountController( + IXIdentityManager identityManager, + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + identityManager, + validationProvider + ) + { } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\ApplicationsController.cs + +using xIds.Interfaces; +using xCommons.Providers; +using xIds.Configurations; +using xIds.Controllers.Base; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace xIds.Controllers +{ + /// + /// Provide all available tools for manipulating users and accounts + /// + [ApiController] + public partial class ApplicationsController : XIBaseController + { + private readonly IXApplicationProvider provider; + private readonly XApiKeyConfiguration apiKeyConfiguration; + + public ApplicationsController( + IXApplicationProvider provider, + IXIdentityManager identityManager, + XAppConfiguration appConfiguration, + ILogger logger, + XValidationProvider validationProvider, + XApiKeyConfiguration apiKeyConfiguration + ) : base( + logger, + appConfiguration, + identityManager, + validationProvider + ) + { + this.provider = provider; + this.apiKeyConfiguration = apiKeyConfiguration; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\StartupController.cs + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using xCommons.Configurations; +using xCommons.Controllers; +using xCommons.Providers; + +namespace xIds.Controllers +{ + /// + /// Startup and Running Controller + /// + [Route("")] + [AllowAnonymous] + public class StartupController : XBaseController + { + public StartupController( + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + validationProvider + ) + { } + + /// + /// Show Configured Welcome Message + /// + /// an string message + [HttpGet("")] + [AllowAnonymous] + public virtual ActionResult Index() + { + // + var controllerName = GetControllerName(); + var message = $"{AppConfiguration.WelcomeMessage}"; + + // + return Ok(message); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Admin.cs + +using System; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class AccountController + { + // + #region Admin Actions ... + /// + /// Ban Specific Users + /// + /// an instance of XUserNameIdRequest which represents user identifier list to Ban + /// a list of banned users identifiers + [RequireXPowered] + [HttpPost("Ban")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledAdmin)] + public async Task>> Ban( + [FromBody] XUserNameIdRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotNull(model) + .AddNotZeroChilds(model.Ids) + .ValidateGroupAsync(); + + // + var result = await IdentityManager + .Ban( + User.Identity.Name, + model + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// UnBann Specific Users + /// + /// an instance of XUserNameIdRequest which represents user identifier list to Ban + /// a list of unbanned users identifiers + [RequireXPowered] + [HttpPost("UnBan")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledAdmin)] + public async Task>> UnBan( + [FromBody] XUserNameIdRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotNull(model) + .AddNotZeroChilds(model.Ids) + .ValidateGroupAsync(); + + // + var result = await IdentityManager + .UnBan( + User.Identity.Name, + model + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Check Specific User is Banned or not + /// + /// specified user's identifier + /// a boolean value which represent user banned or not + [RequireXPowered] + [Authorize(Policy = LocalApi.PolicyName)] + [HttpGet("IsBanned/{userSelectByParam?}")] + [Authorize(Policy = XPolicies.EnabledAdmin)] + [Authorize(Policy = XPolicies.EnabledAgent)] + public async Task> IsBanned( + [FromRoute] string userSelectByParam = null + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (userSelectByParam.IsNullOrEmpty()) + { + userSelectByParam = User.Identity.Name; + } + ValidationProvider.NotEmpty(userSelectByParam); + + // + var result = await IdentityManager + .IsBanned( + User.Identity.Name, + userSelectByParam + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Authentication.cs + +using System; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using IdentityModel.Client; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using Microsoft.AspNetCore.Mvc; +using xIdentityModels.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using static xIdentityHelper.XApiScopeHelper; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class AccountController + { + // + #region Authentication Actions ... + /// + /// Retrieve OAuth Discovery Document + /// + /// an instance of DiscoveryDocumentResponse + [AllowAnonymous] + [RequireXPowered] + [HttpGet("DiscoveryDocument")] + public async Task> GetDiscoveryDocument() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .RequestDiscoveryDocument(); + + // + // Ceck Response Result ... + if (result.IsError) + { + XException.InvalidConfiguration.Throw(); + } + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Request AccessToken for Specific XApiScope + /// + /// a member of XApiScope + /// an instance of XTokenResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("RequestScopeAccessToken")] + public async Task> RequestScopeAccessToken( + [FromHeader] XApiScope scope + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + var scopeName = string.Empty; + try + { + scopeName = scope.GetStringValue(); + } + catch { } + ValidationProvider.NotEmpty(scopeName); + + // + var result = await IdentityManager + .RequestScopeAccessToken(scopeName); + + // + // Ceck Response Result ... + if (result.IsError) + { + XException.InvalidConfiguration.Throw(); + } + + // + return Ok(result + .CreateXTokenResponse() + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Authenticate User + /// + /// + /// Sample request: + /// + /// { + /// "password": "", + /// "userSelectBy": "", + /// } + /// + /// + /// an instance of XLoginRequest class which represent Authentication requirements + /// an instance of XTokenResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("Authenticate")] + public async Task> Authenticate( + [FromBody] XLoginRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotNull(model) + .AddNotEmpty( + model.UserSelectBy, + model.Password + ) + .ValidateGroupAsync(); + + // + var result = await IdentityManager + .Authenticate(model); + + // + // Check Result ... + if (result.IsError) + { + throw result.GetException(); + } + + // + // Return Result ... + return Ok(result + .CreateXTokenResponse() + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Authenticate User + /// + /// + /// Sample request: + /// + /// { + /// "language": "fa-IR" + /// "password": "", + /// "userSelectBy": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XLoginRequest class which represent Authentication requirements + /// an instance of XLoginResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("Login")] + public async Task> Login( + [FromBody] XLoginRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotNull( + model, + model.Device) + .AddNotEmpty( + model.Language, + model.UserSelectBy, + model.Password + ) + .ValidateGroupAsync(); + + // + var result = await IdentityManager + .Login(model); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Refresh Expired Tokens + /// + /// + /// in addition to AccessToken, you had to pass RefreshToken due to Headers + /// + /// an instance of XTokenResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("RefreshTokens")] + public async Task> RefreshTokens() + { + // + // Do Action ... + try + { + // + // Retrieve Toke Response ... + var model = await RetrieveTokensAsXTokenResponse(); + + // + // Validate Args ... + ValidationProvider + .GroupValidationBuilder() + .AddNotNull(model) + .AddNotEmpty( + model.AccessToken, + model.RefreshToken) + .ValidateGroup(); + + // + var result = await IdentityManager + .RefreshTokens(model); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + #endregion + + // + #region Password Actions ... + /// + /// Change a User's Password ... + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - Password: user's current active password. + /// - NewPassword: user's new password to change. + /// - Device: user's client device which is an instance of XDevice. + /// - ReturnUrl: client application Login URL. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "password": "" + /// "newPassword": "", + /// "returnUrl": "http://localhost/login", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// + [RequireXPowered] + [HttpPost("ChangePassword")] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = XPolicies.EnabledUser)] + [Authorize(Policy = LocalApi.PolicyName)] + public async Task ChangePassword( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotNull( + model, + model.Device + ) + .AddNotEmpty( + model.Lang, + model.Password, + model.NewPassword, + model.ReturnUrl + ) + .ValidateGroupAsync(); + + // + // Get User Select By ... + var userSelectByParam = GetUserSelectByParam( + model, + forceNotNull: false + ); + if (userSelectByParam.IsNullOrEmpty()) + { + userSelectByParam = User.Identity.Name; + } + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Get Request ... + await IdentityManager + .ChangePassword( + model.Lang, + model.Device, + userSelectByParam, + model.Password, + model.NewPassword, + model.ReturnUrl + ); + + // + return Ok(); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Friendship.cs + +using System; +using System.Linq; +using xModels.Dtos; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using xIdentityModels.Constants; +using System.Collections.Generic; +using xIdentityModels.Navigations; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class AccountController + { + // + #region Friendship Actions ... + // + #region Actions ... + /// + /// Follow a User + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipFollowing + [RequireXPowered] + [HttpPost("Friendship/{destUser}/Follow")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> Follow( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .Follow( + User.Identity.Name, + destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Cancel Following + /// + /// a user identifier which represent destination user + /// a boolean value + [HttpPost("Friendship/{destUser}/Cancel")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> Cancel( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .Cancel( + User.Identity.Name, + destUser + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Unfollow a Follower + /// + /// a user identifier which represent destination user + /// + [HttpPost("Friendship/{destUser}/UnFollowFollower")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task UnFollowFollower( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + await IdentityManager + .UnFollowFollower( + User.Identity.Name, + destUser + ); + + // + return Ok(); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Unfollow Following + /// + /// a user identifier which represent destination user + /// + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpPost("Friendship/{destUser}/UnFollowFollowing")] + public async Task UnFollowFollowing( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + await IdentityManager + .UnFollowFollowing( + User.Identity.Name, + destUser + ); + + // + return Ok(); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Block a Follower + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipFollowing + [HttpPost("Friendship/{destUser}/Block")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> Block( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .Block( + User.Identity.Name, + destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + + // + return exResult; + } + } + + /// + /// Unblock a Blocked User + /// + /// an instance of XFriendshipFollowing + /// an instance of XFriendshipFollowing + [HttpPost("Friendship/{destUser}/UnBlock")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> UnBlock( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .UnBlock( + User.Identity.Name, + destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + + // + #region Request Handlers ... + /// + /// Accept a Following Request + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipFollower + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpPost("Friendship/{destUser}/AcceptRequest")] + public async Task> AcceptRequest( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .AcceptRequest( + User.Identity.Name, + destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Reject a Following Request + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipFollower + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpPost("Friendship/{destUser}/RejectRequest")] + public async Task> RejectRequest( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .RejectRequest( + User.Identity.Name, + destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + + // + #region Getters ... + /// + /// Check a User IsFollower of Requested User + /// + /// a user identifier which represent destination user + /// a boolean value + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("Friendship/{destUser}/IsFollower")] + public async Task> IsFollower( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .IsFollower( + User.Identity.Name, + destUser + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get Follower State of a User + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipState + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("Friendship/{destUser}/FollowerState")] + public async Task> FollowerState( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .GetFollowerState( + User.Identity.Name, + destUser + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get Specific Follower + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipFollower + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("Friendship/{destUser}/Follower")] + public async Task> Follower( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .GetFollower( + User.Identity.Name, + destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get Followers List Of Current User + /// + /// a collection of XFriendshipFollower + [HttpGet("Friendship/Followers")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> Followers() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .GetFollowers(User.Identity.Name); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + + // + return exResult; + } + } + + /// + /// Get All Followers List Includes Blocked, Requested and etc + /// of Current User + /// + /// a collection of XFriendshipFollower + [HttpGet("Friendship/AllFollowers")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> AllFollowers() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .GetAllFollowers(User.Identity.Name); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get All Followers List Includes Blocked, Requested and etc + /// of Current User based On Query Model ... + /// + /// a Query Result of XFriendDto + [HttpGet("Friendship/QueryFollowers")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> QueryFollowers( + [FromQuery] XQuery query, + [FromQuery] string destUser + ) + { + // + // Do Action ... + try + { + // + // Normalizing Dest User ... + var userInfo = await GetUserInfo(); + bool isAdmin = userInfo.Roles.Contains(XUserRoles.ADMIN); + if (destUser.IsNullOrEmpty()) + { + destUser = userInfo.UserName; + } + bool isDestUserValid = isAdmin || destUser == userInfo.UserId || destUser == userInfo.Email || destUser == userInfo.PhoneNumber || destUser == userInfo.UserName; + if (!isDestUserValid) + { + XException.NotAllowed.Throw(); + } + + // + var result = await IdentityManager + .QueryFollowers( + query: query, + userSelectByParam: destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Check a User is in Followings of Current User + /// + /// a user identifier which represent destination user + /// a boolean value + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("Friendship/{destUser}/IsFollowing")] + public async Task> IsFollowing( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .IsFollowing( + User.Identity.Name, + destUser + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get Following State Relation between Specific User + /// and Current User + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipState + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("Friendship/{destUser}/FollowingState")] + public async Task> FollowingState( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .GetFollowingState( + User.Identity.Name, + destUser + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get Specific Following Model + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipFollowing + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("Friendship/{destUser}/Following")] + public async Task> Following( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotEmpty(destUser); + + // + var result = await IdentityManager + .GetFollowing( + User.Identity.Name, + destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get Followings of Current User + /// + /// a collection of XFriendshipFollowing + [HttpGet("Friendship/Followings")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> Followings() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .GetFollowings(User.Identity.Name); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get All Following List Includes Blocked, Requested and etc + /// of Current User + /// + /// a collection of XFriendshipFollowing + [HttpGet("Friendship/AllFollowings")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> AllFollowings() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .GetAllFollowings(User.Identity.Name); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get All Following List Includes Blocked, Requested and etc + /// of Current User based On Query Model ... + /// + /// a Query Result of XFriendDto + [HttpGet("Friendship/QueryFollowings")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> QueryFollowings( + [FromQuery] XQuery query, + [FromQuery] string destUser + ) + { + // + // Do Action ... + try + { + // + // Normalizing Dest User ... + var userInfo = await GetUserInfo(); + bool isAdmin = userInfo.Roles.Contains(XUserRoles.ADMIN); + if (destUser.IsNullOrEmpty()) + { + destUser = userInfo.UserName; + } + bool isDestUserValid = isAdmin || destUser == userInfo.UserId || destUser == userInfo.Email || destUser == userInfo.PhoneNumber || destUser == userInfo.UserName; + if (!isDestUserValid) + { + XException.NotAllowed.Throw(); + } + + // + var result = await IdentityManager + .QueryFollowings( + query: query, + userSelectByParam: destUser + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + + // + #region Others ... + /// + /// Return Following UserName's List of Current User + /// + /// a collection of UserNames + [HttpGet("Friendship/FollowingsList")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> FollowingsList() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .GetFollowingList(User.Identity.Name); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Return Followers UserName's List of Current User + /// + /// a collection of UserNames + [HttpGet("Friendship/FollowersList")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> FollowersList() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .GetFollowersList(User.Identity.Name); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Get Friendship Info Model between Specific User and Current User + /// + /// a user identifier which represent destination user + /// an instance of XFriendshipInfoDto + [HttpGet("Friendship/{destUser}/FriendshipInfo")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> FriendshipInfo( + [FromRoute] string destUser + ) + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .GetFriendshipInfo(User.Identity.Name, destUser); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Open.cs + +using System; +using xCommons.Constants; +using xCommons.Attributes; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; + +namespace xIds.Controllers +{ + public partial class AccountController + { + // + // Implememnt all Open Actions here ... + + // + #region Open Actions ... + [AllowAnonymous] + [RequireXPowered] + [HttpGet("Open")] + public async Task> Get( + [FromHeader] string token, + [FromQuery] string request + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty(token) + .AddNotEmpty(request) + .ValidateGroupAsync(); + + // + // Prepare Inner Dto Model ... + var dto = new XOpenActionInnerDto + { + Token = token, + Payload = request + }; + + // + // Do Action ... + var resultDto = await IdentityManager.OpenGet(dto); + Response.Headers.Add(XAuthentication.TOKEN, resultDto.Token); + + // + // Return Result + return Ok(resultDto.Payload); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + #endregion + + // + #region Private ... + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Profile.cs + +using System; +using System.Linq; +using xModels.Dtos; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xIdentityModels.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class AccountController + { + // + #region Retrieve Actions ... + /// + /// Retrieve User Names based on UserIds + /// + /// a comma seperated list of UserIds + /// a collection of UserNames + [HttpGet("Profile/{ids}/GetNames")] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> GetNames( + [FromRoute] string ids + ) + { + // + // Do Action ... + try + { + // + ValidationProvider.NotEmpty(ids); + + // + var xIdList = ids.ParseListString(); + var result = await IdentityManager.GetUserNamesAsync( + xIdList + ); + + // + // Return Result + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// get user ids and retrieve corresponding user names + /// + /// an instance of XUserNameIdRequest which represent required UserIds collection + /// a collection of XUserNameIdResponse instance + [HttpPost("Profile/GetNameIds")] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> GetNameIds( + [FromBody] XUserNameIdRequest model + ) + { + // + // Do Action ... + try + { + // + ValidationProvider.NotNull(model); + + // + var result = await IdentityManager.GetUserNameIdsAsync( + model + ); + + // + // Return Result + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Get Profile Object of Specific User + /// + /// specifies which user profile must be retrieved + /// an instance of XUserProfileDto + [RequireXPowered] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + [HttpGet("Profile/{userSelectByParam?}")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> GetProfile( + [FromRoute] string userSelectByParam = "" + ) + { + // + // Do Action ... + try + { + // + if (userSelectByParam.IsNullOrEmpty()) + { + userSelectByParam = User.Identity.Name; + } + + // + var result = await IdentityManager + .GetUserProfileAsync( + userSelectByParam, + User.Identity.Name + ); + + // + // Return Result + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Query Users + /// for specifiy users role for query user 'role: string' and 'forceRole: boolean' in header + /// + /// how to filter results based on XQuery structure + /// an string which represent user role + /// if it's true the user must has exact role, otherwise top level users also listed + /// an instance of XQueryResult of XUserProfileDto + [RequireXPowered] + [HttpGet("Profile/Query")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledAdmin)] + [Authorize(Policy = XPolicies.EnabledAgent)] + public async Task>> QueryProfiles( + [FromQuery] XQuery query, [FromHeader] string role, [FromHeader] bool forceRole = false + ) + { + // + // Do Action ... + try + { + // + var userId = User.Identity.Name; + + // + var result = new XQueryResult(); + if (role.IsNullOrEmpty()) + { + // + result = await IdentityManager.QueryUsers( + userId, + query + ); + } + else + { + // + result = await IdentityManager.QueryInRoleUsers( + userId, + role, + query, + forceRole + ); + } + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exception = GetExceptionActionResult(ex); + return exception; + } + } + + /// + /// Query Specified User's Profile Images + /// + /// determine's which user profile must be retrieved + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XProfileImage + [RequireXPowered] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [Authorize(Policy = XPolicies.EnabledAdmin)] + [HttpGet("Profile/Avatars/Query/{userSelectByParam?}")] + public async Task>> QueryAvatars( + [FromQuery] XQuery query, [FromRoute] string userSelectByParam = null + ) + { + // + // Do Action ... + try + { + // + if (userSelectByParam.IsNullOrEmpty()) + { + userSelectByParam = User.Identity.Name; + } + + // + var result = await IdentityManager.QueryAvatars( + userSelectByParam, + query + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exception = GetExceptionActionResult(ex); + return exception; + } + } + #endregion + + // + #region Update Actions ... + /// + /// Update User Profile based on XProfileUpdateRequest (FirstName/LastName/DateOfBirth) + /// + /// user update info, an instance of XProfileUpdateRequest + /// determine's which user profile must be retrieved + /// an instance of XUserProfileDto + [RequireXPowered] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpPost("Profile/Update/{userSelectByParam?}")] + public async Task> UpdateProfile( + [FromBody] XProfileUpdateRequest model, [FromRoute] string userSelectByParam = null + ) + { + // + // Do Action ... + try + { + // + if (userSelectByParam.IsNullOrEmpty()) + { + userSelectByParam = User.Identity.Name; + } + + // + var result = await IdentityManager.ProfileUpdateAsync( + userSelectByParam, + User.Identity.Name, + model + ); + + // + // Return Result + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Update a User Profile based on XProfileUpdateRequest Full + /// + /// user update info, an instance of XProfileUpdateRequest + /// determine's which user profile must be retrieved + /// an instance of XUserProfileDto + [RequireXPowered] + [Authorize(Policy = LocalApi.PolicyName)] + [HttpPost("Profile/{userSelectByParam?}")] + [Authorize(Policy = XPolicies.EnabledAdminOrAgent)] + public async Task> FullUpdateProfile( + [FromBody] XProfileUpdateRequest model, [FromRoute] string userSelectByParam = null + ) + { + // + // Do Action ... + try + { + // + if (userSelectByParam.IsNullOrEmpty()) + { + userSelectByParam = User.Identity.Name; + } + + // + var result = await IdentityManager.FullProfileUpdateAsync( + userSelectByParam, + User.Identity.Name, + model + ); + + // + // Return Result + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + #endregion + + // + #region Avatar Actions ... + /// + /// Add a New Profile Image + /// + /// an specific File to upload, IFormFile + /// an instance of XUserProfileDto + [RequireXPowered] + [HttpPost("Profile/Avatar")] + [RequestSizeLimit(966_367_641)] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> AddAvatar( + [FromForm] IFormFile file + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotNull(file); + + // + // Get Request ... + var result = await IdentityManager + .AddAvatar( + User.Identity.Name, + file + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Add a New Collection of Profile Images + /// + /// a collection of Files to upload, IFormFileCollection + /// an instance of XUserProfileDto + [RequireXPowered] + [HttpPost("Profile/Avatars")] + [RequestSizeLimit(966_367_641)] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> AddAvatars( + [FromForm] IFormFileCollection files + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + ValidationProvider.NotZeroChilds(files); + + // + // Get Request ... + var result = await IdentityManager.AddAvatars( + User.Identity.Name, + files + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Remove Profile Images + /// + /// a comma seperated list of avatarIds to remove + /// an instance of XUserProfileDto + [RequireXPowered] + [Authorize(Policy = XPolicies.User)] + [HttpDelete("Profile/Avatars/{ids}")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> RemoveAvatars( + [FromRoute] string ids + ) + { + // + // Do Action ... + try + { + // + ValidationProvider.NotEmpty(ids); + var idList = ids.Split(","); + var idCollection = new Collection(); + + // + foreach (var s in idList) + { + idCollection.Add(int.Parse(s)); + } + + // + // Get Request ... + var result = await IdentityManager + .RemoveAvatar( + User.Identity.Name, + idCollection.ToArray() + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// set Specified Profile Image as Avatar + /// + /// an integer which reperesent AvatarId to set as current Avatar + /// an instance of XUserProfileDto + [RequireXPowered] + [HttpPost("Profile/Avatars/{id:int}/Set")] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> SetAvatar( + [FromRoute] int id + ) + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .SetAvatar( + User.Identity.Name, + id + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exception = GetExceptionActionResult(ex); + return exception; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Query.cs + +using System; +using xModels.Dtos; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class AccountController + { + /// + /// Query Users + /// for specifiy users role for query user 'role: string' and 'forceRole: boolean' in header + /// + /// how to filter results based on XQuery structure + /// an string which represent user role + /// if it's true the user must has exact role, otherwise top level users also listed + /// an instance of XQueryResult of XUserProfileDto + [RequireXPowered] + [HttpGet("Users/Query")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> QueryUsers( + [FromQuery] XQuery query, + [FromHeader] string role, + [FromHeader] bool forceRole = false + ) + { + // + // Do Actions ... + try + { + // + // Retrieve User Info ... + var userInfo = await GetUserInfo(); + + // + // Retrieve Result Based on Provided Query ... + var result = await IdentityManager.QueryUsers( + role: role, + query: query, + forceRole: forceRole, + requestedUserSelectByParam: userInfo.UserId + ); + + // + // Return result ... + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exception = GetExceptionActionResult(ex); + return exception; + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Registration.cs + +using System; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using xIdentityModels.Constants; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class AccountController + { + // + #region CanRegister ... + /// + /// Check a UserName is Available For Registration or not + /// + /// an string value which represent desired username + /// a boolean value + [AllowAnonymous] + [RequireXPowered] + [HttpGet("CanRegisterUserName/{userName?}")] + public async Task> CanRegisterUserName( + [FromRoute] string userName + ) + { + // + // Validate Args ... + if (userName.IsNullOrEmpty()) + { + return BadRequest( + XException.InvalidArgs + .ToXError() + ); + } + + // + // Do Action ... + try + { + // + var result = await IdentityManager + .CanRegister(userName); + + // + // Return Result + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + + // + return result; + } + } + + /// + /// Check a Mobile Number is Available For Registration or not + /// + /// an string value which represent desired mobile number + /// a boolean value + [AllowAnonymous] + [RequireXPowered] + [HttpGet("CanRegisterMobileNumber/{mobileNumber?}")] + public async Task> CanRegisterMobileNumber( + [FromRoute] string mobileNumber + ) + { + // + // Validate Args ... + if (mobileNumber.IsNullOrEmpty()) + { + return BadRequest( + XException.InvalidArgs + .ToXError() + ); + } + if (!mobileNumber.IsValidMobileNumber()) + { + return BadRequest( + XException.InvalidMobileNumber + .ToXError() + ); + } + + // + // Do Action ... + try + { + // + var result = await IdentityManager + .CanRegister(mobileNumber); + + // + // Return Result + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + + // + return result; + } + } + + /// + /// Check a Email Address is Available For Registration or not + /// + /// an string value which represent desired email address + /// a boolean value + [AllowAnonymous] + [RequireXPowered] + [HttpGet("CanRegisterEmail/{email?}")] + public async Task> CanRegisterEmail( + [FromRoute] string email + ) + { + // + // Validate Args ... + if (email.IsNullOrEmpty()) + { + return BadRequest( + XException.InvalidArgs + .ToXError() + ); + } + if (!email.IsValidEmail()) + { + return BadRequest( + XException.InvalidEmailAddress + .ToXError() + ); + } + + // + // Do Action ... + try + { + // + var result = await IdentityManager + .CanRegister(email); + + // + // Return Result + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + + // + return result; + } + } + #endregion + + // + #region Confirmations ... + /// + /// Check User is Confirmed Email or not + /// + /// a boolean value + [RequireXPowered] + [HttpGet("IsConfirmedEmail")] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + public async Task> IsConfirmedEmail() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .IsConfirmedEmail(User.Identity.Name); + + // + // Return Result + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + + // + return result; + } + } + + /// + /// Check User is Confirmed Mobile or not + /// + /// a boolean value + [RequireXPowered] + [HttpGet("IsConfirmedMobile")] + [Authorize(Policy = XPolicies.User)] + [Authorize(Policy = LocalApi.PolicyName)] + public async Task> IsConfirmedMobile() + { + // + // Do Action ... + try + { + // + var result = await IdentityManager + .IsConfirmedMobile(User.Identity.Name); + + // + // Return Result + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + + // + return result; + } + } + + /// + /// Confirm Registration + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - ActionToken: user's requested action token for this action. + /// - Device: user's client device which is an instance of XDevice. + /// - ReturnUrl: client application Login URL. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "actionToken": "" + /// "returnUrl": "http://localhost/login", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// + [AllowAnonymous] + [RequireXPowered] + [HttpPost("ConfirmRegistration")] + public async Task ConfirmRegistration( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.ActionToken, + model.ReturnUrl + ) + .AddNotNull(model.Device) + .ValidateGroupAsync(); + + // + // Get Request ... + await IdentityManager + .ConfirmRegistration( + model.Lang, + model.Device, + model.ActionToken, + model.ReturnUrl + ); + + // + return Ok(); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Confirm Requested Mobile Number + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - ActionToken: user's requested action token for this action. + /// - Device: user's client device which is an instance of XDevice. + /// - MobileVerificationCode: a verification code which produced due Request Action. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "actionToken": "" + /// "mobileVerificationCode": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("ConfirmMobileNumber")] + public async Task> ConfirmMobileNumber( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.ActionToken, + model.MobileVerificationCode + ) + .AddNotNull(model.Device) + .ValidateGroupAsync(); + + // + // Get Request ... + var result = await IdentityManager + .ConfirmMobileNumber( + model.Lang, + model.Device, + model.ActionToken, + model.MobileVerificationCode + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Confirm Requested Email Address + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - ActionToken: user's requested action token for this action. + /// - Device: user's client device which is an instance of XDevice. + /// - EmailVerificationCode: a verification code which produced due Request Action. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "actionToken": "" + /// "emailVerificationCode": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("ConfirmEmailAddress")] + public async Task> ConfirmEmailAddress( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.ActionToken, + model.EmailVerificationCode + ) + .AddNotNull(model.Device) + .ValidateGroupAsync(); + + // + // Get Request ... + var result = await IdentityManager + .ConfirmEmailAddress( + model.Lang, + model.Device, + model.ActionToken, + model.EmailVerificationCode + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + + // + #region Request for Actions ... + /// + /// Request For Mobile Number Change/Confirm + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - Password: user's current active password. + /// - MobileNumber: which phone number is going to confirm. + /// - Device: user's client device which is an instance of XDevice. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "password": "", + /// "mobileNumber": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("RequestConfirmMobile")] + public async Task> RequestConfirmMobile( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.Password, + model.MobileNumber + ) + .AddNotNull(model.Device) + .AddMobileNumber(model.MobileNumber) + .ValidateGroupAsync(); + + // + // Get UserSelect By Param ... + var userSelectByParam = GetUserSelectByParam( + model, + excludes: new List { + XUserSelectBy.Email, + XUserSelectBy.MobileNumber + }); + + // + // Get Request ... + var result = await IdentityManager + .RequestConfirmMobile( + model.Lang, + model.Device, + userSelectByParam, + model.Password, + model.MobileNumber + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Request For Email Address Change/Confirm + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - Password: user's current active password. + /// - Email: which email address is going to confirm. + /// - Device: user's client device which is an instance of XDevice. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "password": "", + /// "email": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("RequestConfirmEmail")] + public async Task> RequestConfirmEmail( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.Email, + model.Password) + .AddNotNull(model.Device) + .AddEmailAddress(model.Email) + .ValidateGroupAsync(); + + // + // Get UserSelect By Param ... + var userSelectByParam = GetUserSelectByParam( + model, + excludes: new List { + XUserSelectBy.Email, + XUserSelectBy.MobileNumber + }); + + // + // Get Request ... + var result = await IdentityManager + .RequestConfirmEmail( + model.Lang, + model.Device, + userSelectByParam, + model.Password, + model.Email + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Request Reset Password Action + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - Device: user's client device which is an instance of XDevice. + /// - ReturnUrl: client application ResetPassword URL. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "returnUrl": "http://localhost/reset-password", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("RequestResetPassword")] + public async Task> RequestResetPassword( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.ReturnUrl) + .AddNotNull(model.Device) + .AddUrl(model.ReturnUrl) + .ValidateGroupAsync(); + + // + // Get UserSelect By Param ... + var userSelectByParam = GetUserSelectByParam(model); + + // + // Get Request ... + var result = await IdentityManager + .RequestResetPassword( + model.Lang, + model.Device, + userSelectByParam, + model.ReturnUrl + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Reset a User's Password + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - ActionToken: user's requested action token for this action. + /// - NewPassword: user's new password to change. + /// - Device: user's client device which is an instance of XDevice. + /// - ReturnUrl: client application Login URL. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "actionToken": "", + /// "newPassword": "", + /// "returnUrl": "http://localhost/login", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// + [AllowAnonymous] + [RequireXPowered] + [HttpPost("ResetPassword")] + public async Task ResetPassword( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.ActionToken, + model.NewPassword, + model.ReturnUrl) + .AddNotNull(model.Device) + .ValidateGroupAsync(); + + // + // Get Request ... + await IdentityManager + .ResetPassword( + model.Lang, + model.Device, + model.ActionToken, + model.NewPassword, + model.ReturnUrl + ); + + // + return Ok(); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Request Registration Confirm + /// + /// + /// for this action you have to provide: + /// - a UserSelectedBy identifier which represet User. + /// - Lang: client device currently used locale, such as: en-US. + /// - Password: user's current active password. + /// - Device: user's client device which is an instance of XDevice. + /// - ReturnUrl: client application Confirm Registration URL. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "userId": "" + /// "password": "", + /// "returnUrl": "http://localhost/confirm", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// + [AllowAnonymous] + [RequireXPowered] + [HttpPost("RequestConfirmRegistration")] + public async Task RequestConfirmRegistration( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.Password, + model.ReturnUrl) + .AddNotNull(model.Device) + .AddUrl(model.ReturnUrl) + .ValidateGroupAsync(); + + // + // Get UserSelect By Param ... + var userSelectByParam = GetUserSelectByParam(model); + + // + // Get Request ... + await IdentityManager + .RequestConfirmRegistration( + model.Lang, + model.Device, + userSelectByParam, + model.Password, + model.ReturnUrl + ); + + // + return Ok(); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + #endregion + + // + #region Registration ... + /// + /// Invite a User to Register on Dashboard + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - Email: which email address is going to invite. + /// - Device: user's client device which is an instance of XDevice. + /// - ReturnUrl: client application Registration URL. + /// notice in this step you had to provide all this informations as FormData + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "email": "", + /// "returnUrl": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [RequireXPowered] + [HttpPost("InviteUser")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledAdmin)] + [Authorize(Policy = XPolicies.EnabledAgent)] + public async Task> InviteUser( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + + // + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.Email, + model.ReturnUrl) + .AddEmailAddress(model.Email) + .AddNotNull(model.Device) + .ValidateGroupAsync(); + + // + var result = await IdentityManager + .InviteUser( + model.Lang, + model.Device, + model.Email, + model.ReturnUrl); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Recieve some Basic Informations and Start Registration Proccess + /// if they Valid + /// + /// Registration Proccess Starts with Invoking this Action + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - FirstName: user's FirstName. + /// - LastName: user's LastName. + /// - DateOfBirth: user's dob date. + /// - MobileNumber: user's MobileNumber optional. + /// - Email: user's Email optional. + /// - ActionToken: user's requested action token for this action (if user invited). + /// - Device: user's client device which is an instance of XDevice. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "firstName": "", + /// "lastName": "", + /// "dateOfBirth": "", + /// "email": "", + /// "mobileNumber": "", + /// "actionToken": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("RequestRegistration")] + public async Task> RequestRegistration( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.FirstName, + model.LastName) + .AddNotNull( + model.Device, + model.DateOfBirth) + .ValidateGroupAsync(); + + // + // Check Date Of Birth ... + if (!model.DateOfBirth.HasValue) + { + XException.InvalidArgs.Throw(); ; + } + + // + // Validate Mobile Number if Exists ... + if (!model.MobileNumber.IsNullOrEmpty()) + { + ValidationProvider.MobileNumber(model.MobileNumber); + } + + // + // Validate Email Address if Exists ... + if (!model.Email.IsNullOrEmpty()) + { + ValidationProvider.EmailAddress(model.Email); + } + + // + // Get Request ... + var result = await IdentityManager + .RequestRegistration( + model.Lang, + model.Device, + model.ActionToken, + model.FirstName, + model.LastName, + model.DateOfBirth.GetValueOrDefault(), + model.MobileNumber, + model.Email + ); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + + // + return exResult; + } + } + + /// + /// Add User Account Informations + /// + /// + /// for this action you have to provide: + /// - Lang: client device currently used locale, such as: en-US. + /// - ActionToken: user's requested action token for this action. + /// - UserName: user name. + /// - Password: assigne password. + /// - Device: user's client device which is an instance of XDevice. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "userName": "", + /// "password": "", + /// "actionToken": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("AddAccountInfo")] + public async Task> AddAccountInfo( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.ActionToken, + model.UserName, + model.Password) + .AddNotNull(model.Device) + .ValidateGroupAsync(); + + // + // Get Request ... + var result = await IdentityManager + .AddAcountInfo( + model.Lang, + model.Device, + model.ActionToken, + model.UserName, + model.Password); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Upload and Attach a Profile Image for User. this is an Optional Step + /// + /// user's requested action token + /// an instance of IFormFile for user's Avatar + /// an instance of XActionResponse + [AllowAnonymous] + [RequireXPowered] + [HttpPost("AttachProfileImage")] + public async Task> AttachProfileImage( + [FromForm] string actionToken, [FromForm] IFormFile file + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty(actionToken) + .AddNotNull(file) + .ValidateGroupAsync(); + + // + var result = await IdentityManager + .AttachProfileImage(actionToken, file); + + // + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + return exResult; + } + } + + /// + /// Finish Registrationn Proccess + /// + /// + /// for this action you have to provide: + /// - a proper User Identifier such as: (UserId, UserName, Email or MobileNumber). + /// - Lang: client device currently used locale, such as: en-US. + /// - ActionToken: user's requested action token for this action. + /// - Password: assigne password. + /// - Device: user's client device which is an instance of XDevice. + /// - ReturnUrl: client application Login URL. + /// + /// Sample request: + /// + /// { + /// "lang": "fa-IR", + /// "userName": "", + /// "password": "", + /// "returnUrl": "", + /// "device": { + /// "os":"Mac", + /// "browser":"Chrome", + /// "osVersion":"mac-os-x-15", + /// "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36", + /// "deviceType":3, + /// "identifier":"[Mac]-[mac-os-x-15]-[3]-[Chrome]-[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) x-framework-test/0.0.0 Chrome/87.0.4280.141 Electron/11.2.0 Safari/537.36]", + /// "token":"96f400dd9aa5c57a8cec9d6f4775bad3" + /// } + /// } + /// + /// + /// an instance of XActionRequest class which provider requirement for action + /// + [AllowAnonymous] + [RequireXPowered] + [HttpPost("FinishRegistration")] + public async Task FinishRegistration( + [FromBody] XActionRequest model + ) + { + // + // Do Action ... + try + { + // + // Validate Args ... + if (!ModelState.IsValid) + { + XException.InvalidArgs.Throw(); + } + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + model.Lang, + model.Password, + model.ReturnUrl) + .AddNotNull(model.Device) + .AddUrl(model.ReturnUrl) + .ValidateGroupAsync(); + + // + var userSelectByParam = IdentityManager + .GetUserSelectByParam(model); + ValidationProvider + .NotEmpty(userSelectByParam); + + // + // Get Request ... + await IdentityManager + .FinishRegistration( + model.Lang, + model.Device, + userSelectByParam, + model.Password, + model.ReturnUrl + ); + + // + return Ok(); + } + catch (Exception ex) + { + // + var exResult = GetExceptionActionResult(ex); + + // + return exResult; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Search.cs + +using System; +using System.Linq; +using xModels.Dtos; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + /// + /// Actions which related to Search and Suggest Users for + /// Friendship or Detecting ... + /// + public partial class AccountController + { + // + #region Actions of Search ... + /// + /// Query Open To Search Users Profiles ... + /// Query all Users Which their Profiles is Open To Search ... + /// + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XUserProfileDto + [RequireXPowered] + [HttpGet("OpenToSearch/Query")] + [Authorize(Policy = LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> QueryOpenToSearchProfiles( + [FromQuery] XQuery query) + { + // + // Do Action ... + try + { + // + var userId = User.Identity.Name; + + // + var result = await IdentityManager.QueryOpenToSearchUsers( + userId, + query + ); + + return Ok(result + .ToDynamicObject()); + } + catch (Exception ex) + { + // + var exception = GetExceptionActionResult(ex); + return exception; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Account\AccountController+Test.cs + +using System.Linq; +using xIdentityHelper; +using xCommons.Attributes; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class AccountController + { + + // + #region Test Actions ... + /// + /// API Read Scope + /// + /// string message + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpGet("Test/PassReadAccess")] + [Authorize(Policy = XPolicies.ReadAccess)] + public ActionResult PassReadAccess() + { + return Ok("Read Access Passed ..."); + } + + /// + /// API Write Scope + /// + /// string message + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpGet("Test/PassWriteAccess")] + [Authorize(Policy = XPolicies.WriteAccess)] + public ActionResult PassWriteAccess() + { + return Ok("Write Access Passed ..."); + } + + /// + /// API Admin Scope + /// + /// string message + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpGet("Test/PassAdminAccess")] + [Authorize(Policy = XPolicies.AdminAccess)] + public ActionResult PassAdminAccess() + { + return Ok("Admin Access Passed ..."); + } + + /// + /// API Manage Scop + /// + /// string message + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpGet("Test/PassManageAccess")] + [Authorize(Policy = XPolicies.ManageAccess)] + public ActionResult PassManageAccess() + { + return Ok("Manage Access Passed ..."); + } + + /// + /// a simple Action which returns a List of Authenticated User Claims + /// + /// string message which represent current user's claims + [RequireXPowered] + [HttpGet("Test/HiClaims")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.User)] + public ActionResult HiClaims() + { + // + var result = new + { + name = User.Identity.Name, + claims = User.Claims.Select(c => new + { + c.Type, + c.Value + }) + }; + + // + return Ok(result); + } + + /// + /// a simple Hello User for Checking Authentication and Policy + /// + /// string message which contains authenticated user name + [RequireXPowered] + [HttpGet("Test/HiUser")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.User)] + public ActionResult HiUser() + { + // + var result = $"Hi User: {User.Identity.Name} ..."; + + // + return Ok(result); + } + + /// + /// a simple Hello User for Checking Authentication and Policy + /// + /// string message which contains authenticated user name + [RequireXPowered] + [HttpGet("Test/HiEnabledUser")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public ActionResult HiEnabledUser() + { + // + var result = $"Hi User: {User.Identity.Name} is Enabled ..."; + + // + return Ok(result); + } + + /// + /// a simple Hello User for Checking Authentication and Policy + /// + /// string message which contains authenticated user name + [RequireXPowered] + [HttpGet("Test/HiAdmin")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.Admin)] + public ActionResult HiAdmin() + { + // + var result = $"Hi Admin: {User.Identity.Name} ..."; + + // + return Ok(result); + } + + /// + /// a simple Hello User for Checking Authentication and Policy + /// + /// string message which contains authenticated user name + [RequireXPowered] + [HttpGet("Test/HiEnabledAdmin")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledAdmin)] + public ActionResult HiEnabledAdmin() + { + // + var result = $"Hi Admin: {User.Identity.Name} is Enabled ..."; + + // + return Ok(result); + } + + [RequireXPowered] + [HttpGet("Test/HiApiKeyAccess")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.ApiKeyAccess)] + public ActionResult HiApiKeyAccess() + { + // + var result = $"Hi ApiKey Access, is Passed ..."; + + // + return Ok(result); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Applications\ApplicationsController+ApiKey.cs + +using System; +using System.Linq; +using xModels.Dtos; +using System.Threading; +using xIdentityHelper; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class ApplicationsController + { + // + #region ApiKey ... + /// + /// Get Specified ApiKey ... + /// + /// + /// + /// + [RequireXPowered] + [HttpGet("ApiKeys/{id}")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> GetApiKey( + [FromRoute] Guid id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (id.IsDefaultGuid()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.GetApiKeyAsync( + id: id, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Remove Specified ApiKey ... + /// + /// + /// + /// + [RequireXPowered] + [HttpDelete("ApiKeys/{id}")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> RemoveApiKey( + [FromRoute] Guid id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (id.IsDefaultGuid()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.RemoveApiKeyAsync( + id: id, + softDelete: false, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Update Specified ApiKey ... + /// + /// + /// + /// + /// + [RequireXPowered] + [HttpPut("ApiKeys/{id}")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> UpdateApiKey( + [FromRoute] Guid id, + [FromBody] XApiKeyDto item, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + id.IsDefaultGuid() || + !item.IsValidForAdd() + ) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.UpdateApiKeyAsync( + id: id, + item: item, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Get All Specified ApiKeys ... + /// + /// + /// + [RequireXPowered] + [HttpGet("ApiKeys")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> GetAllApiKeys( + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.GetAllApiKeyAsync( + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.CreatedOn) + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Query ApiKeys ... + /// + /// + /// + /// + [RequireXPowered] + [HttpGet("ApiKeys/Query")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> QueryApiKeys( + [FromRoute] XQuery query, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.QueryApiKeyAsync( + query: query, + predicate: null, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.CreatedOn) + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Applications\ApplicationsController+ApiKeyUsage.cs + +using System; +using System.Linq; +using xModels.Dtos; +using xIdentityHelper; +using System.Threading; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class ApplicationsController + { + // + #region ApiKeyUsage ... + /// + /// Add Api Key Usage ... + /// + /// + /// + /// + [RequireXPowered] + [HttpPost("ApiKeys/Usages")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> AddApiKeyUsage( + [FromBody] XApiKeyUsageDto item, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (item.IsValidForAdd()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.AddApiKeyUsageAsync( + item: item, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Get Specified Api Key Usage ... + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpGet("ApiKeys/Usages/{id}")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> GetApiKeyUsage( + [FromRoute] long id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (id <= 0) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.GetApiKeyUsageAsync( + id: id, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Remove Specified Api Key Usage ... + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpDelete("ApiKeys/Usages/{id}")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> RemoveApiKeyUsage( + [FromRoute] long id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (id <= 0) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.RemoveApiKeyUsageAsync( + id: id, + softDelete: false, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Update Specified Api Key Usage ... + /// + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpPut("ApiKeys/Usages/{id}")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> UpdateApiKeyUsage( + [FromRoute] long id, + [FromBody] XApiKeyUsageDto item, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + id <= 0 || + !item.IsValidForAdd() + ) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.UpdateApiKeyUsageAsync( + id: id, + item: item, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Get All Api Key Usages ... + /// + /// + /// + [RequireXPowered] + [HttpGet("ApiKeys/Usages")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> GetAllApiKeyUsages( + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.GetAllApiKeyUsageAsync( + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.RequestedOn) + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Query Api Key Usages ... + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpGet("ApiKeys/Usages/Query")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> QueryApiKeyUsages( + [FromRoute] XQuery query, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.QueryApiKeyUsageAsync( + query: query, + predicate: null, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.RequestedOn) + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Applications\ApplicationsController+Application.cs + +using System; +using System.Linq; +using xModels.Dtos; +using xIdentityHelper; +using System.Threading; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using xIdentityModels.Extensions; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class ApplicationsController + { + // + #region Application ... + /// + /// Create Application ... + /// + /// + /// Sample request: + /// + /// { + /// "type": 0, + /// "name": "string", + /// "description": "string", + /// } + /// + /// + /// + /// + /// + [HttpPost("")] + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> CreateApplication( + [FromBody] XApplicationDto item, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!item.IsValidForAdd()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.AddApplicationAsync( + item: item, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Update Specified Application ... + /// + /// + /// Sample request: + /// + /// { + /// "id": 0, + /// "type": 0, + /// "name": "string", + /// "description": "string", + /// } + /// + /// + /// + /// + /// + [HttpPut("")] + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> UpdateApplication( + [FromBody] XApplicationDto item, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!item.IsValidForAdd()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.UpdateApplicationAsync( + item: item, + id: item.Id, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Remove Specified Application ... + /// + /// + /// + /// + [RequireXPowered] + [HttpDelete("{id}")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> RemoveApplication( + [FromRoute] int id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!id.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.RemoveApplicationAsync( + id: id, + softDelete: false, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Get Specified Application ... + /// + /// + /// + /// + [RequireXPowered] + [HttpGet("{id}")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> GetApplication( + [FromRoute] int id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!id.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.GetApplicationAsync( + id: id, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Get All Application ... + /// + /// + /// + [HttpGet("")] + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> GetAllApplications( + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.GetAllApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.Id) + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Query Applications ... + /// + /// + /// + /// + [RequireXPowered] + [HttpGet("Query")] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> QueryApplications( + [FromRoute] XQuery query, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.QueryApplicationAsync( + query: query, + predicate: null, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.Id) + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Applications\ApplicationsController+Custom.cs + +using System; +using System.Linq; +using xIdentityHelper; +using System.Threading; +using xCommons.Attributes; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using xIdentityModels.Models; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using Microsoft.AspNetCore.Authorization; +using static IdentityServer4.IdentityServerConstants; + +namespace xIds.Controllers +{ + public partial class ApplicationsController + { + /// + /// Show Api Key ... + /// + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("{applicationId}/ApiKeys/{apiKeyId}/Show")] + public async Task> ShowApiKey( + [FromRoute] Guid apiKeyId, + [FromRoute] int applicationId, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + !applicationId.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.ShowApiKey( + userInfo: userInfo, + apiKeyId: apiKeyId, + applicationId: applicationId, + cancellationToken: cancellationToken + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Create Api Key ... + /// + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpPost("{applicationId}/ApiKeys/Create")] + public async Task> CreateApiKey( + [FromRoute] int applicationId, + [FromBody] XCreateApiKeyRequest request, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + !request.IsValid() || + !applicationId.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + // Retrieve Application for Validating ... + var application = await provider.GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Noramlize TimeSpan ... + var expirationTimeSpan = TimeSpan + .FromMinutes(apiKeyConfiguration.DefaultExpirationMinutes); + if ( + request.ExpirationMinutes.HasValue && + request.ExpirationMinutes.Value > 0 + ) + { + // + // Normalize Expiration Minutes ... + int minutes = request.ExpirationMinutes.Value; + if (minutes > apiKeyConfiguration.MaxExpirationMinutes) + { + minutes = apiKeyConfiguration.MaxExpirationMinutes; + } + + // + expirationTimeSpan = TimeSpan.FromMinutes(minutes); + } + + // + var result = await provider.CreateApiKey( + userInfo: userInfo, + scopes: request.Scopes, + applicationId: applicationId, + rateLimit: request.RateLimit, + allowedIPs: request.AllowedIPs, + expiration: expirationTimeSpan, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Remove Specified Api Key ... + /// + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpDelete("{applicationId}/ApiKeys/{apiKeyId}")] + public async Task> RemoveApiKey( + [FromRoute] Guid apiKeyId, + [FromRoute] int applicationId, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + !applicationId.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + // Retrieve Application for Validating ... + var application = await provider.GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await provider.RemoveApiKeyAsync( + id: apiKeyId, + softDelete: false, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Rotate Specified Api Key ... + /// + /// + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpPost("{applicationId}/ApiKeys/{apiKeyId}")] + public async Task> RotateApiKey( + [FromRoute] Guid apiKeyId, + [FromRoute] int applicationId, + [FromBody] XRotateApiKeyRequest request, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + !applicationId.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + // Normalize ... + if ( + request.IsNullOrDefault() || + request.NewExpiration.IsNull() || + !request.NewExpiration.HasValue + ) + { + // + var timeSpan = TimeSpan.FromMinutes(apiKeyConfiguration.DefaultExpirationMinutes); + request = new XRotateApiKeyRequest + { + NewExpiration = timeSpan + }; + } + + // + var userInfo = await GetUserInfo(); + + // + // Retrieve Application for Validating ... + var application = await provider.GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await provider.RotateApiKey( + apiKeyId: apiKeyId, + userInfo: userInfo, + newExpiration: request.NewExpiration, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Revoke Specified Api Key ... + /// + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpPost("{applicationId}/ApiKeys/{apiKeyId}/Revoke")] + public async Task> RevokeApiKey( + [FromRoute] Guid apiKeyId, + [FromRoute] int applicationId, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + !applicationId.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + // Retrieve Application for Validating ... + var application = await provider.GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await provider.RevokeApiKey( + apiKeyId: apiKeyId, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Retrieve Specified Application's Api Keys ... + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [HttpGet("{applicationId}/ApiKeys")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task>> GetApiKeys( + [FromRoute] int applicationId, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!applicationId.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + // Retrieve Application for Validating ... + var application = await provider.GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await provider.GetApplicationApiKeys( + userInfo: userInfo, + applicationId: applicationId, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Retrieve Specified Api Keys Usages ... + /// + /// + /// + /// + /// + [RequireXPowered] + [Authorize(LocalApi.PolicyName)] + [Authorize(Policy = XPolicies.EnabledUser)] + [HttpGet("{applicationId}/ApiKeys/{apiKeyId}/Usages")] + public async Task>> GetApiKeyUsages( + [FromRoute] Guid apiKeyId, + [FromRoute] int applicationId, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + !applicationId.IsValidIntId()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + + // + // Retrieve Application for Validating ... + var application = await provider.GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Retrieve ApiKey for Validating ... + var apiKey = await provider.GetApiKeyAsync( + id: apiKeyId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await provider.FindManyApiKeyUsageAsync( + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + predicate: x => x.ApiKeyId == apiKeyId, + orderBuilder: x => x.OrderBy(y => y.RequestedOn) + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Validate Specifed Api Key ... + /// + /// + /// + /// + [RequireXPowered] + [HttpPost("ApiKeys/Validate")] + [Authorize(LocalApi.PolicyName)] + public async Task> ValidateApiKey( + [FromBody] XApiKeyValidationRequest request, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotNull(request) + .AddNotEmpty(request.ApiKey) + .AddNotEmpty(request.ClientIP) + .ValidateGroupAsync(); + + // + var userInfo = await GetUserInfo(); + + // + var result = await provider.ValidateApiKey( + userInfo: userInfo, + apiKey: request.ApiKey, + clientIP: request.ClientIP, + requiredScope: request.RequiredScope, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Controllers\Base\XIBaseController.cs + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using xCommons.Configurations; +using xCommons.Constants; +using xCommons.Controllers; +using xCommons.Extensions; +using xCommons.Providers; +using xExceptions.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Models; +using xIds.Interfaces; + +namespace xIds.Controllers.Base +{ + public abstract class XIBaseController : XBaseController + { + public IXIdentityManager IdentityManager { get; } + + protected XIBaseController( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityManager identityManager, + XValidationProvider validationProvider + ) : base( + logger, + appConfiguration, + validationProvider + ) + { + IdentityManager = identityManager; + } + + // + #region User Handlers NonActions ... + /// + /// Retrieve User Identifier Base on XActionRequest + /// + /// + /// + /// + /// + [NonAction] + public string GetUserSelectByParam( + XActionRequest model, + bool forceNotNull = true, + ICollection excludes = null) + { + // + // Validate Args ... + ValidationProvider.NotNull(model); + + // + var result = IdentityManager.GetUserSelectByParam( + model, + forceNotNull, + excludes); + + // + return result; + } + + /// + /// Retrieve Access Token + /// + /// + [NonAction] + public async Task GetAccessToken() + { + // + var accessToken = Request.Headers[XAuthorization.Header].ToString(); + if (accessToken.IsNullOrEmpty()) + { + accessToken = await HttpContext.GetTokenAsync(XAuthorization.AccessToken); + } + + // + if (accessToken + .ToNormalString() + .Contains(XAuthorization.TokenIdentifier.ToNormalString())) + { + accessToken = accessToken.Remove(0, XAuthorization.TokenIdentifier.Length); + } + + // + Logger.LogInformation($"Token: {accessToken}"); + return accessToken; + } + + /// + /// Retrieve Refresh Token + /// + /// + [NonAction] + public async Task GetRefreshToken() + { + // + var refreshToken = Request.Headers[XAuthorization.RefreshToken].ToString(); + if (refreshToken.IsNullOrEmpty()) + { + refreshToken = await HttpContext.GetTokenAsync(XAuthorization.RefreshToken); + } + + // + Logger.LogInformation($"Refresh Token: {refreshToken}"); + return refreshToken; + } + + /// + /// Retrieve Access Token Expiration Date + /// + /// + [NonAction] + public async Task GetTokenExpiresAt() + { + // + var expiresAtStr = Request.Headers[XAuthorization.ExpiresAt].ToString(); + if (expiresAtStr.IsNullOrEmpty()) + { + expiresAtStr = await HttpContext.GetTokenAsync(XAuthorization.ExpiresAt); + } + + // + var expiresAt = expiresAtStr.ConvertTo(); + + // + Logger.LogInformation($"Token ExpiresAt: {expiresAtStr}"); + return expiresAt; + } + + /// + /// Retrieve All Required Tokens + /// + /// + [NonAction] + public async Task RetrieveTokensAsXLoginResponse() + { + // + var accessToken = await GetAccessToken(); + var refreshToken = await GetRefreshToken(); + var expiresAt = await GetTokenExpiresAt(); + + // + return new XLoginResponse + { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + + /// + /// Retrieve All Required Tokens + /// + /// + [NonAction] + public async Task RetrieveTokensAsXTokenResponse() + { + // + var accessToken = await GetAccessToken(); + var refreshToken = await GetRefreshToken(); + var expiresAt = await GetTokenExpiresAt(); + + // + return new XTokenResponse + { + AccessToken = accessToken, + RefreshToken = refreshToken, + ExpiresAt = expiresAt + }; + } + + /// + /// Retrive UserInfo + /// + /// + [NonAction] + public async Task GetUserInfo() + { + // + var claims = User.Claims ?? null; + if (!claims.HasChild()) + { + return null; + } + + var xTokens = await RetrieveTokensAsXTokenResponse(); + var result = new XUserClaimsInfoDto( + xTokens.AccessToken, + xTokens.RefreshToken, + xTokens.ExpiresAt, + User.Claims + ); + + // + return result; + } + + /// + /// Validate User Authenticated and Retrieve User Info + /// + /// + [NonAction] + public async Task ValidateAndGetUserInfo() + { + // + if (!User.Identity.IsAuthenticated) + { + XException.NotAuthorized.Throw(); + } + + // + var result = await GetUserInfo(); + if (result.IsNull()) + { + XException.NotAuthorized.Throw(); + } + + // + return result; + } + #endregion + + // + #region NonActions ... + /// + /// Convert an Exception to Propper Error Result + /// + /// + /// + [NonAction] + public new ActionResult GetExceptionActionResult(Exception ex) + { + // + var exception = GetExceptionResult(ex); + var error = ex.Message.ToXError(); + + // + Logger.LogError($"exception: {exception}, error: {error}"); + + // + try + { + var xError = exception.Message.ToXError(); + var xException = (XException)xError.Id; + + // + switch (xException) + { + // + case XException.NotFound: + return NotFound(error); + + // + case XException.NotAuthorized: + return Unauthorized(error); + } + } + catch { } + + // + return BadRequest(error); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\DbContext\XIdentityDbContext.cs + +using xIdentityModels; +using xIdentityModels.Models; +using xIdentityModels.Navigations; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; + +namespace xIds.DbContext +{ + public class XIdentityDbContext : IdentityDbContext + { + // + #region Props/DbSets ... + public DbSet Tokens { get; set; } + public DbSet Devices { get; set; } + public DbSet ApiKeys { get; set; } + public DbSet Avatars { get; set; } + public DbSet ApiKeyUsages { get; set; } + public DbSet Applications { get; set; } + public DbSet BannedDevices { get; set; } + public DbSet Followers { get; set; } + public DbSet Followings { get; set; } + public DbSet VerificationRequests { get; set; } + #endregion + + public XIdentityDbContext(DbContextOptions options) : base(options) + { + // + try + { + Database.EnsureCreated(); + Database.Migrate(); + } + catch { } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // + base.OnModelCreating(modelBuilder); + + // + // Fix XUser GUID Generator on Add ... + modelBuilder + .Entity() + .Property(e => e.Id) + .ValueGeneratedOnAdd(); + + // + // Handle Role List ... + modelBuilder.Entity(u => + { + u.HasMany(x => x.Roles) + .WithOne() + .HasForeignKey(ur => ur.UserId) + .IsRequired(); + }); + + // + // Configure XApplication ... + modelBuilder.Entity(e => + { + e.HasIndex(a => a.Name).IsUnique(); + e.HasMany(a => a.ApiKeys) + .WithOne(k => k.Application) + .HasForeignKey(k => k.ApplicationId); + }); + + // + // Configure XApiKey ... + modelBuilder.Entity(e => + { + e.HasIndex(k => k.KeyHash).IsUnique(); + e.HasIndex(k => k.ExpiresAt); + }); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\DI\XDIHelperExtension.cs + +using xIds.Helpers; +using xIds.Constants; +using xIds.DbContext; +using xIds.Providers; +using IdentityServer4; +using xIdentityHelper; +using xIdentityModels; +using xIds.Extensions; +using xIds.Interfaces; +using xIds.Validators; +using xCommons.Constants; +using xMessageService.DI; +using xStorageService.DI; +using xCommons.Extensions; +using xIds.Configurations; +using xExceptions.Constants; +using xCommons.Authorization; +using System.Collections.Generic; +using xMessageService.Interfaces; +using xStorageService.Interfaces; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Identity; +using xIdentityModels.Configurations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; +using xIdentityService.Constants; + +namespace xIds.DI +{ + public static class XDIHelperExtension + { + /// + /// Register Application DataProvider + /// + /// + /// + public static void AddXDataProvider( + this IServiceCollection services, + IConfiguration configuration + ) + { + // + // Retrieve DbInfo ... + var dbInfo = configuration.GetXDbInfo(); + var identityDbConnectionString = configuration.GetConnectionString(ConnectionStringNames.IDENTITY_CONNECTION_NAME); + + // + // Register Db Context for EF ... + services.AddDbContext(builder => + { + // + builder.PrepareXDbContextOptionsBuilder( + dbInfo, + identityDbConnectionString + ); + }); + } + + /// + /// Extract Connection String From IConfiguration + /// + /// + /// + /// + public static string GetXConnectionString( + this IConfiguration config, + string connectionName = null + ) + { + // + if (connectionName.IsNullOrEmpty()) + { + connectionName = XDbProviderConfigurations.DEFAULT_CONNECTION_NAME; + } + + // + return config.GetConnectionString(connectionName); + } + + /// + /// Retrive XDataService Configurations ... + /// + /// + /// + /// + public static XDataServiceConfiguration GetXDataServiceConfiguration( + this IConfiguration source, + string connectionName = null + ) + { + // + var xDataServiceConfigSection = source + .GetSection(Constants.ConfigurationNodeNames.DATA_SERVICE_NODE_NAME); + var result = xDataServiceConfigSection.Get(); + if (result.IsNull()) + { + result = new XDataServiceConfiguration(); + } + + // + result.Provider = source.GetXDbProviderType(); + result.ConnectionString = source.GetXConnectionString(connectionName); + + // + return result; + } + + /// + /// Retrieve XApiKeyConfiguration Configurations ... + /// + /// + /// + public static XApiKeyConfiguration GetXApiKeyConfiguration(this IConfiguration source) + { + // + var section = source + .GetSection(Constants.ConfigurationNodeNames.APIKEY_NODE_NAME); + var result = section.Get(); + if (result.IsNull()) + { + result = new XApiKeyConfiguration(); + } + + // + return result; + } + + /// + /// Register XDataService Configuration + /// + /// + /// + /// + public static void AddXDataServiceConfiguration( + this IServiceCollection services, + IConfiguration configuration, + string connectionName = null + ) + { + // + var dataServiceConfiguration = configuration + .GetXDataServiceConfiguration(connectionName); + + // + services.AddSingleton(dataServiceConfiguration); + } + + /// + /// Add Specified ApiKey Configurations Instance ... + /// + /// + /// + public static void AddXApiKeyConfiguration( + this IServiceCollection source, + XApiKeyConfiguration configuration + ) + { + // + if (configuration.IsNull()) + { + return; + } + + // + source.AddSingleton(configuration); + } + + /// + /// Extract APpi KLey Configurations from app settings + /// and Register in Services ... + /// + /// + /// + public static void AddXApiKeyConfiguration( + this IServiceCollection source, + IConfiguration configuration + ) + { + // + var config = configuration.GetXApiKeyConfiguration(); + if (config.IsNull()) + { + return; + } + + // + source.AddXApiKeyConfiguration(config); + } + + /// + /// Register Application Provider ... + /// Managing Applications/ApiKeys/ApiKeyUsages ... + /// + /// + /// + public static void AddXApplicationProvider( + this IServiceCollection source, + IConfiguration configuration + ) + { + // + source.AddXApiKeyConfiguration(configuration); + + // + source.AddMemoryCache(); + source.AddScoped(); + } + + /// + /// Register All Requirements For XIdentityServer Usage + /// + /// + /// + public static void AddXIdentityServerRequirements( + this IServiceCollection services, + IConfiguration configuration + ) + { + // + services.AddXDataProvider(configuration); + + // + // Register XIdentityMessageProvider ... + services.AddXIdentityMessageProvider(); + + // + // Register IdentityManager Service ... + var xIdentityManager = services.GetRegisteredService(); + if (xIdentityManager.IsNull()) + { + services.AddXIdentityManager(); + } + + // + // Check XMessage Service Registered or not and Register it if not ... + var xMessageProvider = services.GetRegisteredService(); + if (xMessageProvider.IsNull()) + { + services.AddXMessageService(configuration); + } + + // + // Check Storage Service Registered or not and Register it if not ... + var xStorageProvider = services.GetRegisteredService(); + if (xStorageProvider.IsNull()) + { + services.AddXStorageService(configuration); + } + + // + // Register XIdentityConfiguration ... + var xIdentityConfiguration = configuration.GetXIdentityConfiguration(); + services.AddSingleton(xIdentityConfiguration); + + // + // Register XIdentityHelper ... + services.AddSingleton(); + var xIdentityHelper = services.GetRegisteredService(); + + // + // Register Asp.net Identity ... + services.AddXIdentity(); + } + + /// + /// Add IdentityServer with Support of Ef + /// + /// + /// + public static void AddEfSupportXIdentityServer( + this IServiceCollection services, + IConfiguration configuration + ) + { + // + // Register All Requirements ... + services.AddXIdentityServerRequirements(configuration); + + // + // add DbSeeder Config ... + services.AddXDebSeederDescriptor(configuration); + + // + // Retrieve Certificate ... + var identityServerSigningCertificate = configuration.GetCertificate(CertificatesConfigurations.CertificateNames.IdentityServerSigning); + + // + // Retrieve DbInfo ... + var dbInfo = configuration.GetXDbInfo(); + + // + // Retrieve Connection String Names ... + var configDbConnectionString = configuration.GetConnectionString(ConnectionStringNames.CONFIGURATION_CONNECTION_NAME); + var persistedGrantDbConnectionString = configuration.GetConnectionString(ConnectionStringNames.PRESISTED_GRANTS_CONNECTION_NAME); + + // + // Register IdentityServer ... + services.AddIdentityServer(options => + { + // + options.Discovery.CustomEntries.Add("account_api", "~/account"); + + // + // options.Events.RaiseErrorEvents = true; + // options.Events.RaiseFailureEvents = true; + // options.Events.RaiseSuccessEvents = true; + // options.Events.RaiseInformationEvents = true; + }) + .LoadSigningCredentialFrom(identityServerSigningCertificate) + .AddXDbProvider(configuration) + .AddAspNetIdentity() + .AddProfileService() + .AddResourceOwnerValidator(); + } + + /// + /// force app to use IdentityServer + /// + /// + /// + public static void UseXIdentityServer( + this IApplicationBuilder app, + ILogger logger + ) + { + // + // Update XIdentityServer Configurations before Use Identity Server ... + app.SeedData(logger) + .GetAwaiter() + .GetResult(); + + // + // Use Identity Server ... + app.UseIdentityServer(); + } + + /// + /// Register Asp.net Identity as User Store + /// + /// + public static void AddXIdentity(this IServiceCollection services) + { + // + var xIdentityConfiguration = services.GetRegisteredService(); + if (xIdentityConfiguration.IsNull()) + { + XException.InvalidConfiguration.Throw(); + } + + // + var xIdentityHelper = services.GetRegisteredService(); + if (xIdentityHelper.IsNull()) + { + XException.InvalidConfiguration.Throw(); + } + + // + // Add Identity for User Persists ... + services.AddIdentity(options => + { + // + var mLockoutSpan = xIdentityHelper.GetLockoutTimeSpan(); + + // + // Lockout Setting ... + options.Lockout.DefaultLockoutTimeSpan = mLockoutSpan; + options.Lockout.MaxFailedAccessAttempts = xIdentityConfiguration.Policy.Lockout.MaxFailedAccessAttempts; + options.Lockout.AllowedForNewUsers = xIdentityConfiguration.Policy.Lockout.AllowedForNewUsers; + + // + // Password settings ... + options.Password.RequireDigit = xIdentityConfiguration.Policy.Password.RequireDigit; + options.Password.RequireLowercase = xIdentityConfiguration.Policy.Password.RequireLowercase; + options.Password.RequireNonAlphanumeric = xIdentityConfiguration.Policy.Password.RequireNonAlphanumeric; + options.Password.RequireUppercase = xIdentityConfiguration.Policy.Password.RequireUppercase; + options.Password.RequiredLength = xIdentityConfiguration.Policy.Password.RequiredLength; + options.Password.RequiredUniqueChars = xIdentityConfiguration.Policy.Password.RequiredUniqueChars; + + // + // SignIn settings ... + options.SignIn.RequireConfirmedEmail = xIdentityConfiguration.Policy.SignIn.RequireConfirmedEmail; + options.SignIn.RequireConfirmedPhoneNumber = xIdentityConfiguration.Policy.SignIn.RequireConfirmedPhoneNumber; + + // + // User settings ... + options.User.AllowedUserNameCharacters = xIdentityConfiguration.Policy.User.AllowedUserNameCharacters; + options.User.RequireUniqueEmail = xIdentityConfiguration.Policy.User.RequireUniqueEmail; + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + } + + /// + /// Register IdentityServer Based Authentication + /// + /// + /// + /// + public static void AddXIdentityServerAuthentication( + this IServiceCollection services, + IConfiguration configuration, + bool addApiKeyAuthentication = false + ) + { + // + services.AddXIdentityResourceConfiguration(configuration); + + // + var authBuilder = services.AddAuthentication(); + + // + // Add JWT Bearer ... + authBuilder.AddJwtBearer(options => + { + // + options.SaveToken = true; + options.RequireHttpsMetadata = false; + + // + options.ForwardDefault = XAuthentication.IDENTITY_SERVER_LOCAL_API; + }) + .AddLocalApi(); + + // + if (addApiKeyAuthentication) + { + // + authBuilder.AddScheme( + XAuthenticationScheme.XApiKey.GetStringValue(), + options => {} + ); + } + } + + /// + /// Register Authorization Policies + /// + /// + /// + public static void AddXAuthorization( + this IServiceCollection services, + IDictionary policies = null + ) + { + // + services.AddAuthorization(options => + { + // + if (!policies.IsNull()) + { + // + // Fill Policies ... + using (var policiesEnumerator = policies.GetEnumerator()) + { + while (policiesEnumerator.MoveNext()) + { + var policyDescriptor = policiesEnumerator.Current; + options.AddPolicy(policyDescriptor.Key, policyDescriptor.Value); + } + } + } + + // + var xPolicies = XAuthorizationHelper.GetXAuthorizationPolicies(); + if (!xPolicies.IsNull()) + { + // + // Fill Policies ... + using (var policiesEnumerator = xPolicies.GetEnumerator()) + { + while (policiesEnumerator.MoveNext()) + { + var policyDescriptor = policiesEnumerator.Current; + options.AddPolicy(policyDescriptor.Key, policyDescriptor.Value); + } + } + } + + // + // Add Local APi Policy ... + options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy => + { + // + policy.AddAuthenticationSchemes(IdentityServerConstants.LocalApi.AuthenticationScheme); + policy.RequireAuthenticatedUser(); + }); + }); + + // + services.AddSingleton(); + } + + /// + /// Use Forward Headers Options for resolving behind a proxy issues ... + /// + /// + /// + public static void UseXForwardOptions(this IApplicationBuilder source) + { + // + var forwardOptions = new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, + RequireHeaderSymmetry = false + }; + + // + forwardOptions.KnownNetworks.Clear(); + forwardOptions.KnownProxies.Clear(); + + // + // ref: https://github.com/aspnet/Docs/issues/2384 + source.UseForwardedHeaders(forwardOptions); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Extensions\DbExtensions.cs + +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using MySql.EntityFrameworkCore.Infrastructure; +using xCommons.Extensions; +using xIdentityModels.Configurations; +using xIds.Configurations; +using xIds.Constants; +using xIds.Models; + +namespace xIds.Extensions +{ + public static class DbExtensions + { + /// + /// Retrieve Data Provider Type from Configurations + /// + /// + /// + public static XDbProviders GetXDbProviderType(this IConfiguration source) + { + // + var dbProvider = (source[$"{ConfigurationNodeNames.PROVIDER_NODE_NAME}"]) + .ToNormalString(); + + // + return dbProvider == ProviderType.SQLServer.ToNormalString() ? + XDbProviders.SQLServer : + dbProvider == ProviderType.SQLite.ToNormalString() ? + XDbProviders.SQLite : + dbProvider == ProviderType.MySQL.ToNormalString() ? + XDbProviders.MySQL : + XDbProviders.None; + } + + /// + /// Retrieve AspNet Identity Configurations + /// + /// + /// + public static XIdentityConfiguration GetXIdentityConfiguration(this IConfiguration source) + { + // + var xIdentityConfigSection = source + .GetSection(ConfigurationNodeNames.IDENTITY_NODE_NAME); + return xIdentityConfigSection.Get(); + } + + /// + /// Retrieve XIdentityResource Configuration from AppSettings + /// + /// + /// + public static XIdentityResourceConfiguration GetXIdentityResourceConfiguration(this IConfiguration configuration) + { + // + var xIdentityResourceSection = configuration.GetSection(ConfigurationNodeNames.IDENTITY_RESOURCE_NODE_NAME); + return xIdentityResourceSection.Get(); + } + + /// + /// Register XIdentityResourceConfiguration + /// + /// + /// + public static void AddXIdentityResourceConfiguration( + this IServiceCollection services, + IConfiguration configuration + ) + { + // + var xIdentityResourceConfiguration = configuration.GetXIdentityResourceConfiguration(); + if (!xIdentityResourceConfiguration.IsNull()) + { + services.AddSingleton(xIdentityResourceConfiguration); + } + } + + /// + /// Converts DbInfo Options Builder for MySql Usage + /// + /// + /// + public static Action GetMySqlOptionsBuilder(this XDbInfo source) + { + return ((Action)source.OptionsBuilder); + } + + /// + /// Converts DbInfo Options Builder for SQLite Usage + /// + /// + /// + public static Action GetSQLiteOptionsBuilder(this XDbInfo source) + { + return ((Action)source.OptionsBuilder); + } + + /// + /// Converts DbInfo Options Builder for SQLServer Usage + /// + /// + /// + public static Action GetSQLServerOptionsBuilder(this XDbInfo source) + { + return ((Action)source.OptionsBuilder); + } + + /// + /// Retrieve Required Informations to Register DbContexts into Di as XDbInfo instance + /// + /// + /// + public static XDbInfo GetXDbInfo(this IConfiguration configuration) + { + return new XDbInfo + { + ProviderType = configuration.GetXDbProviderType(), + MigrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + }; + } + + /// + /// Prepare DbContextOptionsBuilder with Propper data to Support Configured DbProvider + /// + /// + /// + /// + public static void PrepareXDbContextOptionsBuilder( + this DbContextOptionsBuilder source, + XDbInfo dbInfo, + string connectionString + ) + { + // + if (dbInfo.ProviderType == XDbProviders.MySQL) + { + // + // Add Support for MySql ... + source.UseMySQL( + connectionString, + dbInfo.GetMySqlOptionsBuilder() + ); + } + else if (dbInfo.ProviderType == XDbProviders.SQLite) + { + // + // Add Support for SQLite ... + source.UseSqlite( + connectionString, + dbInfo.GetSQLiteOptionsBuilder() + ); + } + else if (dbInfo.ProviderType == XDbProviders.SQLServer) + { + // + // Add Support for SQLServer ... + source.UseSqlServer( + connectionString, + dbInfo.GetSQLServerOptionsBuilder() + ); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Extensions\IdentityExtensions.cs + +using xIds.Providers; +using xIds.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace xIds.Extensions +{ + public static class IdentityExtensions + { + /// + /// Register IdentityManager + /// + /// + public static void AddXIdentityManager(this IServiceCollection source) + { + source.AddScoped(); + } + + /// + /// Register IdentityMessage Provider + /// + /// + public static void AddXIdentityMessageProvider( + this IServiceCollection source + ) + { + source.AddScoped(); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Extensions\IIdentityServerBuilderExtensions.cs + +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using xCommons.Extensions; +using xCommons.Models; +using xIds.Constants; + +namespace xIds.Extensions +{ + public static class IIdentityServerBuilderExtensions + { + /// + /// Add Support for Configured XDbProvider to IdentityServer + /// + /// + /// + /// + public static IIdentityServerBuilder AddXDbProvider( + this IIdentityServerBuilder source, + IConfiguration configuration + ) + { + // + // Retrieve DB Info ... + var dbInfo = configuration.GetXDbInfo(); + dbInfo.OptionsBuilder = (optionsBuilder) => + { + optionsBuilder.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); + optionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + }; + + // + // Retrieve Connection String Names ... + var identityDbConnectionString = configuration.GetConnectionString(ConnectionStringNames.IDENTITY_CONNECTION_NAME); + var configDbConnectionString = configuration.GetConnectionString(ConnectionStringNames.CONFIGURATION_CONNECTION_NAME); + var persistedGrantDbConnectionString = configuration.GetConnectionString(ConnectionStringNames.PRESISTED_GRANTS_CONNECTION_NAME); + + // + // Add Operational Store ... + source.AddOperationalStore(opt => + { + opt.ConfigureDbContext = + builder => + { + // + builder.PrepareXDbContextOptionsBuilder( + dbInfo, + persistedGrantDbConnectionString + ); + + // + builder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + }; + + // + // this enables automatic token cleanup. this is optional. + opt.EnableTokenCleanup = true; + opt.TokenCleanupInterval = 3600; + }); + + // + // Add Configuration Store ... + source.AddConfigurationStore(opt => + { + opt.ConfigureDbContext = + builder => + { + // + builder.PrepareXDbContextOptionsBuilder( + dbInfo, + configDbConnectionString + ); + }; + }); + + // + return source; + } + + /// + /// Set Configured Certificate File to IdentityServer + /// + /// + /// + /// + public static IIdentityServerBuilder LoadSigningCredentialFrom( + this IIdentityServerBuilder builder, + XCertificate certificate + ) + { + // + if (!certificate.IsNull() && + !certificate.Path.IsNullOrEmpty() && + !certificate.Secret.IsNullOrEmpty() + ) + { + try + { + builder.AddSigningCredential(new X509Certificate2(certificate.Path, certificate.Secret)); + } + catch + { + builder.AddDeveloperSigningCredential(); + } + } + else + { + builder.AddDeveloperSigningCredential(); + } + + // + return builder; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Extensions\IQueryableExtensions.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using xCommons.Extensions; +using xExceptions.Constants; +using xModels.Base; +using xIds.Constants; + +namespace xIds.Extensions +{ + public static class IQueryableExtensions + { + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Extensions\XDbSeederExtensions.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.EntityFramework.DbContexts; +using IdentityServer4.EntityFramework.Mappers; +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using xCommons.Extensions; +using xIdentityModels.Descriptors; +using xIdentityModels.Extensions; +using xIds.Constants; +using xIds.Interfaces; +using xIds.Models; + +namespace xIds.Extensions +{ + public static class XDbSeeder + { + /// + /// Retrieve DbSeedDescriptor from appSetting Configuration + /// + /// + /// + public static XDbSeedDescriptor GetXDbSeedDescriptor(this IConfiguration configuration) + { + // + var dbSeedSection = configuration.GetSection(ConfigurationNodeNames.DB_SEED_NODE_NAME); + return dbSeedSection.Get(); + } + + /// + /// Register XDbSeedDescriptor as Singleton Service + /// + /// + /// + public static void AddXDebSeederDescriptor( + this IServiceCollection services, + IConfiguration configuration + ) + { + // + var dbSeedDescriptor = configuration.GetXDbSeedDescriptor(); + if (!dbSeedDescriptor.IsNull()) + { + services.AddSingleton(dbSeedDescriptor); + } + else + { + Console.WriteLine("Error: DbSeeder Configuration not found in AppSetting ..."); + } + } + + /// + /// Add Base Required Data to XIdentitySer DbContexts + /// + /// + /// + /// + public static async Task SeedData( + this IApplicationBuilder app, + ILogger logger + ) + { + // + logger.LogInformation($"Start Add/Update XIdentityServer Descriptors ..."); + + // + // Create Scope ... + using (var scope = app.ApplicationServices.CreateScope()) + { + // + // Retrieve Required Configurations ... + var dbSeedDescriptor = scope.ServiceProvider.GetService(); + if (dbSeedDescriptor.IsNull()) + { + // + Console.WriteLine("DbSeeder is Null ..."); + return; + } + logger.LogInformation($"Update Exists Descriptors: {dbSeedDescriptor.UpdateExists.ToString()}"); + + // + // Retrieve Required Services ... + var identityProvider = scope.ServiceProvider.GetService(); + + // + // Retrieve DbContexts ... + var grantDbContext = scope.ServiceProvider.GetService(); + var configDbContext = scope.ServiceProvider.GetService(); + + // + #region Clients ... + // + logger.LogInformation($"Start Seeding Clients ..."); + + // + var dClients = dbSeedDescriptor.Clients; + var hasClient = configDbContext.Clients.Any(); + if ((!hasClient || + (hasClient && dbSeedDescriptor.UpdateExists)) && + dClients.HasChild()) + { + // + var clientEntities = dClients.Select(dc => dc.ToEntity()); + + // + foreach (var client in clientEntities) + { + // + logger.LogInformation($"Add/Update: {client.ClientId}"); + + // + // Retrieve Exists Client ... + var existsClient = configDbContext.Clients + .Include(c => c.AllowedCorsOrigins) + .Include(c => c.AllowedGrantTypes) + .Include(c => c.AllowedScopes) + .Include(c => c.Claims) + .Include(c => c.ClientSecrets) + .FirstOrDefault(cl => cl.ClientId == client.ClientId); + + // + var isExistsClient = !existsClient.IsNull(); + var isSameContent = isExistsClient && + existsClient.IsSameContent(client, propertyBlackList: new[] { nameof(existsClient.Id) }); + + // + if (isExistsClient) + { + // + if (!isSameContent) + { + // + logger.LogInformation($"Update Client: {existsClient.ClientId}"); + + // + existsClient = existsClient.UpdateData(client, propertyBlackList: new[] { nameof(existsClient.Id) }); + configDbContext.Clients.Update(existsClient); + } + } + else + { + // + logger.LogInformation($"Add Client: {client.ClientId}"); + + // + await configDbContext.Clients.AddAsync(client); + } + } + + // + // Save Changes on Config Db Context ... + await configDbContext.SaveChangesAsync(); + } + + // + logger.LogInformation($"Finish Seeding Clients ..."); + #endregion + + // + #region IdentityResources ... + // + logger.LogInformation($"Start Seeding Identity Resources ..."); + + // + var dIdentityResources = dbSeedDescriptor.IdentityResources; + var hasIdentityResources = configDbContext.IdentityResources.Any(); + if ((!hasIdentityResources || + (hasIdentityResources && dbSeedDescriptor.UpdateExists)) && + dIdentityResources.HasChild()) + { + // + var identityResourceEntities = dIdentityResources.Select(di => di.ToEntity()); + + // + foreach (var identityResource in identityResourceEntities) + { + // + logger.LogInformation($"Add/Update: {identityResource.DisplayName}"); + + // + // Retrieve Exists IdentityResource ... + var existsIdentityResource = configDbContext.IdentityResources + .AsNoTracking() + .FirstOrDefault(ir => ir.Name == identityResource.Name); + + // + var isExistsResource = !existsIdentityResource.IsNull(); + var isSameContent = isExistsResource && + existsIdentityResource.IsSameContent(identityResource, propertyBlackList: new[] { nameof(existsIdentityResource.Id) }); + + // + if (isExistsResource && + dbSeedDescriptor.UpdateExists) + { + // + if (!isSameContent) + { + // + logger.LogInformation($"Update IdentityResource: {identityResource.DisplayName}"); + + // + existsIdentityResource = existsIdentityResource.UpdateData(identityResource, propertyBlackList: new[] { nameof(existsIdentityResource.Id) }); + configDbContext.IdentityResources.Update(existsIdentityResource); + } + } + else + { + // + logger.LogInformation($"Add IdentityResource: {identityResource.DisplayName}"); + + // + await configDbContext.IdentityResources.AddAsync(identityResource); + } + } + + // + // Save Changes on Config Db Context ... + // await configDbContext.SaveChangesAsync(); + } + + // + logger.LogInformation($"Finish Seeding Identity Resources ..."); + #endregion + + // + #region Api Resources ... + // + logger.LogInformation($"Start Seeding Api Resources ..."); + + // + var dApiResources = dbSeedDescriptor.ApiResources; + var hasApiResources = configDbContext.ApiResources.Any(); + if ((!hasApiResources || + (hasApiResources && dbSeedDescriptor.UpdateExists)) && + dApiResources.HasChild()) + { + // + var apiResourceEntities = dApiResources.Select(da => da.ToEntity()); + + // + foreach (var apiResource in apiResourceEntities) + { + // + logger.LogInformation($"Add/Update: {apiResource.DisplayName}"); + + // + // Retrieve Exists ApiResource ... + var existsApiResource = configDbContext.ApiResources + .Include(r => r.Secrets) + .Include(r => r.Scopes) + .FirstOrDefault(ar => ar.Name == apiResource.Name); + + // + var isExistsResource = !existsApiResource.IsNull(); + var isSameContent = isExistsResource && + existsApiResource.IsSameContent(apiResource, propertyBlackList: new[] { nameof(existsApiResource.Id) }); + + // + if (isExistsResource && + dbSeedDescriptor.UpdateExists) + { + // + if (!isSameContent) + { + // + logger.LogInformation($"Update ApiResource: {existsApiResource.DisplayName}"); + + // + existsApiResource = existsApiResource.UpdateData(apiResource, propertyBlackList: new[] { nameof(existsApiResource.Id) }); + configDbContext.ApiResources.Update(existsApiResource); + } + } + else + { + // + logger.LogInformation($"Add ApiResource: {apiResource.DisplayName}"); + + // + await configDbContext.ApiResources.AddAsync(apiResource); + } + } + + // + // Save Changes on Config Db Context ... + // await configDbContext.SaveChangesAsync(); + } + + // + logger.LogInformation($"Finish Seeding Api Resources ..."); + #endregion + + // + #region Api Scopes ... + // + logger.LogInformation($"Start Seeding Scopes ..."); + + // + var dScopes = dbSeedDescriptor.ApiScopes; + var hasScope = configDbContext.ApiScopes.Any(); + if ((!hasScope || + (hasScope && dbSeedDescriptor.UpdateExists)) && + dScopes.HasChild()) + { + // + var apiScopeEntities = dScopes.Select(dc => dc.ToEntity()); + + // + foreach (var apiScope in apiScopeEntities) + { + // + logger.LogInformation($"Add/Update: {apiScope.DisplayName}"); + + // + // Retrieve Exists Client ... + var existsScope = configDbContext.ApiScopes + .FirstOrDefault(sc => sc.Name == apiScope.Name); + + // + var isExistsScope = !existsScope.IsNull(); + var isSameContent = isExistsScope && + existsScope.IsSameContent(apiScope, propertyBlackList: new[] { nameof(existsScope.Id) }); + + // + if (isExistsScope && + dbSeedDescriptor.UpdateExists) + { + // + if (!isSameContent) + { + // + logger.LogInformation($"Update ApiScope: {existsScope.Name}"); + + // + existsScope = existsScope.UpdateData(apiScope, propertyBlackList: new[] { nameof(existsScope.Id) }); + configDbContext.ApiScopes.Update(existsScope); + } + } + else + { + // + logger.LogInformation($"Add ApiScope: {apiScope.Name}"); + + // + await configDbContext.ApiScopes.AddAsync(apiScope); + } + } + + // + // Save Changes on Config Db Context ... + await configDbContext.SaveChangesAsync(); + } + + // + logger.LogInformation($"Finish Seeding Scopes ..."); + #endregion + + // + #region Users ... + // + logger.LogInformation($"Start Seeding Users ..."); + + // + var configUsers = dbSeedDescriptor.Users; + var hasUser = identityProvider.GetUsersDbSet().Any(); + if ((!hasUser || + (hasUser && dbSeedDescriptor.UpdateExists)) && + configUsers.HasChild()) + { + foreach (var user in configUsers) + { + // + logger.LogInformation($"Add/Update User: {user.UserName}"); + + // + var isUserExists = await identityProvider.IsUserExistsAsync(user.UserName); + if (isUserExists && + dbSeedDescriptor.UpdateExists) + { + // + var existsUser = await identityProvider.GetUserAsync(user.UserName); + var isSame = existsUser.IsSameAs(user); + if (!isSame) + { + // + // Update Entity ... + existsUser = existsUser.UpdateData(user, propertyWhiteList: new[] { + nameof (XIdentityUserDescriptor.FirstName), + nameof (XIdentityUserDescriptor.LastName), + nameof (XIdentityUserDescriptor.UserName), + nameof (XIdentityUserDescriptor.Email), + nameof (XIdentityUserDescriptor.EmailConfirmed), + nameof (XIdentityUserDescriptor.PhoneNumber), + nameof (XIdentityUserDescriptor.PhoneNumberConfirmed), + nameof (XIdentityUserDescriptor.IsEnable), + nameof (XIdentityUserDescriptor.IsBanned), + nameof (XIdentityUserDescriptor.DateOfBirth) + }, propertyValueProviders: new[] { + new KeyValuePair> ( + nameof (XIdentityUserDescriptor.DateOfBirth), + (user) => DateTime.Parse (user.DateOfBirth)) + }); + + // + await identityProvider.UpdateUserAsync(existsUser, false, false); + } + } + else + { + await identityProvider.CreateUserAsync(user); + } + } + } + + // + logger.LogInformation($"Finish Seeding Users ..."); + #endregion + } + + // + logger.LogInformation($"Finish Add/Update XIdentityServer Descriptors ..."); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Extensions\XModelExtensions.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using xCommons.Extensions; +using xDataService.Configuration; +using xIdentityModels.Models; + +namespace xIds.Extensions +{ + public static class XModelExtensions + { + /// + /// Determine a banned time is passed or not + /// + /// + /// + /// + public static bool IsDelayTimePassed( + this XBannedDevice source, + int delaySeconds + ) + { + // + var passedTime = source.BannedOn.AddSeconds(delaySeconds); + + // + var result = DateTime.UtcNow >= passedTime; + + // + return result; + } + + /// + /// Applying Filter to IQueryable + /// /// + /// + public static async Task> ApplyFilterAsync( + this IQueryable source, + string filter + ) where T : class + { + // + // Apply Filter ... + if (!filter.IsNullOrEmpty()) + { + // + var items = source.AsAsyncEnumerable(); + var filteredItems = new List(); + await + foreach (var item in items) + { + // + if (item.GetPropValues() + .ToNormalString() + .Contains(filter + .ToNormalString())) + { + // + filteredItems.Add(item); + } + } + + // + return filteredItems.AsQueryable(); + } + + // + return source; + } + + public static XDataServiceConfiguration ToXDataServiceConfig(this Configurations.XDataServiceConfiguration config) + { + // + var result = new XDataServiceConfiguration(); + + // + if (!config.IsNullOrDefault()) + { + // + result.PagingConfiguration.DefaultPageSize = config.PagingConfiguration.DefaultPageSize; + result.PagingConfiguration.MaxAvailablePageSize = config.PagingConfiguration.MaxAvailablePageSize; + result.PagingConfiguration.MinAvailablePageSize = config.PagingConfiguration.MinAvailablePageSize; + } + + // + return result; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Helpers\XApiKeyHelper.cs + +using System; +using System.Text; +using System.Security.Cryptography; + +namespace xIds.Helpers +{ + /// + /// Api Key Generating Helper Class ... + /// + public static class XApiKeyHelper + { + /// + /// Compute Hash of Specified Content using SHA256 ... + /// + /// + /// + public static string ComputeHash(string plain) + { + // + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(plain)); + var result = BitConverter.ToString(hashBytes) + .Replace("-", "") + .ToLower(); + + // + return result; + } + + /// + /// Generate an Api Key ... + /// + /// + public static (string plainKey, string hash, string prefix) Generate(string keyPrefix) + { + // + // Generate 32 bytes of cryptographically secure random data + var randomBytes = new byte[32]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomBytes); + } + + // + // Format: xapp_{base64url} + var plainKey = $"{keyPrefix}_{Convert.ToBase64String(randomBytes) + .Replace("+", "-").Replace("/", "_").TrimEnd('=')}"; + + // + // Compute Hash ... + var hash = ComputeHash(plainKey); + + // + // Prefix for quick identification (first 12 chars) + var prefix = plainKey.Substring(0, 16) + "..."; + + // + return (plainKey, hash, prefix); + } + + /// + /// Verify Generating Api Key ... + /// + /// + /// + /// + public static bool Verify( + string plainKey, + string storedHash + ) + { + // + var hash = ComputeHash(plainKey); + return hash == storedHash; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Helpers\XIdentityHelper.cs + +using System; +using xIds.Interfaces; +using xCommons.Providers; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityModels.Models; +using xIdentityModels.Extensions; +using Microsoft.IdentityModel.Tokens; +using xIdentityModels.Configurations; +using System.IdentityModel.Tokens.Jwt; + +namespace xIds.Helpers +{ + public class XIdentityHelper : IXIdentityHelper + { + private readonly XIdentityConfiguration configurations; + private readonly JwtSecurityTokenHandler tokenHandler; + private readonly XValidationProvider dataValidationHelper; + + public XIdentityHelper( + XIdentityConfiguration configurations, + XValidationProvider dataValidationHelper + ) + { + // + this.configurations = configurations; + this.tokenHandler = new JwtSecurityTokenHandler(); + this.dataValidationHelper = dataValidationHelper; + } + + // + #region Configuration Parser ... + /// + /// Generate a Key for Signing JWT Tokens + /// + /// + public SymmetricSecurityKey GetTokenSecretKey() + { + // + dataValidationHelper + .NotEmpty(configurations.IdentitySecretKey); + + // + var result = new SymmetricSecurityKey(configurations + .IdentitySecretKey.ToBytes()); + + // + return result; + } + + /// + /// Generate Security Token Signing Key + /// + /// + public SigningCredentials GetTokenSigningCredentials() + { + // + var key = GetTokenSecretKey(); + dataValidationHelper + .NotNull(key); + + // + var result = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + // + return result; + } + + /// + /// Get Token Validation Parameters + /// + /// + public TokenValidationParameters GetTokenValidationParams() + { + // + var key = GetTokenSecretKey(); + var issuer = configurations.IdentityIssuer; + var audience = configurations.IdentityAudience; + + // + dataValidationHelper + .NotNull(key); + dataValidationHelper + .NotEmpty(issuer, audience); + + // + var result = new TokenValidationParameters + { + ValidIssuer = issuer, + ValidAudience = audience, + ValidateLifetime = true, + IssuerSigningKey = key + }; + + // + return result; + } + + /// + /// Get Account Lockout Time Span after Max Fail Reached + /// + /// + public TimeSpan GetLockoutTimeSpan() + { + // + dataValidationHelper + .NotNull(configurations.Policy, configurations.Policy.Lockout); + + // + var timeSpanString = configurations.Policy.Lockout.LockoutTimeSpanProvider; + dataValidationHelper + .NotEmpty(timeSpanString); + + // + var mExpireValue = 0; + var mExpireValueStr = timeSpanString.GetDigits(); + var mExpireUnit = timeSpanString.Replace(mExpireValueStr, ""); + dataValidationHelper + .NotEmpty(mExpireUnit); + dataValidationHelper + .NotEmpty(mExpireValueStr); + + // + var isParsed = int.TryParse(mExpireValueStr, out mExpireValue); + + // + switch (mExpireUnit) + { + case "d": + return TimeSpan.FromDays(mExpireValue); + + case "h": + return TimeSpan.FromHours(mExpireValue); + + case "m": + return TimeSpan.FromMinutes(mExpireValue); + + case "s": + return TimeSpan.FromSeconds(mExpireValue); + + case "ms": + default: + return TimeSpan.FromMilliseconds(mExpireValue); + } + } + + /// + /// Generate Security Token Descriptor for Tokenize another + /// + /// + public SecurityTokenDescriptor GetActionTokenDescriptor() + { + // + dataValidationHelper + .NotNull(configurations); + + // + var issuer = configurations.IdentityIssuer; + var audience = configurations.IdentityAudience; + dataValidationHelper + .NotEmpty(issuer, audience); + + // + var expDate = GetActionTokenExpirationDate(); + var signingCreds = GetTokenSigningCredentials(); + dataValidationHelper + .NotNull(expDate, signingCreds); + + // + var descriptor = new SecurityTokenDescriptor + { + Issuer = issuer, + Audience = audience, + Expires = expDate, + SigningCredentials = signingCreds + }; + + // + return descriptor; + } + + /// + /// Get Action Token Expiration Date + /// + /// + public DateTime GetActionTokenExpirationDate() + { + // + dataValidationHelper + .NotNull(configurations); + + // + var expDateProvider = configurations + .ActionTokenExpirationDateProvider; + dataValidationHelper + .NotEmpty(expDateProvider); + + // + var result = GetExpirationTime(expDateProvider); + dataValidationHelper + .NotNull(result); + + // + return result; + } + #endregion + + // + #region Action Token Handlers ... + public bool ValidateToken(string token) + { + // + // Validate Args ... + dataValidationHelper + .NotEmpty(token); + dataValidationHelper + .NotNull(tokenHandler); + + // + // Prepare Requirements ... + var tokenValidationParams = GetTokenValidationParams(); + dataValidationHelper + .NotNull(tokenValidationParams); + + // + var result = token + .ValidateToken(tokenHandler, tokenValidationParams); + + // + return result; + } + + /// + /// Converts a Token string to SecurityToken instance + /// + /// + /// + public SecurityToken ToSecurityToken(string token) + { + // + dataValidationHelper + .NotEmpty(token); + + // + // Validate token ... + var isTokenValid = ValidateToken(token); + if (!isTokenValid) + { + XException.InvalidToken.Throw(); + } + + // + // Prepare and Validate Requirements ... + var tokenValidationParams = GetTokenValidationParams(); + dataValidationHelper + .NotNull(tokenHandler, tokenValidationParams); + + // + var result = token + .ToSecurityToken(tokenHandler, tokenValidationParams); + dataValidationHelper + .NotNull(result); + + // + return result; + } + + /// + /// Convert an ActionRequest to Token Object + /// + /// + /// + public SecurityToken ToSecurityToken(XActionRequestToken request) + { + // + var tokenDescriptor = GetActionTokenDescriptor(); + + // + dataValidationHelper.NotNull(request, tokenHandler, tokenDescriptor); + + // + var result = request.ToJwtToken(tokenHandler, tokenDescriptor); + + // + return result; + } + + /// + /// Retrieve a Token Exiration Date + /// + /// + /// + public DateTime GetTokenExpirationDate(string token) + { + // + var tokenObj = ToSecurityToken(token); + dataValidationHelper.NotNull(tokenObj); + + // + var result = tokenObj.ValidTo; + + // + return result; + } + + public XActionRequestToken ParseActionRequestToken(string token) + { + // + dataValidationHelper + .NotEmpty(token); + + // + // Validate token ... + var isTokenValid = ValidateToken(token); + if (!isTokenValid) + { + XException.InvalidToken.Throw(); + } + + // + // Prepare and Validate Requirements ... + var tokenValidationParams = GetTokenValidationParams(); + dataValidationHelper + .NotNull(tokenHandler, tokenValidationParams); + + // + var result = token + .ParseActionRequestToken(tokenHandler, tokenValidationParams); + dataValidationHelper + .NotNull(result); + + // + return result; + } + + /// + /// Convert a JWT Security Token Object to + /// it's String Representation + /// + /// + /// + public string ToTokenString(JwtSecurityToken token) + { + // + dataValidationHelper.NotNull(token); + + // + var result = tokenHandler + .WriteToken(token); + + // + return result; + } + + /// + /// Convert an Action Token Object to it's String Representation + /// + /// + /// + public string ToTokenString(SecurityToken token) + { + // + // Validate Args ... + dataValidationHelper + .NotNull(token, tokenHandler); + + // + // Generate and return Result ... + var result = token.ToTokenString(tokenHandler); + dataValidationHelper + .NotEmpty(result); + + // + return result; + } + #endregion + + // + #region Private Actions ... + /// + /// Calculating Date Difference Provider String + /// and Generate Date based on UTC Time + /// + /// + /// + private DateTime GetExpirationTime(string expirationProvider) + { + // + dataValidationHelper + .NotEmpty(expirationProvider); + + // + var mExpireValue = 0; + var mExpireValueStr = expirationProvider.GetDigits(); + var mExpireUnit = expirationProvider.Replace(mExpireValueStr, ""); + dataValidationHelper + .NotEmpty(mExpireUnit); + dataValidationHelper + .NotEmpty(mExpireValueStr); + + // + var isParsed = int.TryParse(mExpireValueStr, out mExpireValue); + + // + switch (mExpireUnit) + { + case "d": + return DateTime.UtcNow.AddDays(mExpireValue); + + case "h": + return DateTime.UtcNow.AddHours(mExpireValue); + + case "m": + return DateTime.UtcNow.AddMinutes(mExpireValue); + + case "s": + return DateTime.UtcNow.AddSeconds(mExpireValue); + + case "ms": + default: + return DateTime.UtcNow.AddMilliseconds(mExpireValue); + } + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Interfaces\IXApplicationProvider.cs + +using System; +using System.Linq; +using xModels.Dtos; +using System.Threading; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Linq.Expressions; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Query; + +namespace xIds.Interfaces +{ + /// + /// a Service For Manage Security Applications and also Api Key(s) ... + /// + public interface IXApplicationProvider + { + // + #region Application ... + /// + /// Add Specified Item ... + /// + /// + /// + /// + /// + Task AddApplicationAsync( + XApplicationDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Update Specified Item ... + /// + /// + /// + /// + /// + /// + Task UpdateApplicationAsync( + int id, + XApplicationDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Remove Specified Item ... + /// + /// + /// + /// + /// + /// + Task RemoveApplicationAsync( + int id, + bool softDelete = true, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve an Item by Providing it's ID ... + /// + /// + /// + /// + /// + /// + /// + Task GetApplicationAsync( + int id, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve all Items ... + /// + /// + /// + /// + /// + /// + /// + Task> GetAllApplicationAsync( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve all Items as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + IAsyncEnumerable GetAllApplicationAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find an Item By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + Task FindOneApplicationAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find a Collection of Items By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + Task> FindManyApplicationAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve Items based on XQuery Pagination Structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task> QueryApplicationAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region ApiKey ... + /// + /// Add Specified Item ... + /// + /// + /// + /// + /// + /// + Task AddApiKeyAsync( + XApiKeyDto item, + string apiKey = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Update Specified Item ... + /// + /// + /// + /// + /// + /// + Task UpdateApiKeyAsync( + Guid id, + XApiKeyDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Remove Specified Item ... + /// + /// + /// + /// + /// + /// + Task RemoveApiKeyAsync( + Guid id, + bool softDelete = true, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve an Item by Providing it's ID ... + /// + /// + /// + /// + /// + /// + /// + Task GetApiKeyAsync( + Guid id, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve all Items ... + /// + /// + /// + /// + /// + /// + /// + Task> GetAllApiKeyAsync( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve all Items as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + IAsyncEnumerable GetAllApiKeyAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find an Item By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + Task FindOneApiKeyAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find a Collection of Items By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + Task> FindManyApiKeyAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve Items based on XQuery Pagination Structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task> QueryApiKeyAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region ApiKeyUsage ... + /// + /// Add Specified Item ... + /// + /// + /// + /// + /// + Task AddApiKeyUsageAsync( + XApiKeyUsageDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Update Specified Item ... + /// + /// + /// + /// + /// + /// + Task UpdateApiKeyUsageAsync( + long id, + XApiKeyUsageDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Remove Specified Item ... + /// + /// + /// + /// + /// + /// + Task RemoveApiKeyUsageAsync( + long id, + bool softDelete = true, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve an Item by Providing it's ID ... + /// + /// + /// + /// + /// + /// + /// + Task GetApiKeyUsageAsync( + long id, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve all Items ... + /// + /// + /// + /// + /// + /// + /// + Task> GetAllApiKeyUsageAsync( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve all Items as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + IAsyncEnumerable GetAllApiKeyUsageAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find an Item By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + Task FindOneApiKeyUsageAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find a Collection of Items By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + Task> FindManyApiKeyUsageAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve Items based on XQuery Pagination Structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task> QueryApiKeyUsageAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region Custom Actions ... + /// + /// Show Specifid Api Key ... + /// + /// + /// + /// + /// + /// + Task ShowApiKey( + Guid apiKeyId, + int applicationId, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Generate a New ApiKey and Add It to DB ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task CreateApiKey( + int applicationId, + TimeSpan? expiration = null, + IEnumerable scopes = null, + IEnumerable allowedIPs = null, + int? rateLimit = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Revoke Specified Api Key ... + /// + /// + /// + /// + /// + Task RevokeApiKey( + Guid apiKeyId, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Renew Specified Api Key ... + /// + /// + /// + /// + /// + /// + Task RotateApiKey( + Guid apiKeyId, + TimeSpan? newExpiration = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve All Api Keys for Specified Application ... + /// + /// + /// + /// + /// + Task> GetApplicationApiKeys( + int applicationId, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Validate Specified Api Key in based on ClientIP ... + /// + /// + /// + /// + /// + /// + /// + Task ValidateApiKey( + string apiKey, + string clientIP, + string requiredScope = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region Enrichers ... + /// + /// Enrich Api Key ... + /// + /// + /// + /// + Task EnrichAsync( + XApiKey source, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich Api Key ... + /// + /// + /// + /// + Task EnrichAsync( + XApiKeyDto source, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich Api Key Usage ... + /// + /// + /// + /// + Task EnrichAsync( + XApiKeyUsage source, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich Application ... + /// + /// + /// + /// + Task EnrichAsync( + XApplication source, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich Api Key Usage ... + /// + /// + /// + /// + Task EnrichAsync( + XApiKeyUsageDto source, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich Application ... + /// + /// + /// + /// + Task EnrichAsync( + XApplicationDto source, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich a Collection of Api Key(s) ... + /// + /// + /// + /// + Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich a Collection of Api Key(s) ... + /// + /// + /// + /// + Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich a Collection of Api Key Usage(s) ... + /// + /// + /// + /// + Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich a Collection of Application(s) ... + /// + /// + /// + /// + Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich a Collection of Api Key Usage(s) ... + /// + /// + /// + /// + Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ); + + /// + /// Enrich a Collection of Application(s) ... + /// + /// + /// + /// + Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ); + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Interfaces\IXIdentityHelper.cs + +using System; +using System.IdentityModel.Tokens.Jwt; +using Microsoft.IdentityModel.Tokens; +using xIdentityModels.Models; + +namespace xIds.Interfaces +{ + public interface IXIdentityHelper + { + // + #region Configuration Parser ... + SymmetricSecurityKey GetTokenSecretKey(); + SigningCredentials GetTokenSigningCredentials(); + TokenValidationParameters GetTokenValidationParams(); + TimeSpan GetLockoutTimeSpan(); + #endregion + + // + #region Action Token Handlers ... + bool ValidateToken(string token); + SecurityToken ToSecurityToken(string token); + SecurityToken ToSecurityToken(XActionRequestToken request); + string ToTokenString(SecurityToken token); + string ToTokenString(JwtSecurityToken token); + DateTime GetTokenExpirationDate(string token); + XActionRequestToken ParseActionRequestToken(string token); + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Interfaces\IXIdentityManager.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using xCommons.Providers; +using xIdentityModels; +using xIdentityModels.Constants; +using xIdentityModels.Descriptors; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +using xIdentityModels.Navigations; +using xIds.DbContext; +using xModels.Dtos; + +namespace xIds.Interfaces +{ + public interface IXIdentityManager + { + XIdentityDbContext DbContext { get; } + UserManager UserManager { get; } + ILogger Logger { get; } + SignInManager SignInManager { get; } + IXSecurityProvider SecurityProvider { get; } + RoleManager RoleManager { get; } + XValidationProvider ValidationProvider { get; } + + // + #region Class Getter (s) Methods ... + IQueryable GetUsersDbSet( + bool containsDetail = false + ); + IQueryable GetUsersFullDbSet(bool enableTracking = false); + #endregion + + // + #region Tools ... + string GetUserSelectByParam( + XActionRequest model, + bool forceNotNull = true, + ICollection excludes = null + ); + + Task ValidateUserExistsAndRetrieve( + string userSelectByParam, + bool containsDetail = false, + bool checkCanLoginPolicies = false, + bool checkIsBanned = true, + bool ignoreDisabledUser = false, + bool forceAdmin = false, + Exception exception = null + ); + #endregion + + // + #region Default Identity Preparation Actions ... + Task CreateIdentityRoles(); + Task CreateUserAsync(XIdentityUserDescriptor user); + #endregion + + // + #region Identity Actions ... + HttpClient GetHttpClient(); + Task RequestScopeAccessToken(string scope); + Task RequestDiscoveryDocument(); + Task Authenticate(XLoginRequest model); + Task Login(XLoginRequest model); + Task RefreshTokens(XTokenResponse model); + #endregion + + // + #region Role Handlers ... + Task IsRoleExistsAsync(string roleName); + Task CreateRoleAsync(string roleName); + Task GetRoleAsync(string roleName); + Task AddUserToRoleAsync(XUser user, string roleName); + Task GetRoleNameAsync(string roleId); + Task> GetRoleNamesAsync( + string userSelectByParam, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ); + Task RemoveFromRoleAsync(XUser user, string roleName); + Task RemoveFromRolesAsync(XUser user, IEnumerable roleNames); + #endregion + + // + #region User Handlers ... + Task CanRegister(string userSelectByParam); + Task IsUserExistsAsync(string userSelectByParam); + Task GetUserAsync( + string userSelectByParam, + bool containsDetail = false); + + Task CreateUserAsync( + XUser user, + string password, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ); + + Task UpdateUserAsync( + XUser user, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ); + + Task> GetUserNamesAsync( + IEnumerable userIds + ); + + Task> GetUserNameIdsAsync( + XUserNameIdRequest model + ); + + Task ToJwtClaims( + XUser user, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ); + + Task CheckPasswordSignInAsync( + XUser user, + string password, + XDevice device, + string language, + bool lockoutOnFailure, + bool isForced = true + ); + #endregion + + // + #region User Profile Handlers ... + Task GetUserProfileAsync( + string userSelectByParam, + string requestedUserSelectByParam, + bool forceCheckRequestedUser = true, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ); + + Task> QueryUsers( + string requestedUserSelectByParam, + XQuery query + ); + + Task> QueryInRoleUsers( + string requestedUserSelectByParam, + string role, + XQuery query, + bool forceRole = false + ); + + Task> GetUserProfilesAsync( + ICollection ids, + string requestedUserSelectByParam, + bool forceCheckRequestedUser = true, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ); + + Task> QueryAvatars( + string userSelectByParam, + XQuery query + ); + + Task ProfileUpdateAsync( + string userSelectByParam, + string requestedUserSelectByParam, + XProfileUpdateRequest request + ); + + Task FullProfileUpdateAsync( + string userSelectByParam, + string requestedUserSelectByParam, + XProfileUpdateRequest request + ); + + Task RequestConfirmMobile( + string lang, + XDevice device, + string userSelectByParam, + string password, + string mobileNumber = null + ); + + Task RequestConfirmEmail( + string lang, + XDevice device, + string userSelectByParam, + string password, + string emailAddress = null + ); + + Task RequestResetPassword( + string lang, + XDevice device, + string userSelectByParam, + string returnUrl + ); + + Task RequestConfirmRegistration( + string lang, + XDevice device, + string userSelectByParam, + string password, + string returnUrl + ); + + Task ChangePassword( + string lang, + XDevice device, + string userSelectByParam, + string password, + string newPassword, + string returnUrl + ); + + Task ResetPassword( + string lang, + XDevice device, + string actionHash, + string newPassword, + string returnUrl + ); + + Task AddAvatar( + string userSelectByParam, + IFormFile file + ); + + Task AddAvatars( + string userSelectByParam, + IFormFileCollection files + ); + + Task SetAvatar( + string userSelectByParam, + int profileImageId + ); + + Task RemoveAvatar( + string userSelectByParam, + ICollection profileImageIds + ); + + Task ConfirmRegistration( + string lang, + XDevice device, + string actionHash, + string returnUrl + ); + + Task ConfirmMobileNumber( + string lang, + XDevice device, + string registrationHash, + string verificationCode + ); + + Task ConfirmEmailAddress( + string lang, + XDevice device, + string registrationHash, + string verificationCode + ); + + Task IsConfirmedEmail(string userSelectByParam); + + Task IsConfirmedMobile(string userSelectByParam); + #endregion + + // + #region Registration Handlers ... + Task InviteUser( + string lang, + XDevice device, + string email, + string handlerUrl + ); + + Task RequestRegistration( + string lang, + XDevice device, + string invitationHash, + string firstName, + string lastName, + DateTime dateOfBirth, + string mobileNumber = null, + string emailAddress = null + ); + + Task AddAcountInfo( + string lang, + XDevice device, + string registrationHash, + string userName, + string password + ); + + Task AttachProfileImage( + string tokenHash, + IFormFile file + ); + + Task FinishRegistration( + string lang, + XDevice device, + string userSelectByParam, + string password, + string returnUrl + ); + #endregion + + // + #region Friendship Handlers ... + // + #region Actions ... + Task Follow( + string userSelectByParam, + string destUserSelectByParam + ); + + Task Cancel( + string userSelectByParam, + string destUserSelectByParam + ); + + Task UnFollowFollower( + string userSelectByParam, + string destUserSelectByParam + ); + + Task UnFollowFollowing( + string userSelectByParam, + string destUserSelectByParam + ); + + Task Block( + string userSelectByParam, + string destUserSelectByParam + ); + + Task UnBlock( + string userSelectByParam, + string destUserSelectByParam + ); + #endregion + + // + #region Request Handlers ... + Task AcceptRequest( + string userSelectByParam, + string destUserSelectByParam + ); + + Task RejectRequest( + string userSelectByParam, + string destUserSelectByParam + ); + #endregion + + // + #region Getters ... + Task IsFollower( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ); + + Task GetFollowerState( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ); + + Task GetFollower( + string userSelectByParam, + string destUserSelectByParam + ); + + Task> GetFollowers(string userSelectByParam); + Task> GetAllFollowers(string userSelectByParam); + Task> GetFollowersList(string userSelectByParam); + Task> QueryFollowers( + XQuery query, + string userSelectByParam + ); + + Task IsFollowing( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ); + + Task GetFollowingState( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ); + + Task GetFollowing( + string userSelectByParam, + string destUserSelectByParam + ); + + Task> GetFollowings(string userSelectByParam); + Task> GetAllFollowings(string userSelectByParam); + Task> GetFollowingList(string userSelectByParam); + Task> QueryFollowings( + XQuery query, + string userSelectByParam + ); + #endregion + + // + Task GetFriendshipInfo( + string userSelectByParam, + string destUserSelectByParam, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ); + #endregion + + // + #region Admin Actions ... + Task> Ban( + string userSelectByParam, + XUserNameIdRequest model + ); + + Task> UnBan( + string userSelectByParam, + XUserNameIdRequest model + ); + + Task IsBanned( + string userSelectByParam, + string destUserSelectByParam + ); + #endregion + + // + #region Open Actions ... + // + #region OpenGet ... + Task OpenGet( + XOpenActionInnerDto model + ); + Task OpenGet( + string token, + string actionRequest + ); + #endregion + + Task GetUserInfo( + XOpenActionRequestDto dto + ); + + Task> GetUserInfos( + XOpenActionRequestDto dto + ); + #endregion + + // + #region Search ... + /// + /// Query Open To Search User Profiles ... + /// + /// requested user's identifier + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XUserProfileDto + Task> QueryOpenToSearchUsers( + string requestedUserSelectByParam, + XQuery query + ); + #endregion + + // + #region Query Service ... + /// + /// Retrieve Users as Query Model for Query Service ... + /// + /// + /// + /// + /// + /// + public Task> QueryUsers( + string requestedUserSelectByParam, + XQuery query, + string role = null, + bool forceRole = false + ); + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Interfaces\IXIdentityMessageProvider.cs + +namespace xIds.Interfaces +{ + public interface IXIdentityMessageProvider + { + string InviteMsg { get; } + string RegistrationApproveMsg { get; } + string RegisteredMsg { get; } + string VerificationCodeMsg { get; } + string ChangePasswordMsg { get; } + string PasswordChangedMsg { get; } + string NewDeviceLoggedInMsg { get; } + + bool IsReady(); + void PrepareMessages(string lang); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Interfaces\IXSecurityProvider.cs + +namespace xIds.Interfaces +{ + public interface IXSecurityProvider + { + string Encrypt(string plainText); + string Decrypt(string cipherText); + string EncryptFromBytes(byte[] textBytes); + string DecryptFromBytes(byte[] cipherBytes); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\20261009161409_InitialConfigurationMigration.cs + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace xIds.Migrations +{ + public partial class InitialConfigurationMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiResources", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Enabled = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "TEXT", maxLength: 200, nullable: true), + Description = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + AllowedAccessTokenSigningAlgorithms = table.Column(type: "TEXT", maxLength: 100, nullable: true), + ShowInDiscoveryDocument = table.Column(type: "INTEGER", nullable: false), + Created = table.Column(type: "TEXT", nullable: false), + Updated = table.Column(type: "TEXT", nullable: true), + LastAccessed = table.Column(type: "TEXT", nullable: true), + NonEditable = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiScopes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Enabled = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "TEXT", maxLength: 200, nullable: true), + Description = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + Required = table.Column(type: "INTEGER", nullable: false), + Emphasize = table.Column(type: "INTEGER", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Clients", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Enabled = table.Column(type: "INTEGER", nullable: false), + ClientId = table.Column(type: "TEXT", maxLength: 200, nullable: false), + ProtocolType = table.Column(type: "TEXT", maxLength: 200, nullable: false), + RequireClientSecret = table.Column(type: "INTEGER", nullable: false), + ClientName = table.Column(type: "TEXT", maxLength: 200, nullable: true), + Description = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + ClientUri = table.Column(type: "TEXT", maxLength: 2000, nullable: true), + LogoUri = table.Column(type: "TEXT", maxLength: 2000, nullable: true), + RequireConsent = table.Column(type: "INTEGER", nullable: false), + AllowRememberConsent = table.Column(type: "INTEGER", nullable: false), + AlwaysIncludeUserClaimsInIdToken = table.Column(type: "INTEGER", nullable: false), + RequirePkce = table.Column(type: "INTEGER", nullable: false), + AllowPlainTextPkce = table.Column(type: "INTEGER", nullable: false), + RequireRequestObject = table.Column(type: "INTEGER", nullable: false), + AllowAccessTokensViaBrowser = table.Column(type: "INTEGER", nullable: false), + FrontChannelLogoutUri = table.Column(type: "TEXT", maxLength: 2000, nullable: true), + FrontChannelLogoutSessionRequired = table.Column(type: "INTEGER", nullable: false), + BackChannelLogoutUri = table.Column(type: "TEXT", maxLength: 2000, nullable: true), + BackChannelLogoutSessionRequired = table.Column(type: "INTEGER", nullable: false), + AllowOfflineAccess = table.Column(type: "INTEGER", nullable: false), + IdentityTokenLifetime = table.Column(type: "INTEGER", nullable: false), + AllowedIdentityTokenSigningAlgorithms = table.Column(type: "TEXT", maxLength: 100, nullable: true), + AccessTokenLifetime = table.Column(type: "INTEGER", nullable: false), + AuthorizationCodeLifetime = table.Column(type: "INTEGER", nullable: false), + ConsentLifetime = table.Column(type: "INTEGER", nullable: true), + AbsoluteRefreshTokenLifetime = table.Column(type: "INTEGER", nullable: false), + SlidingRefreshTokenLifetime = table.Column(type: "INTEGER", nullable: false), + RefreshTokenUsage = table.Column(type: "INTEGER", nullable: false), + UpdateAccessTokenClaimsOnRefresh = table.Column(type: "INTEGER", nullable: false), + RefreshTokenExpiration = table.Column(type: "INTEGER", nullable: false), + AccessTokenType = table.Column(type: "INTEGER", nullable: false), + EnableLocalLogin = table.Column(type: "INTEGER", nullable: false), + IncludeJwtId = table.Column(type: "INTEGER", nullable: false), + AlwaysSendClientClaims = table.Column(type: "INTEGER", nullable: false), + ClientClaimsPrefix = table.Column(type: "TEXT", maxLength: 200, nullable: true), + PairWiseSubjectSalt = table.Column(type: "TEXT", maxLength: 200, nullable: true), + Created = table.Column(type: "TEXT", nullable: false), + Updated = table.Column(type: "TEXT", nullable: true), + LastAccessed = table.Column(type: "TEXT", nullable: true), + UserSsoLifetime = table.Column(type: "INTEGER", nullable: true), + UserCodeType = table.Column(type: "TEXT", maxLength: 100, nullable: true), + DeviceCodeLifetime = table.Column(type: "INTEGER", nullable: false), + NonEditable = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Clients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityResources", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Enabled = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "TEXT", maxLength: 200, nullable: true), + Description = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + Required = table.Column(type: "INTEGER", nullable: false), + Emphasize = table.Column(type: "INTEGER", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "INTEGER", nullable: false), + Created = table.Column(type: "TEXT", nullable: false), + Updated = table.Column(type: "TEXT", nullable: true), + NonEditable = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApiResourceId = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "TEXT", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceClaims_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceProperties", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApiResourceId = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", maxLength: 250, nullable: false), + Value = table.Column(type: "TEXT", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceProperties_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceScopes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Scope = table.Column(type: "TEXT", maxLength: 200, nullable: false), + ApiResourceId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceScopes", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceScopes_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceSecrets", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApiResourceId = table.Column(type: "INTEGER", nullable: false), + Description = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + Value = table.Column(type: "TEXT", maxLength: 4000, nullable: false), + Expiration = table.Column(type: "TEXT", nullable: true), + Type = table.Column(type: "TEXT", maxLength: 250, nullable: false), + Created = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceSecrets_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ScopeId = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "TEXT", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeClaims_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeProperties", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ScopeId = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", maxLength: 250, nullable: false), + Value = table.Column(type: "TEXT", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeProperties_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Type = table.Column(type: "TEXT", maxLength: 250, nullable: false), + Value = table.Column(type: "TEXT", maxLength: 250, nullable: false), + ClientId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientClaims", x => x.Id); + table.ForeignKey( + name: "FK_ClientClaims_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientCorsOrigins", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Origin = table.Column(type: "TEXT", maxLength: 150, nullable: false), + ClientId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); + table.ForeignKey( + name: "FK_ClientCorsOrigins_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientGrantTypes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + GrantType = table.Column(type: "TEXT", maxLength: 250, nullable: false), + ClientId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); + table.ForeignKey( + name: "FK_ClientGrantTypes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientIdPRestrictions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Provider = table.Column(type: "TEXT", maxLength: 200, nullable: false), + ClientId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); + table.ForeignKey( + name: "FK_ClientIdPRestrictions_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientPostLogoutRedirectUris", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PostLogoutRedirectUri = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + ClientId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientProperties", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClientId = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", maxLength: 250, nullable: false), + Value = table.Column(type: "TEXT", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientProperties", x => x.Id); + table.ForeignKey( + name: "FK_ClientProperties_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientRedirectUris", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RedirectUri = table.Column(type: "TEXT", maxLength: 2000, nullable: false), + ClientId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientScopes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Scope = table.Column(type: "TEXT", maxLength: 200, nullable: false), + ClientId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientScopes", x => x.Id); + table.ForeignKey( + name: "FK_ClientScopes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientSecrets", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClientId = table.Column(type: "INTEGER", nullable: false), + Description = table.Column(type: "TEXT", maxLength: 2000, nullable: true), + Value = table.Column(type: "TEXT", maxLength: 4000, nullable: false), + Expiration = table.Column(type: "TEXT", nullable: true), + Type = table.Column(type: "TEXT", maxLength: 250, nullable: false), + Created = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ClientSecrets_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityResourceClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + IdentityResourceId = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "TEXT", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResourceClaims", x => x.Id); + table.ForeignKey( + name: "FK_IdentityResourceClaims_IdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityResourceProperties", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + IdentityResourceId = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", maxLength: 250, nullable: false), + Value = table.Column(type: "TEXT", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResourceProperties", x => x.Id); + table.ForeignKey( + name: "FK_IdentityResourceProperties_IdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceClaims_ApiResourceId", + table: "ApiResourceClaims", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceProperties_ApiResourceId", + table: "ApiResourceProperties", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResources_Name", + table: "ApiResources", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceScopes_ApiResourceId", + table: "ApiResourceScopes", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceSecrets_ApiResourceId", + table: "ApiResourceSecrets", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeClaims_ScopeId", + table: "ApiScopeClaims", + column: "ScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeProperties_ScopeId", + table: "ApiScopeProperties", + column: "ScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopes_Name", + table: "ApiScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientClaims_ClientId", + table: "ClientClaims", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientCorsOrigins_ClientId", + table: "ClientCorsOrigins", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientGrantTypes_ClientId", + table: "ClientGrantTypes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientIdPRestrictions_ClientId", + table: "ClientIdPRestrictions", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientPostLogoutRedirectUris_ClientId", + table: "ClientPostLogoutRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientProperties_ClientId", + table: "ClientProperties", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientRedirectUris_ClientId", + table: "ClientRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_Clients_ClientId", + table: "Clients", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientScopes_ClientId", + table: "ClientScopes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientSecrets_ClientId", + table: "ClientSecrets", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResourceClaims_IdentityResourceId", + table: "IdentityResourceClaims", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResourceProperties_IdentityResourceId", + table: "IdentityResourceProperties", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResources_Name", + table: "IdentityResources", + column: "Name", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiResourceClaims"); + + migrationBuilder.DropTable( + name: "ApiResourceProperties"); + + migrationBuilder.DropTable( + name: "ApiResourceScopes"); + + migrationBuilder.DropTable( + name: "ApiResourceSecrets"); + + migrationBuilder.DropTable( + name: "ApiScopeClaims"); + + migrationBuilder.DropTable( + name: "ApiScopeProperties"); + + migrationBuilder.DropTable( + name: "ClientClaims"); + + migrationBuilder.DropTable( + name: "ClientCorsOrigins"); + + migrationBuilder.DropTable( + name: "ClientGrantTypes"); + + migrationBuilder.DropTable( + name: "ClientIdPRestrictions"); + + migrationBuilder.DropTable( + name: "ClientPostLogoutRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientProperties"); + + migrationBuilder.DropTable( + name: "ClientRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientScopes"); + + migrationBuilder.DropTable( + name: "ClientSecrets"); + + migrationBuilder.DropTable( + name: "IdentityResourceClaims"); + + migrationBuilder.DropTable( + name: "IdentityResourceProperties"); + + migrationBuilder.DropTable( + name: "ApiResources"); + + migrationBuilder.DropTable( + name: "ApiScopes"); + + migrationBuilder.DropTable( + name: "Clients"); + + migrationBuilder.DropTable( + name: "IdentityResources"); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\20261009161409_InitialConfigurationMigration.Designer.cs + +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace xIds.Migrations +{ + [DbContext(typeof(ConfigurationDbContext))] + [Migration("20261009161409_InitialConfigurationMigration")] + partial class InitialConfigurationMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.2"); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowedAccessTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("LastAccessed") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Emphasize") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Required") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ScopeId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ScopeId") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("AccessTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("AccessTokenType") + .HasColumnType("INTEGER"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("INTEGER"); + + b.Property("AllowOfflineAccess") + .HasColumnType("INTEGER"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("INTEGER"); + + b.Property("AllowRememberConsent") + .HasColumnType("INTEGER"); + + b.Property("AllowedIdentityTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("INTEGER"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("INTEGER"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("INTEGER"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("INTEGER"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ConsentLifetime") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DeviceCodeLifetime") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalLogin") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("INTEGER"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("IdentityTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("IncludeJwtId") + .HasColumnType("INTEGER"); + + b.Property("LastAccessed") + .HasColumnType("TEXT"); + + b.Property("LogoUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("INTEGER"); + + b.Property("RefreshTokenUsage") + .HasColumnType("INTEGER"); + + b.Property("RequireClientSecret") + .HasColumnType("INTEGER"); + + b.Property("RequireConsent") + .HasColumnType("INTEGER"); + + b.Property("RequirePkce") + .HasColumnType("INTEGER"); + + b.Property("RequireRequestObject") + .HasColumnType("INTEGER"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.Property("UserCodeType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UserSsoLifetime") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Emphasize") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("Required") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IdentityResourceId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IdentityResourceId") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Properties") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("UserClaims") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("Properties") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("Properties") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Navigation("Properties"); + + b.Navigation("Scopes"); + + b.Navigation("Secrets"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Navigation("AllowedCorsOrigins"); + + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedScopes"); + + b.Navigation("Claims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("PostLogoutRedirectUris"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\ConfigurationDbContextModelSnapshot.cs + +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace xIds.Migrations +{ + [DbContext(typeof(ConfigurationDbContext))] + partial class ConfigurationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.2"); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowedAccessTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("LastAccessed") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiResourceId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Emphasize") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Required") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ScopeId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("ScopeId") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("AccessTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("AccessTokenType") + .HasColumnType("INTEGER"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("INTEGER"); + + b.Property("AllowOfflineAccess") + .HasColumnType("INTEGER"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("INTEGER"); + + b.Property("AllowRememberConsent") + .HasColumnType("INTEGER"); + + b.Property("AllowedIdentityTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("INTEGER"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("INTEGER"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("INTEGER"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("INTEGER"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ConsentLifetime") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DeviceCodeLifetime") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalLogin") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("INTEGER"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("IdentityTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("IncludeJwtId") + .HasColumnType("INTEGER"); + + b.Property("LastAccessed") + .HasColumnType("TEXT"); + + b.Property("LogoUri") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("INTEGER"); + + b.Property("RefreshTokenUsage") + .HasColumnType("INTEGER"); + + b.Property("RequireClientSecret") + .HasColumnType("INTEGER"); + + b.Property("RequireConsent") + .HasColumnType("INTEGER"); + + b.Property("RequirePkce") + .HasColumnType("INTEGER"); + + b.Property("RequireRequestObject") + .HasColumnType("INTEGER"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("INTEGER"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.Property("UserCodeType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UserSsoLifetime") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Emphasize") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("NonEditable") + .HasColumnType("INTEGER"); + + b.Property("Required") + .HasColumnType("INTEGER"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IdentityResourceId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IdentityResourceId") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Properties") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("UserClaims") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("Properties") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("Properties") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Navigation("Properties"); + + b.Navigation("Scopes"); + + b.Navigation("Secrets"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Navigation("AllowedCorsOrigins"); + + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedScopes"); + + b.Navigation("Claims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("PostLogoutRedirectUris"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\PersistedGrantDb\20261009161422_InitialPersistedGrantMigration.cs + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace xIds.Migrations.PersistedGrantDb +{ + public partial class InitialPersistedGrantMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DeviceCodes", + columns: table => new + { + UserCode = table.Column(type: "TEXT", maxLength: 200, nullable: false), + DeviceCode = table.Column(type: "TEXT", maxLength: 200, nullable: false), + SubjectId = table.Column(type: "TEXT", maxLength: 200, nullable: true), + SessionId = table.Column(type: "TEXT", maxLength: 100, nullable: true), + ClientId = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "TEXT", nullable: false), + Expiration = table.Column(type: "TEXT", nullable: false), + Data = table.Column(type: "TEXT", maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); + }); + + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Key = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Type = table.Column(type: "TEXT", maxLength: 50, nullable: false), + SubjectId = table.Column(type: "TEXT", maxLength: 200, nullable: true), + SessionId = table.Column(type: "TEXT", maxLength: 100, nullable: true), + ClientId = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "TEXT", nullable: false), + Expiration = table.Column(type: "TEXT", nullable: true), + ConsumedTime = table.Column(type: "TEXT", nullable: true), + Data = table.Column(type: "TEXT", maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_DeviceCode", + table: "DeviceCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_Expiration", + table: "DeviceCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Expiration", + table: "PersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_SessionId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "SessionId", "Type" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DeviceCodes"); + + migrationBuilder.DropTable( + name: "PersistedGrants"); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\PersistedGrantDb\20261009161422_InitialPersistedGrantMigration.Designer.cs + +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace xIds.Migrations.PersistedGrantDb +{ + [DbContext(typeof(PersistedGrantDbContext))] + [Migration("20261009161422_InitialPersistedGrantMigration")] + partial class InitialPersistedGrantMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.2"); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedTime") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants"); + }); +#pragma warning restore 612, 618 + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\PersistedGrantDb\PersistedGrantDbContextModelSnapshot.cs + +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace xIds.Migrations.PersistedGrantDb +{ + [DbContext(typeof(PersistedGrantDbContext))] + partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.2"); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedTime") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants"); + }); +#pragma warning restore 612, 618 + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\XIdentityDb\20261009161435_InitialIdentityServerDbContext.cs + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace xIds.Migrations.XIdentityDb +{ + public partial class InitialIdentityServerDbContext : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiKeyUsages", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApiKeyId = table.Column(type: "TEXT", nullable: false), + Endpoint = table.Column(type: "TEXT", nullable: true), + HttpMethod = table.Column(type: "TEXT", nullable: true), + ClientIP = table.Column(type: "TEXT", nullable: true), + StatusCode = table.Column(type: "INTEGER", nullable: false), + RequestedOn = table.Column(type: "TEXT", nullable: false), + ResponseTimeMs = table.Column(type: "INTEGER", nullable: false), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiKeyUsages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Applications", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 255, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + OwnerId = table.Column(type: "TEXT", maxLength: 255, nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + CreatedOn = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Applications", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Discriminator = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", maxLength: 50, nullable: true), + LastName = table.Column(type: "TEXT", maxLength: 100, nullable: true), + IsEnable = table.Column(type: "INTEGER", nullable: true), + IsBanned = table.Column(type: "INTEGER", nullable: true), + DateOfBirth = table.Column(type: "TEXT", nullable: true), + CreationDate = table.Column(type: "TEXT", nullable: true), + LastLogin = table.Column(type: "TEXT", nullable: true), + Avatar = table.Column(type: "TEXT", nullable: true), + Gender = table.Column(type: "INTEGER", nullable: true), + Bio = table.Column(type: "TEXT", nullable: true), + CoverImage = table.Column(type: "TEXT", nullable: true), + OpenToSearch = table.Column(type: "INTEGER", nullable: true), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BannedDevices", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + BannedOn = table.Column(type: "TEXT", nullable: false), + DeviceStr = table.Column(type: "TEXT", nullable: true), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BannedDevices", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Tokens", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Token = table.Column(type: "TEXT", nullable: true), + Hash = table.Column(type: "TEXT", nullable: true), + UserSelectByParam = table.Column(type: "TEXT", nullable: true), + DeviceStr = table.Column(type: "TEXT", nullable: true), + Type = table.Column(type: "INTEGER", nullable: false), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tokens", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "VerificationRequests", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + VerificationCode = table.Column(type: "TEXT", nullable: false), + NumberOfTries = table.Column(type: "INTEGER", nullable: false), + LastTryOn = table.Column(type: "TEXT", nullable: false), + RequestStr = table.Column(type: "TEXT", nullable: true), + DeviceStr = table.Column(type: "TEXT", nullable: true), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VerificationRequests", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiKeys", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ApplicationId = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", nullable: false), + KeyHash = table.Column(type: "TEXT", maxLength: 512, nullable: false), + KeyPrefix = table.Column(type: "TEXT", maxLength: 100, nullable: true), + ExpiresAt = table.Column(type: "TEXT", nullable: false), + CreatedOn = table.Column(type: "TEXT", nullable: false), + LastUsedAt = table.Column(type: "TEXT", nullable: true), + RevokedAt = table.Column(type: "TEXT", nullable: true), + RevokedBy = table.Column(type: "TEXT", nullable: true), + AllowedScopes = table.Column(type: "TEXT", maxLength: 1000, nullable: true), + AllowedIPs = table.Column(type: "TEXT", maxLength: 2000, nullable: true), + RateLimitPerMinute = table.Column(type: "INTEGER", nullable: false), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiKeys", x => x.Id); + table.ForeignKey( + name: "FK_ApiKeys_Applications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "Applications", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Avatars", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: true), + Name = table.Column(type: "TEXT", maxLength: 255, nullable: false), + Thumb = table.Column(type: "TEXT", maxLength: 255, nullable: false), + Path = table.Column(type: "TEXT", maxLength: 255, nullable: false), + ThumbPath = table.Column(type: "TEXT", maxLength: 255, nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Avatars", x => x.Id); + table.ForeignKey( + name: "FK_Avatars_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Devices", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Os = table.Column(type: "TEXT", nullable: true), + OsVersion = table.Column(type: "TEXT", nullable: true), + Browser = table.Column(type: "TEXT", nullable: true), + UserAgent = table.Column(type: "TEXT", nullable: true), + DeviceType = table.Column(type: "INTEGER", nullable: false), + UserId = table.Column(type: "TEXT", nullable: true), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Devices", x => x.Id); + table.ForeignKey( + name: "FK_Devices_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Followers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + State = table.Column(type: "INTEGER", nullable: false), + UserId = table.Column(type: "TEXT", nullable: true), + DestId = table.Column(type: "TEXT", nullable: true), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Followers", x => x.Id); + table.ForeignKey( + name: "FK_Followers_AspNetUsers_DestId", + column: x => x.DestId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Followers_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Followings", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + State = table.Column(type: "INTEGER", nullable: false), + UserId = table.Column(type: "TEXT", nullable: true), + DestId = table.Column(type: "TEXT", nullable: true), + Deleted = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Followings", x => x.Id); + table.ForeignKey( + name: "FK_Followings_AspNetUsers_DestId", + column: x => x.DestId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Followings_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_ApplicationId", + table: "ApiKeys", + column: "ApplicationId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_ExpiresAt", + table: "ApiKeys", + column: "ExpiresAt"); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_KeyHash", + table: "ApiKeys", + column: "KeyHash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Applications_Name", + table: "Applications", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Avatars_UserId", + table: "Avatars", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Devices_UserId", + table: "Devices", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Followers_DestId", + table: "Followers", + column: "DestId"); + + migrationBuilder.CreateIndex( + name: "IX_Followers_UserId", + table: "Followers", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Followings_DestId", + table: "Followings", + column: "DestId"); + + migrationBuilder.CreateIndex( + name: "IX_Followings_UserId", + table: "Followings", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiKeys"); + + migrationBuilder.DropTable( + name: "ApiKeyUsages"); + + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "Avatars"); + + migrationBuilder.DropTable( + name: "BannedDevices"); + + migrationBuilder.DropTable( + name: "Devices"); + + migrationBuilder.DropTable( + name: "Followers"); + + migrationBuilder.DropTable( + name: "Followings"); + + migrationBuilder.DropTable( + name: "Tokens"); + + migrationBuilder.DropTable( + name: "VerificationRequests"); + + migrationBuilder.DropTable( + name: "Applications"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\XIdentityDb\20261009161435_InitialIdentityServerDbContext.Designer.cs + +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using xIds.DbContext; + +namespace xIds.Migrations.XIdentityDb +{ + [DbContext(typeof(XIdentityDbContext))] + [Migration("20261009161435_InitialIdentityServerDbContext")] + partial class InitialIdentityServerDbContext + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + + b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AllowedIPs") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("AllowedScopes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("INTEGER"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("KeyHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("KeyPrefix") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT"); + + b.Property("RateLimitPerMinute") + .HasColumnType("INTEGER"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("KeyHash") + .IsUnique(); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApiKeyUsage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiKeyId") + .HasColumnType("TEXT"); + + b.Property("ClientIP") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("HttpMethod") + .HasColumnType("TEXT"); + + b.Property("RequestedOn") + .HasColumnType("TEXT"); + + b.Property("ResponseTimeMs") + .HasColumnType("INTEGER"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ApiKeyUsages"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XBannedDevice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedOn") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceStr") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BannedDevices"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceStr") + .HasColumnType("TEXT"); + + b.Property("Hash") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserSelectByParam") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XVerificationRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceStr") + .HasColumnType("TEXT"); + + b.Property("LastTryOn") + .HasColumnType("TEXT"); + + b.Property("NumberOfTries") + .HasColumnType("INTEGER"); + + b.Property("RequestStr") + .HasColumnType("TEXT"); + + b.Property("VerificationCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("VerificationRequests"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XDevice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Browser") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("Os") + .HasColumnType("TEXT"); + + b.Property("OsVersion") + .HasColumnType("TEXT"); + + b.Property("UserAgent") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollower", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DestId") + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestId"); + + b.HasIndex("UserId"); + + b.ToTable("Followers"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollowing", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DestId") + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestId"); + + b.HasIndex("UserId"); + + b.ToTable("Followings"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XProfileImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Thumb") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ThumbPath") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Avatars"); + }); + + modelBuilder.Entity("xIdentityModels.XUser", b => + { + b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); + + b.Property("Avatar") + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DateOfBirth") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("INTEGER"); + + b.Property("IsBanned") + .HasColumnType("INTEGER"); + + b.Property("IsEnable") + .HasColumnType("INTEGER"); + + b.Property("LastLogin") + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OpenToSearch") + .HasColumnType("INTEGER"); + + b.HasDiscriminator().HasValue("XUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("xIdentityModels.XUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApiKey", b => + { + b.HasOne("xIdentityModels.Models.XApplication", "Application") + .WithMany("ApiKeys") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XDevice", b => + { + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Devices") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollower", b => + { + b.HasOne("xIdentityModels.XUser", "Dest") + .WithMany() + .HasForeignKey("DestId"); + + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Followers") + .HasForeignKey("UserId"); + + b.Navigation("Dest"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollowing", b => + { + b.HasOne("xIdentityModels.XUser", "Dest") + .WithMany() + .HasForeignKey("DestId"); + + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Followings") + .HasForeignKey("UserId"); + + b.Navigation("Dest"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XProfileImage", b => + { + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Avatars") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApplication", b => + { + b.Navigation("ApiKeys"); + }); + + modelBuilder.Entity("xIdentityModels.XUser", b => + { + b.Navigation("Avatars"); + + b.Navigation("Devices"); + + b.Navigation("Followers"); + + b.Navigation("Followings"); + + b.Navigation("Roles"); + }); +#pragma warning restore 612, 618 + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Migrations\XIdentityDb\XIdentityDbContextModelSnapshot.cs + +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using xIds.DbContext; + +namespace xIds.Migrations.XIdentityDb +{ + [DbContext(typeof(XIdentityDbContext))] + partial class XIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + + b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AllowedIPs") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("AllowedScopes") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("INTEGER"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("KeyHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("KeyPrefix") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT"); + + b.Property("RateLimitPerMinute") + .HasColumnType("INTEGER"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("RevokedBy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("KeyHash") + .IsUnique(); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApiKeyUsage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiKeyId") + .HasColumnType("TEXT"); + + b.Property("ClientIP") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.Property("HttpMethod") + .HasColumnType("TEXT"); + + b.Property("RequestedOn") + .HasColumnType("TEXT"); + + b.Property("ResponseTimeMs") + .HasColumnType("INTEGER"); + + b.Property("StatusCode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ApiKeyUsages"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XBannedDevice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedOn") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceStr") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("BannedDevices"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceStr") + .HasColumnType("TEXT"); + + b.Property("Hash") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserSelectByParam") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XVerificationRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceStr") + .HasColumnType("TEXT"); + + b.Property("LastTryOn") + .HasColumnType("TEXT"); + + b.Property("NumberOfTries") + .HasColumnType("INTEGER"); + + b.Property("RequestStr") + .HasColumnType("TEXT"); + + b.Property("VerificationCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("VerificationRequests"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XDevice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Browser") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("Os") + .HasColumnType("TEXT"); + + b.Property("OsVersion") + .HasColumnType("TEXT"); + + b.Property("UserAgent") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollower", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DestId") + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestId"); + + b.HasIndex("UserId"); + + b.ToTable("Followers"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollowing", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("DestId") + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DestId"); + + b.HasIndex("UserId"); + + b.ToTable("Followings"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XProfileImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Thumb") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ThumbPath") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Avatars"); + }); + + modelBuilder.Entity("xIdentityModels.XUser", b => + { + b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); + + b.Property("Avatar") + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DateOfBirth") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("INTEGER"); + + b.Property("IsBanned") + .HasColumnType("INTEGER"); + + b.Property("IsEnable") + .HasColumnType("INTEGER"); + + b.Property("LastLogin") + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OpenToSearch") + .HasColumnType("INTEGER"); + + b.HasDiscriminator().HasValue("XUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("xIdentityModels.XUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApiKey", b => + { + b.HasOne("xIdentityModels.Models.XApplication", "Application") + .WithMany("ApiKeys") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XDevice", b => + { + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Devices") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollower", b => + { + b.HasOne("xIdentityModels.XUser", "Dest") + .WithMany() + .HasForeignKey("DestId"); + + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Followers") + .HasForeignKey("UserId"); + + b.Navigation("Dest"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XFriendshipFollowing", b => + { + b.HasOne("xIdentityModels.XUser", "Dest") + .WithMany() + .HasForeignKey("DestId"); + + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Followings") + .HasForeignKey("UserId"); + + b.Navigation("Dest"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Navigations.XProfileImage", b => + { + b.HasOne("xIdentityModels.XUser", "User") + .WithMany("Avatars") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("xIdentityModels.Models.XApplication", b => + { + b.Navigation("ApiKeys"); + }); + + modelBuilder.Entity("xIdentityModels.XUser", b => + { + b.Navigation("Avatars"); + + b.Navigation("Devices"); + + b.Navigation("Followers"); + + b.Navigation("Followings"); + + b.Navigation("Roles"); + }); +#pragma warning restore 612, 618 + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Models\XDbInfo.cs + +using System; +using xIds.Constants; + +namespace xIds.Models +{ + public class XDbInfo + { + private string _migrationAssembly; + public XDbProviders ProviderType { get; set; } + public string MigrationsAssembly + { + get + { + return _migrationAssembly; + } + + set + { + // + _migrationAssembly = value; + + // + OptionsBuilder = b => + { + b.MigrationsAssembly(value); + }; + } + } + public Action OptionsBuilder { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Models\XDbSeedDescriptor.cs + +using System.Collections.Generic; +using IdentityServer4.Models; +using xIdentityModels.Descriptors; + +namespace xIds.Models +{ + /// + /// the resource Descriptors for Using on DbSeed + /// + public class XDbSeedDescriptor + { + public bool UpdateExists { get; set; } + public ICollection ApiScopes { get; set; } + public ICollection ApiResources { get; set; } + public ICollection IdentityResources { get; set; } + public ICollection Clients { get; set; } + public ICollection Users { get; set; } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XApiKeyAuthenticationHandler.cs + +using System.Linq; +using IdentityModel; +using xIds.Interfaces; +using xCommons.Extensions; +using System.Threading.Tasks; +using System.Security.Claims; +using System.Text.Encodings.Web; +using xIdentityService.Constants; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authentication; + +namespace xIds.Providers +{ + /// + /// an Authentication Handler for ApiKey's based App's ... + /// + public class XApiKeyAuthenticationHandler : AuthenticationHandler + { + private readonly IXApplicationProvider applicationProvider; + + // + public string HeaderName { get; } = XHeader.ApiKey.GetStringValue(); + public string AuthenticationScheme { get; } = XAuthenticationScheme.XApiKey.GetStringValue(); + + public XApiKeyAuthenticationHandler( + UrlEncoder encoder, + ISystemClock clock, + ILoggerFactory logger, + IXApplicationProvider applicationProvider, + IOptionsMonitor options + ) : base(options, logger, encoder, clock) + { + this.applicationProvider = applicationProvider; + } + + /// + /// Handle Authentication ... + /// + /// + protected override async Task HandleAuthenticateAsync() + { + // + var apiKey = + !Request.Headers.ContainsKey(HeaderName) + ? string.Empty + : Request.Headers[HeaderName].ToString(); + if (apiKey.IsNullOrEmpty()) + { + return AuthenticateResult.NoResult(); + } + + // + var clientIP = Request.HttpContext.Connection.RemoteIpAddress?.ToString(); + + // + var validationResult = await applicationProvider.ValidateApiKey( + apiKey: apiKey, + clientIP: clientIP + ); + var isValid = !validationResult.Errors.HasChild(); + if (!isValid) + { + // + var message = validationResult.Errors.ToListString('\n'); + return AuthenticateResult.Fail(message); + } + + // + var claims = new[] + { + new Claim(ClaimTypes.Name, validationResult.OwnerId), + new Claim("application_id", validationResult.ApplicationId.ToString()), + new Claim("auth_type", "apikey"), + new Claim(JwtClaimTypes.Scope, "apikey"), + }; + + // + var scopeClaims = validationResult.Scopes + .Select(s => new Claim(JwtClaimTypes.Scope, s)); + + // + var identity = new ClaimsIdentity( + claims.Union(scopeClaims), + AuthenticationScheme + ); + + // + var principal = new ClaimsPrincipal(identity); + + // + var ticket = new AuthenticationTicket( + principal, + AuthenticationScheme + ); + + // + return AuthenticateResult.Success(ticket); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XApplicationProvider.cs + +using System; +using System.Linq; +using xModels.Dtos; +using xIds.DbContext; +using xIds.Interfaces; +using xCommons.Extensions; +using xIds.Configurations; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Linq.Expressions; +using IdentityServer4.Extensions; +using xIdentityModels.Extensions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.Extensions.Caching.Memory; +using AutoMapper; + +namespace xIds.Providers +{ + /// + /// a Service For Manage Security Applications and also Api Key(s) ... + /// + public partial class XApplicationProvider : IXApplicationProvider + { + private readonly IMemoryCache cache; + private readonly string permittedRole; + private readonly XIdentityDbContext dbContext; + private readonly IXIdentityManager identityManager; + private readonly XDataServiceConfiguration configuration; + private readonly XApiKeyConfiguration apiKeyConfiguration; + private readonly Action mapperConfigurationExp; + + public XApplicationProvider( + IMemoryCache cache, + XIdentityDbContext dbContext, + IXIdentityManager identityManager, + XDataServiceConfiguration configuration, + XApiKeyConfiguration apiKeyConfiguration, + string permittedRole = "admin" + ) + { + // + this.cache = cache; + this.dbContext = dbContext; + this.permittedRole = permittedRole; + this.configuration = configuration; + this.identityManager = identityManager; + this.apiKeyConfiguration = apiKeyConfiguration; + + // + // Configure Mapper Options ... + mapperConfigurationExp = cfg => + { + // + cfg.CreateMap() + .ReverseMap(); + + // + cfg.CreateMap() + .ReverseMap(); + + // + cfg.CreateMap() + .ReverseMap(); + }; + } + + /// + /// Retrieve Owner ... + /// + /// + /// + public async Task GetOwner(string ownerId) + { + // + if (ownerId.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + var user = await identityManager.GetUserAsync(ownerId); + XPersonDto result = user.ToXPerson(); + if (result.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + return result; + } + + /// + /// Check Specifed User Info is Owned for Action or not ... + /// + /// + /// + /// + public bool IsOwned( + XApplication source, + XUserClaimsInfoDto userInfo = null + ) + { + // + var result = + !source.IsNullOrDefault() && + !userInfo.IsNullOrDefault() && + (userInfo.UserId == source.OwnerId || + userInfo.Roles + .Any(r => + r.ToNormalString() == permittedRole.ToNormalString())); + + // + return result; + } + + /// + /// Check Specifed User Info is Owned for Action or not ... + /// + /// + /// + /// + public bool IsOwned( + XApplicationDto source, + XUserClaimsInfoDto userInfo = null + ) + { + // + var result = + !source.IsNullOrDefault() && + !userInfo.IsNullOrDefault() && + (userInfo.UserId == source.OwnerId || + userInfo.Roles + .Any(r => + r.ToNormalString() == permittedRole.ToNormalString())); + + // + return result; + } + + /// + /// Normalize an XQuery based on Data Service Configurations ... + /// + /// + /// + /// + public XQuery NormalizeQuery( + XQuery source, + XDataServiceConfiguration configuration // + ) + { + // + if (source.IsNull()) + { + source = new XQuery(); + } + + // + // Page Size Normalization ... + if (source.PageSize < configuration.PagingConfiguration.MinAvailablePageSize) + { + source.PageSize = configuration.PagingConfiguration.MinAvailablePageSize; + } + if (source.PageSize > configuration.PagingConfiguration.MaxAvailablePageSize) + { + source.PageSize = configuration.PagingConfiguration.MaxAvailablePageSize; + } + + // + return source; + } + + /// + /// Prepare a Queryable of Items ... + /// + /// + /// + /// + /// + public IQueryable GetApplicationsQeryable( + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null + ) + { + // + // Global Result ... + var result = dbContext.Applications.AsQueryable(); + + // + // Handle Filtering ... + if (!predicate.IsNull()) + { + result = result.Where(predicate); + } + + // + // Handle Including ... + if (!includeBuilder.IsNull()) + { + result = includeBuilder(result); + } + + // + // Handle Ordering ... + if (!orderBuilder.IsNull()) + { + result = orderBuilder(result); + } + + // + // Return result ... + return result; + } + + /// + /// Prepare a Queryable of Items ... + /// + /// + /// + /// + /// + public IQueryable GetApiKeysQeryable( + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null + ) + { + // + // Global Result ... + var result = dbContext.ApiKeys.AsQueryable(); + + // + // Handle Filtering ... + if (!predicate.IsNull()) + { + result = result.Where(predicate); + } + + // + // Handle Including ... + if (!includeBuilder.IsNull()) + { + result = includeBuilder(result); + } + + // + // Handle Ordering ... + if (!orderBuilder.IsNull()) + { + result = orderBuilder(result); + } + + // + // Return result ... + return result; + } + + /// + /// Prepare a Queryable of Items ... + /// + /// + /// + /// + /// + public IQueryable GetApiKeyUsagesQeryable( + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null + ) + { + // + // Global Result ... + var result = dbContext.ApiKeyUsages.AsQueryable(); + + // + // Handle Filtering ... + if (!predicate.IsNull()) + { + result = result.Where(predicate); + } + + // + // Handle Including ... + if (!includeBuilder.IsNull()) + { + result = includeBuilder(result); + } + + // + // Handle Ordering ... + if (!orderBuilder.IsNull()) + { + result = orderBuilder(result); + } + + // + // Return result ... + return result; + } + + /// + /// Handle Rate Limit Checking ... + /// + /// + /// + /// + private bool CheckRateLimit( + Guid apiKeyId, + int limitPerMinute + ) + { + // + var cacheKey = $"ratelimit:{apiKeyId}"; + var currentCount = cache.GetOrCreate( + cacheKey, + entry => + { + // + entry.SlidingExpiration = TimeSpan.FromMinutes(1); + return 0; + } + ); + + // + if (currentCount >= limitPerMinute) + { + return false; + } + + // + cache.Set( + cacheKey, + currentCount + 1, + TimeSpan.FromMinutes(1) + ); + + // + return true; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager.cs + +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 UserManager { get; } + public IXIdentityHelper IdentityHelper { get; } + public ILogger Logger { get; } + public IXMessageProvider MessageProvider { get; } + public IXStorageProvider StorageProvider { get; } + public SignInManager SignInManager { get; } + public IXSecurityProvider SecurityProvider { get; } + public XIdentityConfiguration Configuration { get; } + public RoleManager RoleManager { get; } + public XValidationProvider ValidationProvider { get; } + public XDataServiceConfiguration DataConfiguration { get; } + public IXIdentityMessageProvider IdentityMessageProvider { get; } + public XIdentityResourceConfiguration IdentityResourceConfiguration { get; } + + public XIdentityManager( + XIdentityDbContext dbContext, + UserManager userManager, + IXIdentityHelper identityHelper, + ILogger logger, + IXMessageProvider messageProvider, + IXStorageProvider storageProvider, + SignInManager signInManager, + IXSecurityProvider securityProvider, + XIdentityConfiguration configuration, + RoleManager 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 ... + /// + /// Retrieve User DB Set + /// + /// specifies returned object contains all Navigation Properties or not, default is false + /// + public IQueryable GetUsersDbSet( + bool containDetails = false + ) + { + // + var dbSet = containDetails ? GetUsersFullDbSet() : + UserManager.Users; + return dbSet; + } + + /// + /// Retrieve User Db Set Full Navigation Properties + /// + /// + /// + public IQueryable 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 ... + /// + /// Get User SelectBy Param + /// + /// + /// + /// + /// + public string GetUserSelectByParam( + XActionRequest model, + bool forceNotNull = true, + ICollection 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityMessageProvider.cs + +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 logger; + private readonly XIdentityConfiguration identityConfiguration; + + public XIdentityMessageProvider( + ILogger logger, + XIdentityConfiguration identityConfiguration + ) + { + this.logger = logger; + this.identityConfiguration = identityConfiguration; + } + + /// + /// Determine All things is Ready or Not + /// + /// + public bool IsReady() + { + return !InviteMsg.IsNullOrEmpty() && + !RegistrationApproveMsg.IsNullOrEmpty() && + !RegisteredMsg.IsNullOrEmpty() && + !VerificationCodeMsg.IsNullOrEmpty() && + !ChangePasswordMsg.IsNullOrEmpty() && + !PasswordChangedMsg.IsNullOrEmpty() && + !NewDeviceLoggedInMsg.IsNullOrEmpty(); + } + + /// + /// Retrieve a Translated Value Based on Given Lang + /// + /// + /// + /// + 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; + } + + /// + /// Prepare Helper Class and Fill Messages + /// with Specific Language + /// + /// + 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(); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityProfileService.cs + +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 userClaimsPrincipalFacotry; + + public XIdentityProfileService( + IXIdentityManager identityManager, + IUserClaimsPrincipalFactory 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; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XSecurityProvider.cs + +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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XApplicationProvider\XApplicationProvider+ApiKey.cs + +using System; +using System.Linq; +using xModels.Dtos; +using System.Threading; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Linq.Expressions; +using xDataService.Extensions; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using Microsoft.EntityFrameworkCore; +using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace xIds.Providers +{ + public partial class XApplicationProvider + { + // + #region ApiKey ... + /// + /// Add Specified Item ... + /// + /// + /// + /// + /// + /// + public async Task AddApiKeyAsync( + XApiKeyDto item, + string apiKey = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (!item.IsValidForAdd()) + { + XException.InvalidArgs.Throw(); + } + + // + // Permission ... + if (userInfo.IsNullOrDefault()) + { + XException.NotAllowed.Throw(); + } + + // + // Check Application Exists ... + var application = await GetApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + id: item.ApplicationId, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Validate Owner ... + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + // Check COunt of API Keys ... + var appApiKeysCount = await dbContext.ApiKeys.CountAsync( + cancellationToken: cancellationToken, + predicate: x => x.ApplicationId == item.ApplicationId + ); + if (appApiKeysCount >= apiKeyConfiguration.MaxKeysPerApplication) + { + XException.NotAllowed.Throw(); + } + + // + item.CreatedOn = DateTime.UtcNow; + + // + var entity = item.MapConvert(mapperConfigurationExp); + if (!apiKey.IsNullOrEmpty()) + { + entity.Key = apiKey; + } + + var entry = await dbContext.ApiKeys.AddAsync( + entity: entity, + cancellationToken: cancellationToken + ); + var changes = await dbContext.SaveChangesAsync(cancellationToken); + + // + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNullOrDefault() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Update Specified Item ... + /// + /// + /// + /// + /// + /// + public async Task UpdateApiKeyAsync( + Guid id, + XApiKeyDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + id != item.Id || + !id.IsDefaultGuid() || + !item.IsValidForAdd() || + item.IsNullOrDefault() || + userInfo.IsNullOrDefault() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Exists Entity ... + var exists = await dbContext.ApiKeys + .FirstOrDefaultAsync(x => x.Id == id && !x.Deleted); + if (exists.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Permission Checking ... + var application = await GetApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + id: item.ApplicationId, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + // Update Data on Exists Entity ... + exists = exists.UpdateData( + updateWith: item, + propertyWhiteList: [], + propertyBlackList: [ + nameof(XApiKey.Id), + nameof(XApiKey.KeyHash), + nameof(XApiKey.KeyPrefix), + nameof(XApiKey.CreatedOn), + nameof(XApiKey.Application), + nameof(XApiKey.ApplicationId), + ] + ); + + // + var entry = dbContext.ApiKeys.Attach(exists); + entry.State = EntityState.Modified; + + // + var changes = await dbContext.SaveChangesAsync(cancellationToken); + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNullOrDefault() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Remove Specified Item ... + /// + /// + /// + /// + /// + /// + public async Task RemoveApiKeyAsync( + Guid id, + bool softDelete = true, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + id.IsDefaultGuid() || + userInfo.IsNullOrDefault() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Exists Entity ... + var exists = await dbContext.ApiKeys + .FirstOrDefaultAsync(x => x.Id == id && !x.Deleted); + if (exists.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Permission Checking ... + var application = await GetApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + id: exists.ApplicationId, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + EntityEntry entry = null; + if (softDelete) + { + // + exists.Deleted = true; + entry = dbContext.ApiKeys.Update(exists); + } + else + { + entry = dbContext.ApiKeys.Remove(exists); + } + + // + var changes = await dbContext.SaveChangesAsync(cancellationToken); + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNull() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve an Item by Providing it's ID ... + /// + /// + /// + /// + /// + /// + /// + public async Task GetApiKeyAsync( + Guid id, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (id.IsDefaultGuid() || + userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + // Prepare Condition ... + Expression> predicate = x => + !ignoreSoftDeleteds || !x.Deleted; + + // + // Retrieve Queryable ... + var queryable = GetApiKeysQeryable( + orderBuilder: null, + predicate: predicate, + includeBuilder: includeBuilder + ); + + // + var entity = await queryable.FirstOrDefaultAsync( + predicate: x => x.Id == id, + cancellationToken: cancellationToken + ); + if (entity.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Permission Checking ... + var application = await GetApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + id: entity.ApplicationId, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + var result = await EnrichAsync( + source: entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve all Items ... + /// + /// + /// + /// + /// + /// + /// + public async Task> GetAllApiKeyAsync( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + var applications = await GetAllApplicationAsync( + userInfo: userInfo, + orderBuilder: null, + includeBuilder: null, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + applications = applications.Where(a => IsOwned(a, userInfo)); + var appIds = applications + .Select(a => a.Id) + .ToList(); + + // + Expression> predicate = x => + appIds.Contains(x.ApplicationId) && + !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeysQeryable( + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder + ); + + // + var items = await queryable.ToListAsync(cancellationToken); + var result = await EnrichRangeAsync( + sources: items, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve all Items as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + public async IAsyncEnumerable GetAllApiKeyAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + [EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + var applications = await GetAllApplicationAsync( + userInfo: userInfo, + orderBuilder: null, + includeBuilder: null, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + applications = applications.Where(a => IsOwned(a, userInfo)); + var appIds = applications + .Select(a => a.Id) + .ToList(); + + // + Expression> predicate = x => + appIds.Contains(x.ApplicationId) && + !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeysQeryable( + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder + ); + + // + var enumerator = queryable.AsAsyncEnumerable(); + await foreach (var item in enumerator) + { + // + // Handle Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + var dto = await EnrichAsync( + source: item, + cancellationToken: cancellationToken + ); + + yield return dto; + } + } + + /// + /// Find an Item By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + public async Task FindOneApiKeyAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + Expression> _predicate = x => !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeysQeryable( + orderBuilder: null, + predicate: _predicate, + includeBuilder: includeBuilder + ); + + // + var entity = (await queryable + .ToListAsync(cancellationToken)) + .FirstOrDefault(x => predicate.Compile()(x)); + + // + // Permission Checking ... + var application = await GetApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + id: entity.ApplicationId, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + var result = await EnrichAsync( + source: entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Find a Collection of Items By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> FindManyApiKeyAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + Expression> _predicate = x => !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeysQeryable( + orderBuilder: null, + predicate: _predicate, + includeBuilder: includeBuilder + ); + + // + var entities = (await queryable + .ToListAsync(cancellationToken)) + .Where(x => predicate.Compile()(x)) + .ToList(); + + // + var appIds = entities + .Select(k => k.ApplicationId) + .ToList(); + + // + foreach (var appId in appIds) + { + // + // Permission Checking ... + var application = await GetApplicationAsync( + id: appId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if ( + application.IsNullOrDefault() || + !IsOwned(application, userInfo)) + { + entities = [.. entities.Where(e => e.ApplicationId != appId)]; + } + } + + // + var result = await EnrichRangeAsync( + sources: entities, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve Items based on XQuery Pagination Structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> QueryApiKeyAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + // Normalize Query ... + query = NormalizeQuery(query, configuration); + + // + Expression> _predicate = x => !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeysQeryable( + predicate: _predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder + ); + + // + var items = (await queryable + .ToListAsync(cancellationToken)) + .Where(x => predicate.Compile()(x)); + + // + var appIds = items + .Select(k => k.ApplicationId) + .ToList(); + + // + foreach (var appId in appIds) + { + // + // Permission Checking ... + var application = await GetApplicationAsync( + id: appId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if ( + application.IsNullOrDefault() || + !IsOwned(application, userInfo)) + { + items = [.. items.Where(e => e.ApplicationId != appId)]; + } + } + + // + var totalItemsCount = items.Count(); + + // + // Apply Filter ... + if (!query.Filter.IsNullOrEmpty()) + { + // + items = items + .AsEnumerable() + .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 + ); + } + + // + var resultItems = await EnrichRangeAsync( + sources: items, + cancellationToken: cancellationToken + ); + + // + // Prepare Result ... + var result = new XQueryResult + { + Page = query.Page, + Items = [.. resultItems], + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XApplicationProvider\XApplicationProvider+ApiKeyUsage.cs + +using System; +using System.Linq; +using xModels.Dtos; +using System.Threading; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Linq.Expressions; +using xDataService.Extensions; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using Microsoft.EntityFrameworkCore; +using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace xIds.Providers +{ + public partial class XApplicationProvider + { + // + #region ApiKeyUsage ... + /// + /// Add Specified Item ... + /// + /// + /// + /// + /// + public async Task AddApiKeyUsageAsync( + XApiKeyUsageDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (!item.IsValidForAdd()) + { + XException.InvalidArgs.Throw(); + } + + // + // Check ApiKey Exists ... + var apiKey = await dbContext.ApiKeys + .FirstOrDefaultAsync(x => x.Id == item.ApiKeyId); + if (apiKey.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Check apiKey Activation ... + if (!apiKey.IsActive()) + { + XException.NotAllowed.Throw(); + } + + // + var entity = item.MapConvert(); + var entry = await dbContext.ApiKeyUsages.AddAsync( + entity: entity, + cancellationToken: cancellationToken + ); + var changes = await dbContext.SaveChangesAsync(cancellationToken); + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNullOrDefault() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Update Specified Item ... + /// + /// + /// + /// + /// + /// + public async Task UpdateApiKeyUsageAsync( + long id, + XApiKeyUsageDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + id <= 0 || + id != item.Id || + !item.IsValidForAdd() || + item.IsNullOrDefault() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Item Exists ... + var exists = dbContext.ApiKeyUsages.FirstOrDefault(x => x.Id == id); + if (exists.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Update Data on Exists Entity ... + exists = exists.UpdateData( + updateWith: item, + propertyWhiteList: [], + propertyBlackList: [ + nameof(XApiKeyUsage.Id), + nameof(XApiKeyUsage.ApiKeyId), + nameof(XApiKeyUsage.ResponseTimeMs), + ] + ); + + // + var entry = dbContext.ApiKeyUsages.Attach(exists); + entry.State = EntityState.Modified; + + // + var changes = await dbContext.SaveChangesAsync(cancellationToken); + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNullOrDefault() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Remove Specified Item ... + /// + /// + /// + /// + /// + /// + public async Task RemoveApiKeyUsageAsync( + long id, + bool softDelete = true, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (id <= 0) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Exists Entity ... + var exists = await dbContext.ApiKeyUsages + .FirstOrDefaultAsync(x => x.Id == id && !x.Deleted); + if (exists.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + EntityEntry entry = null; + if (softDelete) + { + // + exists.Deleted = true; + entry = dbContext.ApiKeyUsages.Update(exists); + } + else + { + entry = dbContext.ApiKeyUsages.Remove(exists); + } + + // + var changes = await dbContext.SaveChangesAsync(cancellationToken); + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNull() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve an Item by Providing it's ID ... + /// + /// + /// + /// + /// + /// + /// + public async Task GetApiKeyUsageAsync( + long id, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (id <= 0) + { + XException.InvalidArgs.Throw(); + } + + // + // Prepare Condition ... + Expression> predicate = x => + !ignoreSoftDeleteds || !x.Deleted; + + // + // Retrieve Queryable ... + var queryable = GetApiKeyUsagesQeryable( + orderBuilder: null, + predicate: predicate, + includeBuilder: includeBuilder + ); + + // + var entity = await queryable.FirstOrDefaultAsync( + predicate: x => x.Id == id, + cancellationToken: cancellationToken + ); + if (entity.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await EnrichAsync( + source: entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve all Items ... + /// + /// + /// + /// + /// + /// + /// + public async Task> GetAllApiKeyUsageAsync( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + Expression> predicate = x => + !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeyUsagesQeryable( + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder + ); + + // + var items = await queryable.ToListAsync(cancellationToken); + var result = await EnrichRangeAsync( + sources: items, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve all Items as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + public async IAsyncEnumerable GetAllApiKeyUsageAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + [EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + Expression> predicate = x => + !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeyUsagesQeryable( + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder + ); + + // + var enumerator = queryable.AsAsyncEnumerable(); + await foreach (var item in enumerator) + { + // + // Handle Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + var dto = await EnrichAsync( + source: item, + cancellationToken: cancellationToken + ); + + yield return dto; + } + } + + /// + /// Find an Item By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + public async Task FindOneApiKeyUsageAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + Expression> _predicate = x => !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeyUsagesQeryable( + orderBuilder: null, + predicate: _predicate, + includeBuilder: includeBuilder + ); + + // + var entity = (await queryable + .ToListAsync(cancellationToken)) + .FirstOrDefault(x => predicate.Compile()(x)); + + // + var result = await EnrichAsync( + source: entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Find a Collection of Items By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> FindManyApiKeyUsageAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + Expression> _predicate = x => !ignoreSoftDeleteds || !x.Deleted; + var queryable = GetApiKeyUsagesQeryable( + orderBuilder: null, + predicate: _predicate, + includeBuilder: includeBuilder + ); + + // + var entities = (await queryable + .ToListAsync(cancellationToken)) + .Where(x => predicate.Compile()(x)) + .ToList(); + + // + var result = await EnrichRangeAsync( + sources: entities, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve Items based on XQuery Pagination Structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> QueryApiKeyUsageAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Normalize Query ... + query = NormalizeQuery(query, configuration); + + // + var items = await GetAllApiKeyUsageAsync( + userInfo: userInfo, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + 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 + ); + } + + // + // Prepare Result ... + var result = new XQueryResult + { + Page = query.Page, + Items = [.. items], + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XApplicationProvider\XApplicationProvider+Application.cs + +using System; +using System.Linq; +using xModels.Dtos; +using System.Threading; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Linq.Expressions; +using xDataService.Extensions; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using xIdentityHelper.Extensions; +using Microsoft.EntityFrameworkCore; +using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace xIds.Providers +{ + public partial class XApplicationProvider + { + // + #region Application ... + /// + /// Add Specified Item ... + /// + /// + /// + /// + /// + public async Task AddApplicationAsync( + XApplicationDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (!item.IsValidForAdd()) + { + XException.InvalidArgs.Throw(); + } + + // + // Permission ... + if (userInfo.IsNullOrDefault()) + { + XException.NotAllowed.Throw(); + } + + // + item.OwnerId = userInfo.UserId; + item.CreatedOn = DateTime.UtcNow; + + // + var entity = item.MapConvert(); + if (entity.Id.IsValidIntId()) + { + entity.Id = -1; + } + + // + var entry = await dbContext.Applications.AddAsync( + entity: entity, + cancellationToken: cancellationToken + ); + var changes = await dbContext.SaveChangesAsync(cancellationToken); + + // + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNullOrDefault() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Update Specified Item ... + /// + /// + /// + /// + /// + /// + public async Task UpdateApplicationAsync( + int id, + XApplicationDto item, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + id != item.Id || + !id.IsValidIntId() || + item.IsNullOrDefault() || + userInfo.IsNullOrDefault() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Exists Entity ... + var exists = await dbContext.Applications + .FirstOrDefaultAsync(x => x.Id == id && !x.Deleted); + if (exists.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Permission Checking ... + if (!IsOwned(exists, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + item.UpdatedAt = DateTime.UtcNow; + + // + // Update Data on Exists Entity ... + exists = exists.UpdateData( + updateWith: item, + propertyWhiteList: [], + propertyBlackList: [ + nameof(XApiKey.Id), + nameof(XApplication.ApiKeys), + nameof(XApplication.CreatedOn), + ] + ); + + // + var entry = dbContext.Applications.Attach(exists); + entry.State = EntityState.Modified; + + // + var changes = await dbContext.SaveChangesAsync(cancellationToken); + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNullOrDefault() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Remove Specified Item ... + /// + /// + /// + /// + /// + /// + public async Task RemoveApplicationAsync( + int id, + bool softDelete = true, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + !id.IsValidIntId() || + userInfo.IsNullOrDefault() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Exists Entity ... + var exists = await dbContext.Applications + .FirstOrDefaultAsync(x => x.Id == id && !x.Deleted); + if (exists.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Permission Checking ... + if (!IsOwned(exists, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + EntityEntry entry = null; + if (softDelete) + { + // + exists.Deleted = true; + entry = dbContext.Applications.Update(exists); + } + else + { + entry = dbContext.Applications.Remove(exists); + } + + // + var changes = await dbContext.SaveChangesAsync(cancellationToken); + if ( + changes <= 0 || + entry.IsNull() || + entry.Entity.IsNull() + ) + { + XException.ActionFailed.Throw(); + } + + // + var result = await EnrichAsync( + source: entry.Entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve an Item by Providing it's ID ... + /// + /// + /// + /// + /// + /// + /// + public async Task GetApplicationAsync( + int id, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (!id.IsValidIntId() || + userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + // Prepare Condition ... + Expression> predicate = x => + x.OwnerId != userInfo.UserId || + !ignoreSoftDeleteds || !x.Deleted; + if (userInfo.IsAdmin()) + { + // + predicate = x => + !ignoreSoftDeleteds || !x.Deleted; + } + + // + // Retrieve Queryable ... + var queryable = GetApplicationsQeryable( + orderBuilder: null, + predicate: predicate, + includeBuilder: includeBuilder + ); + + // + var entity = await queryable.FirstOrDefaultAsync( + predicate: x => x.Id == id, + cancellationToken: cancellationToken + ); + + // + var result = await EnrichAsync( + source: entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve all Items ... + /// + /// + /// + /// + /// + /// + /// + public async Task> GetAllApplicationAsync( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + // Prepare Condition ... + Expression> predicate = x => + x.OwnerId == userInfo.UserId && + (!ignoreSoftDeleteds || !x.Deleted); + if (userInfo.IsAdmin()) + { + // + predicate = x => + !ignoreSoftDeleteds || !x.Deleted; + } + + // + // Retrieve Queryable ... + var queryable = GetApplicationsQeryable( + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder + ); + + // + var items = await queryable.ToListAsync(cancellationToken); + var result = await EnrichRangeAsync( + sources: items, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Retrieve all Items as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + public async IAsyncEnumerable GetAllApplicationAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + [EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + // Prepare Condition ... + Expression> predicate = x => + x.OwnerId == userInfo.UserId && + (!ignoreSoftDeleteds || !x.Deleted); + if (userInfo.IsAdmin()) + { + // + predicate = x => + !ignoreSoftDeleteds || !x.Deleted; + } + + // + // Retrieve Queryable ... + var queryable = GetApplicationsQeryable( + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder + ); + + // + var enumerator = queryable.AsAsyncEnumerable(); + await foreach (var item in enumerator) + { + // + // Handle Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + var dto = await EnrichAsync( + source: item, + cancellationToken: cancellationToken + ); + + yield return dto; + } + } + + /// + /// Find an Item By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + public async Task FindOneApplicationAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + Expression> _predicate = x => !ignoreSoftDeleteds || !x.Deleted; + if (!userInfo.IsAdmin()) + { + _predicate = x => + x.OwnerId == userInfo.UserId && + (!ignoreSoftDeleteds || !x.Deleted); + } + + // + var queryable = GetApplicationsQeryable( + orderBuilder: null, + predicate: _predicate, + includeBuilder: includeBuilder + ); + + // + var entity = (await queryable + .ToListAsync(cancellationToken)) + .FirstOrDefault(x => predicate.Compile()(x)); + + // + var result = await EnrichAsync( + source: entity, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Find a Collection of Items By Providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> FindManyApplicationAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + Expression> _predicate = x => !ignoreSoftDeleteds || !x.Deleted; + if (!userInfo.IsAdmin()) + { + _predicate = x => + x.OwnerId == userInfo.UserId && + (!ignoreSoftDeleteds || !x.Deleted); + } + + // + var queryable = GetApplicationsQeryable( + orderBuilder: null, + predicate: _predicate, + includeBuilder: includeBuilder + ); + + // + var entities = (await queryable + .ToListAsync(cancellationToken)) + .Where(x => predicate.Compile()(x)); + + // + var result = await EnrichRangeAsync( + sources: entities, + cancellationToken: cancellationToken + ); + return result; + } + + /// + /// Retrieve Items based on XQuery Pagination Structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> QueryApplicationAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + XUserClaimsInfoDto userInfo = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Normalize Query ... + query = NormalizeQuery(query, configuration); + + // + var items = await GetAllApplicationAsync( + userInfo: userInfo, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + 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 + ); + } + + // + // Prepare Result ... + var result = new XQueryResult + { + Page = query.Page, + Items = [.. items], + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XApplicationProvider\XApplicationProvider+Custom.cs + +using System; +using System.Linq; +using xIds.Helpers; +using System.Threading; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Collections.Generic; +using xIdentityModels.Extensions; +using Microsoft.EntityFrameworkCore; + +namespace xIds.Providers +{ + public partial class XApplicationProvider + { + // + #region Custom Actions ... + /// + /// Show Specifid Api Key ... + /// + /// + /// + /// + /// + /// + public async Task ShowApiKey( + Guid apiKeyId, + int applicationId, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + userInfo.IsNullOrDefault() || + !applicationId.IsValidIntId() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Application Exists ... + var application = await GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Validate Owner ... + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + // Retrieve ApiKey Entity ... + // Since ApiKey is Only Exists on ApiKey Entity ... + var entity = await dbContext.ApiKeys + .FirstOrDefaultAsync(x => x.Id == apiKeyId); + if (entity.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + return entity.Key; + } + + /// + /// Generate a New ApiKey and Add It to DB ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task CreateApiKey( + int applicationId, + TimeSpan? expiration = null, + IEnumerable scopes = null, + IEnumerable allowedIPs = null, + int? rateLimit = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + userInfo.IsNullOrDefault() || + !applicationId.IsValidIntId() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Application Exists ... + var application = await GetApplicationAsync( + id: applicationId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Validate Owner ... + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + // Normalizing ... + if (!rateLimit.HasValue) + { + rateLimit = apiKeyConfiguration.DefaultRateLimit; + } + if (!expiration.HasValue) + { + expiration = TimeSpan.FromMinutes(apiKeyConfiguration.DefaultExpirationMinutes); + } + else if (expiration.Value > TimeSpan.FromMinutes(apiKeyConfiguration.MaxExpirationMinutes)) + { + expiration = TimeSpan.FromMinutes(apiKeyConfiguration.MaxExpirationMinutes); + } + + // + // Generate New Key ... + (string plainKey, string keyHash, string prefix) = XApiKeyHelper.Generate(apiKeyConfiguration.ApiKeyPrefix); + var expireAt = DateTime.UtcNow.Add(expiration.Value); + + // + var _allowedScopes = scopes.ToJSON(); + var _allowedIPs = allowedIPs.ToListString(); + + // + // Instance Dto Item ... + var result = new XApiKeyDto + { + RevokedAt = null, + KeyHash = keyHash, + LastUsedAt = null, + KeyPrefix = prefix, + ExpiresAt = expireAt, + RevokedBy = string.Empty, + AllowedIPs = _allowedIPs, + CreatedOn = DateTime.UtcNow, + ApplicationId = applicationId, + AllowedScopes = _allowedScopes, + RateLimitPerMinute = rateLimit.Value + }; + + // + // Add Item to Database ... + result = await AddApiKeyAsync( + item: result, + apiKey: plainKey, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Revoke Specified Api Key ... + /// + /// + /// + /// + /// + public async Task RevokeApiKey( + Guid apiKeyId, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve ApiKey ... + var item = await GetApiKeyAsync( + id: apiKeyId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (item.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Permission Checking ... + var application = await GetApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + id: item.ApplicationId, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + // Update Revoke Info ... + item.RevokedAt = DateTime.UtcNow; + item.RevokedBy = userInfo.UserId; + + // + item = await UpdateApiKeyAsync( + id: item.Id, + item: item, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + var result = !item.IsNullOrDefault(); + return result; + } + + /// + /// Renew Specified Api Key ... + /// + /// + /// + /// + /// + /// + public async Task RotateApiKey( + Guid apiKeyId, + TimeSpan? newExpiration = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + apiKeyId.IsDefaultGuid() || + userInfo.IsNullOrDefault() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Item ... + var result = await GetApiKeyAsync( + id: apiKeyId, + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (result.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Expired Date ... + DateTime? expiredDate = null; + if (!newExpiration.HasValue) + { + // + newExpiration = TimeSpan + .FromMinutes(apiKeyConfiguration.DefaultExpirationMinutes); + expiredDate = DateTime.UtcNow.Add(newExpiration.Value); + } + if (!expiredDate.HasValue) + { + XException.InvalidArgs.Throw(); + } + + // + // Update Item ... + result.ExpiresAt = expiredDate.Value; + + // + result = await UpdateApiKeyAsync( + item: result, + id: result.Id, + userInfo: userInfo, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Retrieve All Api Keys for Specified Application ... + /// + /// + /// + /// + /// + public async Task> GetApplicationApiKeys( + int applicationId, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if ( + userInfo.IsNullOrDefault() || + !applicationId.IsValidIntId() + ) + { + XException.InvalidArgs.Throw(); + } + + // + // Permission Checking ... + var application = await GetApplicationAsync( + userInfo: userInfo, + includeBuilder: null, + id: applicationId, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + if (application.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + if (!IsOwned(application, userInfo)) + { + XException.NotAllowed.Throw(); + } + + // + var result = await FindManyApiKeyAsync( + userInfo: userInfo, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.Id), + predicate: x => x.ApplicationId == applicationId + ); + + // + return result; + } + + /// + /// Validate Specified Api Key in based on ClientIP ... + /// + /// + /// + /// + /// + /// + /// + public async Task ValidateApiKey( + string apiKey, + string clientIP, + string requiredScope = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + var result = new XApiKeyValidationResult(); + + // + // Validate ... + var isValid = + !apiKey.IsNullOrEmpty() && + !clientIP.IsNullOrEmpty(); + if (!isValid) + { + return result; + } + + // + // Detect Specified Api Key ... + var apiKeyHash = XApiKeyHelper.ComputeHash(apiKey); + var keyModel = await dbContext.ApiKeys + .FirstOrDefaultAsync( + ak => ak.KeyHash == apiKeyHash, + cancellationToken: cancellationToken + ); + isValid = !keyModel.IsNullOrDefault(); + if (!isValid) + { + // + result.Errors.Add("ApiKey not found ..."); + return result; + } + + // + // Here we Have to Parse Allowed Ips and Scopes ... + var allowedIps = (keyModel.AllowedIPs ?? string.Empty) + .ParseListString(); + var allowedScopes = (keyModel.AllowedScopes ?? string.Empty) + .FromJSON>(); + result.Scopes = [.. allowedScopes]; + + // + // Check Application is Exists and Active ... + var application = await dbContext.Applications + .FirstOrDefaultAsync(x => x.Id == keyModel.ApplicationId); + if (!application.IsNullOrDefault()) + { + result.OwnerId = application.OwnerId; + result.ApplicationId = application.Id; + } + + // + // Check Application is Exists and Active or not ... + var isApplicationActive = + // + // Application Exists ... + !application.IsNullOrDefault() && + // + // Check Application is Active ... + application.IsActive + // + ; + if (!isApplicationActive) + { + result.Errors.Add("Related Application is Inactive ..."); + } + + // + // Check Key is Active or not ... + var isKeyActive = + // + // Check Api Key is Active ... + // Not Revoked ... + // Not Expired ... + keyModel.IsActive() + // + ; + if (!isKeyActive) + { + // + if (keyModel.IsExpired()) + { + result.Errors.Add("ApiKey is Expired ..."); + } + + // + if (keyModel.IsRevoked()) + { + result.Errors.Add("ApiKey is Revoked ..."); + } + } + + // + // Checking Rate Limit ... + var isRateLimitPassed = CheckRateLimit(keyModel.Id, keyModel.RateLimitPerMinute); + if (!isRateLimitPassed) + { + result.Errors.Add("ApiKey Rate Limit Reached ..."); + } + + // + // Audit Log ... + + // + // Check Scope and IP is Valid or not ... + var isIpValid = + !allowedIps.HasChild() || + allowedIps.Contains(clientIP); + var isScopeValid = + !allowedScopes.HasChild() || + requiredScope.IsNullOrEmpty() || + allowedScopes.Contains(requiredScope); + var isScopeIpValid = + isIpValid && + isScopeValid + ; + if (!isScopeIpValid) + { + // + if (!isIpValid) + { + result.Errors.Add("Client IP not Allowed to Use Resource ..."); + } + + // + if (!isScopeValid) + { + result.Errors.Add("Required Scoped is Invalid ..."); + } + } + + // + isValid = + isKeyActive && + isScopeIpValid && + isRateLimitPassed && + isApplicationActive; + if (isValid) + { + result.Errors.Clear(); + } + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XApplicationProvider\XApplicationProvider+Enricher.cs + +using System.Linq; +using System.Threading; +using xCommons.Extensions; +using xIdentityModels.Dtos; +using System.Threading.Tasks; +using xIdentityModels.Models; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace xIds.Providers +{ + public partial class XApplicationProvider + { + // + #region Enrichers ... + /// + /// Enrich Api Key ... + /// + /// + /// + /// + public async Task EnrichAsync( + XApiKey source, + CancellationToken cancellationToken = default + ) + { + // + var result = source.MapConvert(mapperConfigurationExp); + result = await EnrichAsync( + source: result, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Enrich Api Key ... + /// + /// + /// + /// + public async Task EnrichAsync( + XApiKeyDto source, + CancellationToken cancellationToken = default + ) + { + // + if ( + !source.IsNullOrDefault() && + !source.Application.IsNullOrDefault() + ) + { + // + var application = await dbContext.Applications + .FirstOrDefaultAsync( + x => x.Id == source.ApplicationId, + cancellationToken: cancellationToken + ); + if (!application.IsNullOrDefault()) + { + // + var dto = await EnrichAsync( + source: application, + cancellationToken: cancellationToken + ); + if (!dto.IsNullOrDefault()) + { + source.Application = dto; + } + } + } + + // + return source; + } + + /// + /// Enrich Api Key Usage ... + /// + /// + /// + /// + public async Task EnrichAsync( + XApiKeyUsage source, + CancellationToken cancellationToken = default + ) + { + // + var result = source.MapConvert(mapperConfigurationExp); + result = await EnrichAsync( + source: result, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Enrich Application ... + /// + /// + /// + /// + public async Task EnrichAsync( + XApplication source, + CancellationToken cancellationToken = default + ) + { + // + var result = source.MapConvert(mapperConfigurationExp); + result = await EnrichAsync( + source: result, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Enrich Api Key Usage ... + /// + /// + /// + /// + public async Task EnrichAsync( + XApiKeyUsageDto source, + CancellationToken cancellationToken = default + ) + { + // + // There is not any Additional Enrichment ... + var result = await Task.FromResult(source); + return result; + } + + /// + /// Enrich Application ... + /// + /// + /// + /// + public async Task EnrichAsync( + XApplicationDto source, + CancellationToken cancellationToken = default + ) + { + // + if ( + !source.IsNullOrDefault() && + !source.OwnerId.IsNullOrEmpty() + ) + { + // + var owner = await GetOwner(source.OwnerId); + if (!owner.IsNullOrDefault()) + { + source.Owner = owner; + } + } + + // + return source; + } + + /// + /// Enrich a Collection of Api Key(s) ... + /// + /// + /// + /// + public async Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ) + { + // + var tasks = sources + .Select(s => EnrichAsync(s, cancellationToken)); + + // + var result = await Task.WhenAll(tasks); + + // + return result; + } + + /// + /// Enrich a Collection of Api Key(s) ... + /// + /// + /// + /// + public async Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ) + { + // + var tasks = sources + .Select(s => EnrichAsync(s, cancellationToken)); + + // + var result = await Task.WhenAll(tasks); + + // + return result; + } + + /// + /// Enrich a Collection of Api Key Usage(s) ... + /// + /// + /// + /// + public async Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ) + { + // + var tasks = sources + .Select(s => EnrichAsync(s, cancellationToken)); + + // + var result = await Task.WhenAll(tasks); + + // + return result; + } + + /// + /// Enrich a Collection of Application(s) ... + /// + /// + /// + /// + public async Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ) + { + // + var tasks = sources + .Select(s => EnrichAsync(s, cancellationToken)); + + // + var result = await Task.WhenAll(tasks); + + // + return result; + } + + /// + /// Enrich a Collection of Api Key Usage(s) ... + /// + /// + /// + /// + public async Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ) + { + // + var tasks = sources + .Select(s => EnrichAsync(s, cancellationToken)); + + // + var result = await Task.WhenAll(tasks); + + // + return result; + } + + /// + /// Enrich a Collection of Application(s) ... + /// + /// + /// + /// + public async Task> EnrichRangeAsync( + IEnumerable sources, + CancellationToken cancellationToken = default + ) + { + // + var tasks = sources + .Select(s => EnrichAsync(s, cancellationToken)); + + // + var result = await Task.WhenAll(tasks); + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Admin.cs + +using System.Collections.Generic; +using System.Threading.Tasks; +using xCommons.Extensions; +using xIdentityModels.Dtos; + +namespace xIds.Providers +{ + public partial class XIdentityManager + { + #region Admin Actions ... + /// + /// Retrieve All UnBanned Users Identifiers + /// + /// caller user identifier + /// a collection of user identifiers + public async Task> GetAllUnbanned( + ICollection userSelectByParam + ) + { + // + var result = new List(); + 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; + } + + /// + /// Get All Banned Users Identifiers + /// + /// caller user identifier + /// a collection of user identifiers + public async Task> GetAllBanned( + ICollection userSelectByParam + ) + { + // + var result = new List(); + 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; + } + + /// + /// Ban a Collection of Users + /// + /// caller user identifier + /// an instance of XUserNameIdRequest which represents user identifier list to Ban + /// a collection of user identifiers + public async Task> 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(); + 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; + } + + /// + /// UnBan a Collection of Users + /// + /// caller user identifier + /// an instance of XUserNameIdRequest which represents user identifier list to Ban + /// a collection of user identifiers + public async Task> 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(); + 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; + } + + /// + /// Detrmines a User is Banned or not + /// + /// caller user identifier + /// destination user identifier which checked is banned or not + /// a boolean value which represent user banned or not + public async Task 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Device.cs + +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 ... + /// + /// Check a Device Exists or Not + /// + /// an instance of XDevice + /// a boolean value + public async Task IsDeviceExistsAsync( + XDevice device + ) + { + return await DbContext.Devices + .AnyAsync(d => d.IsSameAs(device)); + } + + /// + /// Add new Device + /// + /// an instance of XDevice + /// an instance of XDevice + public async Task AddDeviceAsync( + XDevice device + ) + { + // + var isDeviceExists = await IsDeviceExistsAsync(device); + if (isDeviceExists) + { + return null; + } + + // + await DbContext.Devices + .AddAsync(device); + + await DbContext.SaveChangesAsync(); + + return device; + } + + /// + /// Get a Device + /// + /// an instance of XDevice + /// an instance of XDevice + public Task GetDeviceAsync( + XDevice device + ) + { + // + return DbContext.Devices + .FirstOrDefaultAsync(d => + d.IsSameAs(device)); + } + + /// + /// Retrieve a List of User Related Devices + /// + /// user identifier + /// check a user can log in in system or not + /// check user is banned or not + /// a collection of XDevice instances which related to user + public async Task> 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(); + } + + // + return user.Devices; + } + + /// + /// Determines a Device is Exists in User's Devices or not + /// + /// an instance of XUser + /// an instance of XDevice + /// a boolean value + public bool IsDeviceRelateDToUser( + XUser user, + XDevice device + ) + { + if (!user.Devices.HasChild()) + { + return false; + } + + // + var result = user.Devices.Any(d => d.IsSameAs(device)); + + // + return result; + } + + /// + /// Determines a Device is Exists in User's Devices or not + /// + /// user identifier + /// an instance of XDevice + /// check a user can log in in system or not + /// check user is banned or not + /// a boolean value + public async Task 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; + } + + /// + /// Add a Device to a User Related Devices + /// + /// an instance of XUser + /// an instance of XDevice + /// a boolean value + public async Task 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(); + } + } + + /// + /// Add a Device to a User + /// + /// user identifier + /// an instance of XDevice + /// check a user can log in in system or not + /// check user is banned or not + /// a boolean value + public async Task 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; + } + + /// + /// Remove a Device from a User Related Devices List + /// + /// an instance of XUser + /// an instance of XDevice + /// a boolean value + public async Task 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; + } + + /// + /// Remove a Device from a User Devices + /// + /// user identifier + /// an instance of XDevice + /// check a user can log in in system or not + /// check user is banned or not + /// a boolean value + public async Task 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 ... + /// + /// Check a Device is exists in Banned Devices or not + /// + /// an instance of XDevice + /// a boolean value + public async Task 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; + } + + /// + /// Find First Banned Device + /// + /// an instance of XDevice + /// an instance of XDevice + public async Task BannedDeviceFindOne( + XDevice device + ) + { + // + var result = await DbContext.BannedDevices + .FirstOrDefaultAsync(bd => + bd.Device.IsSameAs(device) + ); + + // + return result; + } + + /// + /// Remove Banned Device + /// + /// an instance of XBannedDevice + /// + 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(); + } + + /// + /// Check a Device is Banned or not + /// + /// an instance of XDevice + /// a boolean value + private async Task IsDeviceBanned( + XDevice device + ) + { + // + var result = await IsBannedDeviceExists(device); + return result; + } + + /// + /// Retrieve Specific Banned Device + /// + /// an instance of XDevice + /// an instance of XBannedDevice + private async Task GetBannedDevice( + XDevice device + ) + { + // + var result = await BannedDeviceFindOne(device); + return result; + } + + /// + /// Is a Banned Device passed Banning Time + /// + /// an instance of XBannedDevice + /// a boolean value + private bool IsDelayTimePassed( + XBannedDevice bannedDevice + ) + { + // + // Validate Args ... + if (bannedDevice == null || + Configuration == null) + { + XException.InvalidArgs.Throw(); + } + + // + var isPassed = bannedDevice + .IsDelayTimePassed(Configuration.BannedDeviceTimeout); + + // + return isPassed; + } + + /// + /// Retrieve Passed Time from Banning Time of specific Device + /// + /// an instance of XBannedDevice + /// an instance of DateTime which represent Passed Time + private DateTime GetPassedTime( + XBannedDevice bannedDevice + ) + { + // + var result = bannedDevice.BannedOn + .AddSeconds(Configuration.BannedDeviceTimeout); + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Friendship.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using xCommons.Extensions; +using xDataService.Extensions; +using xExceptions.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Dtos; +using xIdentityModels.Navigations; +using xModels.Dtos; + +namespace xIds.Providers +{ + public partial class XIdentityManager + { + // + #region Friendship Actions ... + // + #region Actions ... + /// + /// Send a Follow Request for userSelectByParam to + /// destUserSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// an instance of XFriendshipFollowing + public async Task Follow( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + XFriendshipFollowing following = null; + XFriendshipFollower follower = null; + var isRequested = IsFollowRequested(xUser, xDestUser); + // + // Check Request Happens Or not ... + if (!isRequested) + { + // + following = new XFriendshipFollowing + { + UserId = xUser.Id, + DestId = xDestUser.Id, + State = XFriendshipState.Pending + }; + + // + follower = new XFriendshipFollower + { + UserId = xDestUser.Id, + DestId = xUser.Id, + State = XFriendshipState.Pending + }; + + // + xUser.Followings.Add(following); + xDestUser.Followers.Add(follower); + } + else + { + // + // if Request Done Before Must Check State ... + following = xUser.Followings.FirstOrDefault(f => f.DestId == xDestUser.Id); + follower = xDestUser.Followers.FirstOrDefault(f => f.DestId == xUser.Id); + if (following.IsNull() || + follower.IsNull()) + { + XException.NotFound.Throw(); + } + + // + switch (following.State) + { + case XFriendshipState.Accepted: + case XFriendshipState.Blocked: + throw XException.NotAllowed.ToException(); + + case XFriendshipState.Rejected: + following.State = XFriendshipState.Pending; + follower.State = XFriendshipState.Pending; + break; + } + } + + // + var canContinue = !following.IsNull() && !follower.IsNull(); + if (!canContinue) + { + XException.ActionFailed.Throw(); + } + + // + await UpdateUserAsync(xUser); + await UpdateUserAsync(xDestUser); + + // + var result = following; + + // + return result; + } + + /// + /// Cancel Following + /// + /// caller user identifier + /// dest user identifier + /// a boolean value + public async Task Cancel( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + XFriendshipFollowing following = null; + XFriendshipFollower follower = null; + var isRequested = IsFollowRequested(xUser, xDestUser); + // + // Check Request Happens Or not ... + if (!isRequested) + { + return false; + } + + // + // if Request Done Before Must Check State ... + following = xUser.Followings.FirstOrDefault(f => f.DestId == xDestUser.Id); + follower = xDestUser.Followers.FirstOrDefault(f => f.DestId == xUser.Id); + if (following.IsNull() || + follower.IsNull()) + { + XException.NotFound.Throw(); + } + + // + switch (following.State) + { + case XFriendshipState.Accepted: + case XFriendshipState.Blocked: + throw XException.NotAllowed.ToException(); + + case XFriendshipState.Rejected: + case XFriendshipState.Pending: + DbContext.Followers.Remove(follower); + DbContext.Followings.Remove(following); + break; + } + + // + var canContinue = !following.IsNull() && !follower.IsNull(); + if (!canContinue) + { + XException.ActionFailed.Throw(); + } + + // + await DbContext.SaveChangesAsync(); + + // + return true; + } + + /// + /// userSelectByParam remove friendship by it's Follower + /// destUserSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// + public async Task UnFollowFollower( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + ValidateIsFollowers(xUser, xDestUser); + + // + var follower = xUser.Followers + .FirstOrDefault(f => + f.DestId == xDestUser.Id && + f.State == XFriendshipState.Accepted); + var following = xDestUser.Followings + .FirstOrDefault(f => + f.DestId == xUser.Id && + f.State == XFriendshipState.Accepted); + if (follower.IsNull() || + following.IsNull()) + { + XException.NotFound.Throw(); + } + + // + DbContext.Followers.Remove(follower); + DbContext.Followings.Remove(following); + + // + await DbContext.SaveChangesAsync(); + } + + /// + /// userSelectByParam remove friendship by it's Followings + /// destUserSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// + public async Task UnFollowFollowing( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + ValidateIsFollowings(xUser, xDestUser); + + // + var following = xUser.Followings + .FirstOrDefault(f => + f.DestId == xDestUser.Id && + f.State == XFriendshipState.Accepted); + var follower = xDestUser.Followers + .FirstOrDefault(f => + f.DestId == xUser.Id && + f.State == XFriendshipState.Accepted); + if (follower.IsNull() || + following.IsNull()) + { + XException.NotAllowed.Throw(); + } + + // + DbContext.Followers.Remove(follower); + DbContext.Followings.Remove(following); + + // + await DbContext.SaveChangesAsync(); + } + + /// + /// userSelectByParam Block it's follower destUserSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// an instance of XFriendshipFollowing + public async Task Block( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + // Validate XDestUser is Accepted Follower of XUser ... + ValidateIsFollowers(xUser, xDestUser); + + // + // Retrieve FriendShip Models ... + var following = xDestUser.Followings + .FirstOrDefault(f => + f.DestId == xUser.Id && + ( + f.State == XFriendshipState.Accepted + ) + ); + var follower = xUser.Followers + .FirstOrDefault(f => + f.DestId == xDestUser.Id && + ( + f.State == XFriendshipState.Accepted + ) + ); + if (following.IsNull() || + follower.IsNull()) + { + XException.NotAllowed.Throw(); + } + + // + follower.State = XFriendshipState.Blocked; + following.State = XFriendshipState.Blocked; + + // + DbContext.Followers.Update(follower); + DbContext.Followings.Update(following); + + // + await DbContext.SaveChangesAsync(); + + // + var result = following; + + // + return result; + } + + /// + /// userSelectByParam UnBlock it's follower destUserSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// an instance of XFriendshipFollowing + public async Task UnBlock( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + // Validate XDestUser is Accepted Follower of XUser ... + ValidateIsBlock(xUser, xDestUser); + + // + // Retrieve FriendShip Models ... + var following = xDestUser.Followings + .FirstOrDefault(f => + f.DestId == xUser.Id && + ( + f.State == XFriendshipState.Blocked + ) + ); + var follower = xUser.Followers + .FirstOrDefault(f => + f.DestId == xDestUser.Id && + ( + f.State == XFriendshipState.Blocked + ) + ); + if ( + following.IsNull() || + follower.IsNull() + ) + { + XException.NotAllowed.Throw(); + } + + // + follower.State = XFriendshipState.Accepted; + following.State = XFriendshipState.Accepted; + + // + DbContext.Followers.Update(follower); + DbContext.Followings.Update(following); + + // + await DbContext.SaveChangesAsync(); + + // + var result = following; + + // + return result; + } + #endregion + + // + #region Request Handlers ... + /// + /// userSelectByParam Accept Follow Request of + /// destUserSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// an instance of XFriendshipFollower + public async Task AcceptRequest( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + // Validate Request Sent and in Pending State ... + ValidateFollowingRequestForAccept(xUser, xDestUser); + + // + // Retrieve FriendShip Models ... + var following = xDestUser.Followings + .FirstOrDefault(f => + f.DestId == xUser.Id && + ( + f.State == XFriendshipState.Pending || + f.State == XFriendshipState.Rejected + ) + ); + var follower = xUser.Followers + .FirstOrDefault(f => + f.DestId == xDestUser.Id && + ( + f.State == XFriendshipState.Pending || + f.State == XFriendshipState.Rejected + ) + ); + if (following.IsNull() || + follower.IsNull()) + { + XException.NotAllowed.Throw(); + } + + // + follower.State = XFriendshipState.Accepted; + following.State = XFriendshipState.Accepted; + + // + await UpdateUserAsync(xUser); + await UpdateUserAsync(xDestUser); + + // + var result = follower; + + // + return result; + } + + /// + /// Reject a Follow Request of destUserSelectByParam + /// with userSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// an instance of XFriendshipFollower + public async Task RejectRequest( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + // Validate Request Sent and in Pending State ... + ValidateFollowingRequest(xUser, xDestUser); + + // + // Retrieve Pending Specified User Request Friendship models ... + var following = xDestUser.Followings + .FirstOrDefault(f => + f.DestId == xUser.Id && + f.State == XFriendshipState.Pending); + var follower = xUser.Followers + .FirstOrDefault(f => + f.DestId == xDestUser.Id && + f.State == XFriendshipState.Pending); + if (following.IsNull() || + follower.IsNull()) + { + XException.NotFound.Throw(); + } + + // + follower.State = XFriendshipState.Rejected; + following.State = XFriendshipState.Rejected; + + // + DbContext.Followers.Update(follower); + DbContext.Followings.Update(following); + + // + await DbContext.SaveChangesAsync(); + + // + var result = follower; + + // + return result; + } + #endregion + + // + #region Getters ... + /// + /// determines destUserSelectByParam is in followers of + /// userSelectByParam or not + /// + /// caller user identifier + /// dest user identifier + /// check user is banned or not, default is true + /// check a user can log in in system or not, default is true + /// a boolean value + public async Task IsFollower( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + var result = IsFollowRequested(xDestUser, xUser, XFriendshipState.Accepted); + + // + return result; + } + + /// + /// retrieve Friendship state of destUserSelectByParam + /// in Followers of userSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// check user is banned or not, default is true + /// check a user can log in in system or not, default is true + /// a member of XFriendshipState + public async Task GetFollowerState( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + var isFollow = IsFollowRequested(xDestUser, xUser); + if (!isFollow) + { + return XFriendshipState.None; + } + + // + var follower = xUser.Followers.FirstOrDefault(f => f.DestId == xDestUser.Id); + if (follower.IsNull()) + { + XException.NotFound.Throw(); + } + + // + return follower.State; + } + + /// + /// get follower friendship model of destUserSelectByParam + /// in followers of userSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// an instance of XFriendshipFollower + public async Task GetFollower( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + var isFollow = IsFollowRequested(xDestUser, xUser); + if (!isFollow) + { + return null; + } + + // + var follower = xUser.Followers + .FirstOrDefault(f => f.DestId == xDestUser.Id); + if (follower.IsNull()) + { + XException.NotFound.Throw(); + } + + // + var result = follower; + + // + return result; + } + + /// + /// Retrieve Accepted Followers list of userSelectByParam + /// + /// caller user identifier + /// a collection of XFriendshipFollower instances + public async Task> GetFollowers( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + var result = xUser.Followers + .Where(f => f.State == XFriendshipState.Accepted); + + // + return result; + } + + /// + /// Retrieve all Followers list of userSelectByParam + /// + /// caller user identifier + /// a collection of XFriendshipFollower instances + public async Task> GetAllFollowers( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + var result = xUser.Followers; + + // + return result; + } + + /// + /// Read Specified User's Follower's List based on Query Model ... + /// + /// + /// + /// + public async Task> QueryFollowers( + XQuery query, + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(query); + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: false, + containDetails: false, + ignoreDisabledUser: false, + checkCanLoginPolicies: false + ); + + // + // Prepare Query ... + + // + if (query.PageSize < DataConfiguration + .PagingConfiguration + .MinAvailablePageSize) + { + // + query.PageSize = DataConfiguration + .PagingConfiguration + .DefaultPageSize; + } + + // + if (query.PageSize > DataConfiguration + .PagingConfiguration + .MaxAvailablePageSize) + { + // + query.PageSize = DataConfiguration + .PagingConfiguration + .MaxAvailablePageSize; + } + + // + // Prepare Enumerator ... + Expression> whereClause = x => x.UserId == xUser.Id; + var enumerator = DbContext.Followers.AsAsyncEnumerable(); + var resultItems = new List(); + await foreach (var entity in enumerator) + { + // + var isApproved = whereClause.Compile()(entity); + if (isApproved) + { + // + // Convert Approved Entity to Dto ... + // then Validate it and Add it to Results ... + var dto = await ToDto(entity); + if (!dto.IsNull()) + { + resultItems.Add(dto); + } + } + } + + // + // Enumerable ... + var result = resultItems.AsEnumerable(); + + // + // Count Total Items ... + var totalItemsCount = result.Count(); + + // + // Apply Filter ... + if (!query.Filter.IsNullOrEmpty()) + { + result = result.ApplyFilter(query.Filter); + } + + // + // Count Filtered Items ... + var totalFilteredItemsCount = result.Count(); + + // + // Apply Paging ... + if (totalItemsCount > 0 && + totalFilteredItemsCount > 0) + { + // + // Apply Sorting ... + if (!query.SortBy.IsNullOrEmpty()) + { + // + // Since Sorting Based on Date is Very Important ... + // we have to do Sorting at first Time then + // try to apply Paging ... + result = result + .ApplySorting( + query.SortBy, + query.IsAscending) + .ToList(); + } + + // + // Apply Paging ... + result = result + .ApplyPaging( + query.Page, + query.PageSize) + .ToList(); + } + + // + // Calculating Page Count ... + int pagesCount = totalItemsCount / query.PageSize; + if (totalItemsCount % query.PageSize > 0) + { + pagesCount++; + } + + // + // Generate Result Object ... + var queryResult = new XQueryResult + { + Page = query.Page, + TotalPages = pagesCount, + PageSize = query.PageSize, + TotalItems = totalItemsCount, + Items = result.AsEnumerable(), + TotalFilteredItems = totalFilteredItemsCount + }; + + // + return queryResult; + } + + /// + /// determines destUserSelectByParam is in followings of + /// userSelectByParam or not + /// + /// caller user identifier + /// dest user identifier + /// check user is banned or not, default is true + /// check a user can log in in system or not, default is true + /// a boolean value + public async Task IsFollowing( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + var result = IsFollowRequested(xUser, xDestUser, XFriendshipState.Accepted); + + // + return result; + } + + /// + /// retrieve Frindship State of destUserSelectByParam + /// in Followings of userSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// check user is banned or not, default is true + /// check a user can log in in system or not, default is true + /// a member of XFriendshipState + public async Task GetFollowingState( + string userSelectByParam, + string destUserSelectByParam, + bool checkIsBanned = true, + bool checkCanLoginPolicies = true + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + var isFollow = IsFollowRequested(xUser, xDestUser); + if (!isFollow) + { + return XFriendshipState.None; + } + + // + var following = xUser.Followings.FirstOrDefault(f => f.DestId == xDestUser.Id); + if (following.IsNull()) + { + XException.NotFound.Throw(); + } + + // + return following.State; + } + + /// + /// get following friendship model of destUserSelectByParam + /// in followings of userSelectByParam + /// + /// caller user identifier + /// dest user identifier + /// an instance of XFriendshipFollowing + public async Task GetFollowing( + string userSelectByParam, + string destUserSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + // Validate Not Same Source and Dest Users ... + ValidateNotSameUsers(xUser, xDestUser); + + // + var isFollow = IsFollowRequested(xUser, xDestUser); + if (!isFollow) + { + return null; + } + + // + var following = xUser.Followings.FirstOrDefault(f => f.DestId == xDestUser.Id); + if (following.IsNull()) + { + XException.NotFound.Throw(); + } + + // + var result = following; + + // + return result; + } + + /// + /// Retrieve accepted Followings List of userSelectByParam + /// + /// caller user identifier + /// a collection of XFriendshipFollowing instances + public async Task> GetFollowings( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + var result = xUser.Followings + .Where(f => f.State == XFriendshipState.Accepted); + + // + return result; + } + + /// + /// Retrieve all Followings List of userSelectByParam + /// + /// caller user identifier + /// a collection of XFriendshipFollowing instances + public async Task> GetAllFollowings(string userSelectByParam) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: true, + containDetails: true, + ignoreDisabledUser: false, + checkCanLoginPolicies: true + ); + + // + var result = xUser.Followings; + + // + return result; + } + + /// + /// Read Specified User's Following's List based on Query Model ... + /// + /// + /// + /// + public async Task> QueryFollowings( + XQuery query, + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(query); + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve User ... + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + checkIsBanned: false, + containDetails: false, + ignoreDisabledUser: false, + checkCanLoginPolicies: false + ); + + // + // Prepare Query ... + + // + if (query.PageSize < DataConfiguration + .PagingConfiguration + .MinAvailablePageSize) + { + // + query.PageSize = DataConfiguration + .PagingConfiguration + .DefaultPageSize; + } + + // + if (query.PageSize > DataConfiguration + .PagingConfiguration + .MaxAvailablePageSize) + { + // + query.PageSize = DataConfiguration + .PagingConfiguration + .MaxAvailablePageSize; + } + + // + // Prepare Enumerator ... + Expression> whereClause = x => x.UserId == xUser.Id; + var enumerator = DbContext.Followings.AsAsyncEnumerable(); + var resultItems = new List(); + await foreach (var entity in enumerator) + { + // + var isApproved = whereClause.Compile()(entity); + if (isApproved) + { + // + // Convert Approved Entity to Dto ... + // then Validate it and Add it to Results ... + var dto = await ToDto(entity); + if (!dto.IsNull()) + { + resultItems.Add(dto); + } + } + } + + // + // Enumerable ... + var result = resultItems.AsEnumerable(); + + // + // Count Total Items ... + var totalItemsCount = result.Count(); + + // + // Apply Filter ... + if (!query.Filter.IsNullOrEmpty()) + { + result = result.ApplyFilter(query.Filter); + } + + // + // Count Filtered Items ... + var totalFilteredItemsCount = result.Count(); + + // + // Apply Paging ... + if (totalItemsCount > 0 && + totalFilteredItemsCount > 0) + { + // + // Apply Sorting ... + if (!query.SortBy.IsNullOrEmpty()) + { + // + // Since Sorting Based on Date is Very Important ... + // we have to do Sorting at first Time then + // try to apply Paging ... + result = result + .ApplySorting( + query.SortBy, + query.IsAscending) + .ToList(); + } + + // + // Apply Paging ... + result = result + .ApplyPaging( + query.Page, + query.PageSize) + .ToList(); + } + + // + // Calculating Page Count ... + int pagesCount = totalItemsCount / query.PageSize; + if (totalItemsCount % query.PageSize > 0) + { + pagesCount++; + } + + // + // Generate Result Object ... + var queryResult = new XQueryResult + { + Page = query.Page, + TotalPages = pagesCount, + PageSize = query.PageSize, + TotalItems = totalItemsCount, + Items = result.AsEnumerable(), + TotalFilteredItems = totalFilteredItemsCount + }; + + // + return queryResult; + } + #endregion + + // + #region Others ... + /// + /// Retrieve List Of All Followings User Id's + /// of userSelectByParam + /// + /// caller user identifier + /// a collection of user identifiers + public async Task> GetFollowingList( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve followings List ... + var followings = await GetFollowings(userSelectByParam); + + // + var result = followings.Select(f => f.DestId); + + // + return result.ToList(); + } + + /// + /// Retrieve List Of All Followers User Id's + /// of userSelectByParam + /// + /// caller user identifier + /// a collection of user identifiers + public async Task> GetFollowersList( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Validate and Retrieve followings List ... + var followers = await GetFollowers(userSelectByParam); + + // + var result = followers.Select(f => f.DestId); + + // + return result.ToList(); + } + + /// + /// retrieve Friendship Info Model for Specific User + /// + /// caller user identifier + /// dest user identifier + /// check a user can log in in system or not, default is true + /// check user is banned or not, default is true + /// an instance of XFriendshipInfoDto + public async Task GetFriendshipInfo( + string userSelectByParam, + string destUserSelectByParam, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + destUserSelectByParam + ); + + // + // Validate and Retrieve Dest User ... + var xSourceUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + // Validate and Retrieve Dest User ... + var xDestUser = await ValidateUserExistsAndRetrieve( + destUserSelectByParam, + forceAdmin: false, + containDetails: true, + ignoreDisabledUser: false, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + var isSame = xSourceUser.Id == xDestUser.Id; + var isFollower = false; + var isFollowing = false; + var followerState = XFriendshipState.None; + var followingState = XFriendshipState.None; + + // + if (!isSame) + { + // + isFollower = await IsFollower( + userSelectByParam, + destUserSelectByParam, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + isFollowing = await IsFollowing( + userSelectByParam, + destUserSelectByParam, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + followerState = await GetFollowerState( + userSelectByParam, + destUserSelectByParam, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + followingState = await GetFollowingState( + userSelectByParam, + destUserSelectByParam, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + } + + // + var result = new XFriendshipInfoDto + { + UserId = xDestUser.Id, + IsFollower = isFollower, + IsFollowing = isFollowing, + FollowerState = followerState, + FollowingState = followingState, + Followers = xDestUser.Followers + .Count(f => f.State == XFriendshipState.Accepted), + Followings = xDestUser.Followings + .Count(f => f.State == XFriendshipState.Accepted) + }; + + // + return result; + } + #endregion + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Identity.cs + +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 ... + /// + /// Request for Discovery Document + /// + /// an instance of DiscoveryDocumentResponse + public async Task RequestDiscoveryDocument() + { + // + var httpClient = GetHttpClient(); + var result = httpClient + .GetDiscoveryDocumentAsync(IdentityResourceConfiguration.Authority) + .ContinueWith(docTask => + { + // + httpClient.Dispose(); + return docTask.Result; + }); + + // + return await result; + } + + /// + /// Request AccessToken for Specific XApiScope + /// + /// a member of XApiScope + /// an instance of TokenResponse + public async Task 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; + } + + /// + /// Authenticate a User + /// + /// an instance of XLoginRequest class which represent Authentication requirements + /// an instance of TokenResponse + public async Task 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; + } + + /// + /// Do Login based on XLoginRequest + /// + /// an instance of XLoginRequest class which represent Authentication requirements + /// an instance of XLoginResponse + public async Task 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; + } + + /// + /// Refresh Tokens + /// + /// Authentication Tokens, instance of XTokenResponse + /// an instance of XTokenResponse + public async Task 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(); + throw error.ToException(); + } + + // + // Create XLoginResponse Model ... + var result = authResponse.CreateXTokenResponse(); + + // + return result; + } + #endregion + + // + #region Requirements ... + /// + /// Get an Instance of Http Client + /// + /// an instance of HttpClient + 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+MessageProvider.cs + +using System; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityModels.Navigations; +using xMessageService.Models; + +namespace xIds.Providers +{ + public partial class XIdentityManager + { + // + #region Message Provider ... + /// + /// Generate and Prepare an XMessage Instance for + /// User Invite Action + /// + /// specify destination language + /// reciever email address + /// invitation token + /// return url for invitation user to redirect + /// an instance of XMessage + 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; + } + + /// + /// Generate and Prepare an XMessage Instance for + /// New Device LoggedIn Action + /// + /// specify destination language + /// reciever email address + /// an instance of XDevice which represent user new Device + /// an instance of XMessage + 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; + } + + /// + /// Generate and Prepare an XMessage Instance for + /// Verification Code Action + /// + /// specify destination language + /// an string which points to a user email address or mobile number + /// user verification code + /// an instance of XMessage + 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; + } + + /// + /// Generate and Prepare an XMessage Instance for + /// Registration Approve Action + /// + /// specify destination language + /// reciever email address + /// a token which approved user action + /// return url for redirect + /// an instance of XMessage + 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; + } + + /// + /// Generate and Prepare an XMessage Instance for + /// Registration Finished Action + /// + /// specify destination language + /// reciever email address + /// return url for redirect + /// an instance of XMessage + 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; + } + + /// + /// Generate and Prepare an XMessage Instance for + /// Password Changed Action + /// + /// specify destination language + /// reciever email address + /// return url for redirect + /// specify throw exceptions on failure or not, default is true + /// an instance of XMessage + 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; + } + + /// + /// Generate and Prepare an XMessage Instance for + /// Reset Password Action + /// + /// specify destination language + /// reciever email address + /// a token which approved user action + /// return url for redirect + /// an instance of XMessage + 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Open.cs + +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 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 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 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> GetUserInfos( + XOpenActionRequestDto model + ) + { + // + var ids = model.Payload.ParseListString().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(); + if (result.IsNull() || + !result.Validate() || + token != result.Checksum + ) + { + // + result = null; + return result; + } + + // + var modelChecksum = result.ToOpenActionToken(); + result = + token == modelChecksum + ? result + : null; + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Private.cs + +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 ... + /// + /// Generate a Random Number ... + /// + /// a long value + private long GenerateRandom() + { + return new Random().Next(100000, 999999); + } + #endregion + + // + #region UserSelectBy Actions ... + /// + /// Retrieve UserSelectByType based on Given Info + /// + /// a user identifier + /// a boolean value which specify the identifier + /// must be specific and not empty, default is true + /// a boolean value which specify the type + /// must be specific, default is false + /// a member of XUserSelectBy + 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; + } + + /// + /// Retrieve UserSelectByParam based on XUser Object + /// + /// an instance of XUser + /// a boolean value which specify the identifier must nut empty, + /// and throw exception if it is empty, default is true + /// a collection of XUserSelectBy members which + /// exclude them from result, default is null + /// a user identifier + private string GetUserSelectByParam( + XUser user, + bool forceNotNull = true, + ICollection 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; + } + + /// + /// Retrieve UserSelectByParam based on XActionRequestContext instance + /// + /// an instance of XActionRequestContext + /// a boolean value which specify the identifier must nut empty, + /// and throw exception if it is empty, default is true + /// a collection of XUserSelectBy members which + /// exclude them from result, default is null + /// a user identifier + private string GetUserSelectByParam( + XActionRequestContext context, + bool forceNotNull = true, + ICollection 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; + } + + /// + /// Retrieve UserSelectByParam based on XActionRequestToken instance + /// + /// an instance of XActionRequestToken which + /// provides required informations + /// a boolean value which specify the identifier must nut empty, + /// and throw exception if it is empty, default is true + /// a collection of XUserSelectBy members which + /// exclude them from result, default is null + /// a user identifier + private string GetUserSelectByParam( + XActionRequestToken request, + bool forceNotNull = true, + ICollection excludes = null + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(request); + + // + // Generate User SelectBy ... + return GetUserSelectByParam( + context: request.Context, + forceNotNull: forceNotNull, + excludes: excludes + ); + } + + /// + /// Retrieve Available UserSelectByParams from given XUser + /// + /// an instance of XUser + /// + private ICollection GenerateUserSelectByParams( + XUser user + ) + { + // + if (user == null) + { + return null; + } + + // + var result = new List { + user.Id, + user.UserName, + user.PhoneNumber, + user.Email, + }; + + // + return result; + } + + /// + /// Retrieve Required XUserSelectByTypes + /// + /// a collection of XUserSelectBy members which + /// ignored in result + /// a collection of available XUserSelectBy members + private ICollection GenerateUserSelectByExcludes( + ICollection ignores + ) + { + // + var result = ObjectHelper + .ToEnumerableValues() + .Except(ignores); + + // + return result.ToList(); + } + #endregion + + // + #region XAction Actions ... + /// + /// Retrieve Action Result Response based on XToken + /// + /// an instance of XToken + /// an instance of XActionResponse + 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 ... + /// + /// Prepare Message Provider and Check it's State + /// + /// specify destination language + 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 ... + /// + /// Check Follow Request Sent Before + /// from dest to source + /// + /// an instance of XUser + /// an instance of XUser + /// a boolean value + 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; + } + + /// + /// Check Follow Request Sent Before + /// from dest to source + /// + /// an instance of XUser + /// an instance of XUser + /// a member of XFriendshipState + /// a boolean value + 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; + } + + /// + /// Converts to XFriendDto ... + /// + /// + /// + private async Task 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; + } + + /// + /// Converts to XFriendDto ... + /// + /// + /// + private async Task 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 ... + /// + /// Count User's Page + /// + /// an integer value which represent page size + /// an inetger value which represent pages count + public async Task UserPagesCount( + int pageSize + ) + { + // + int count = await UserManager.Users.CountAsync(); + int pagesCount = count / pageSize; + + // + if (count % pageSize > 0) + { + pagesCount++; + } + + // + return pagesCount; + } + + /// + /// Count Avatar's Page + /// + /// an integer value which represent page size + /// an inetger value which represent pages count + public async Task ProfileImagePagesCount( + int pageSize + ) + { + // + int count = await DbContext.Avatars.CountAsync(); + int pagesCount = count / pageSize; + + // + if (count % pageSize > 0) + { + pagesCount++; + } + + // + return pagesCount; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Profile.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +using xIdentityModels.Navigations; +using xModels.Dtos; +using xIds.Extensions; +using xIdentityModels.Extensions; +using xIdentityHelper; +using xIdentityHelper.Extensions; +using xDataService.Extensions; + +namespace xIds.Providers +{ + public partial class XIdentityManager + { + // + #region Retrieve Actions ... + /// + /// Retrieve User Profile + /// + /// a user identifier + /// requested user's identifier + /// specify check requested user's identifier not empty + /// and throw exception if it is, default true + /// check a user can log in in system or not, default is true + /// check user is banned or not, default is true + /// an instance of XUserProfileDto + public async Task GetUserProfileAsync( + string userSelectByParam, + string requestedUserSelectByParam, + bool forceCheckRequestedUser = true, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ) + { + // + // Data Validation ... + if (forceCheckRequestedUser) + { + ValidationProvider.NotEmpty( + userSelectByParam, + requestedUserSelectByParam); + } + else + { + ValidationProvider.NotEmpty(userSelectByParam); + } + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + var isAdmin = false; + var isSameUser = false; + if (forceCheckRequestedUser && + !requestedUserSelectByParam.IsNullOrEmpty()) + { + // + var xRequestedUser = await ValidateUserExistsAndRetrieve( + requestedUserSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + isSameUser = xUser.Id == xRequestedUser.Id; + + // + var xRequestUserRoleNames = await GetRoleNamesAsync( + userSelectByParam: requestedUserSelectByParam, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + isAdmin = xRequestUserRoleNames.Contains(XUserRole.Admin.GetStringValue()); + } + + // + var roleNames = await GetRoleNamesAsync( + userSelectByParam, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + var xFriendshipInfo = + requestedUserSelectByParam.IsNullOrEmpty() + ? null + : await GetFriendshipInfo( + requestedUserSelectByParam, + userSelectByParam, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies + ); + + // + var result = xUser.ToXProfileDto( + isAdmin: isAdmin, + roles: roleNames, + isSameUser: isSameUser, + friendship: xFriendshipInfo, + profileConfig: Configuration.Policy.Profile + ); + + // + return result; + } + + /// + /// Retrieve a Collection of User Profiles + /// + /// a collection of user identifiers + /// requested user's identifier + /// specify check requested user's identifier not empty + /// and throw exception if it is, default true + /// check a user can log in in system or not, default is true + /// check user is banned or not, default is true + /// an collection of XUserProfileDto instances + public async Task> GetUserProfilesAsync( + ICollection ids, + string requestedUserSelectByParam, + bool forceCheckRequestedUser = true, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ) + { + // + // Data Validation ... + if (forceCheckRequestedUser) + { + ValidationProvider.NotEmpty(requestedUserSelectByParam); + } + ValidationProvider.NotZeroChilds(ids); + + // + var result = new List(); + foreach (var id in ids) + { + // + var xProfile = await GetUserProfileAsync( + id, + requestedUserSelectByParam, + checkIsBanned: checkIsBanned, + checkCanLoginPolicies: checkCanLoginPolicies, + forceCheckRequestedUser: forceCheckRequestedUser + ); + result.Add(xProfile); + } + + // + return result; + } + + /// + /// Query User Profiles + /// + /// requested user's identifier + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XUserProfileDto + public async Task> QueryUsers( + 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() + .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 + { + Items = items, + Page = query.Page, + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + + /// + /// Query In Roles User Profiles + /// + /// requested user's identifier + /// an string which represent user role + /// how to filter results based on XQuery structure + /// if it's true the user must has exact role, otherwise top level users also listed, default is false + /// an instance of XQueryResult of XUserProfileDto + public async Task> QueryInRoleUsers( + string requestedUserSelectByParam, + string role, + XQuery query, + bool forceRole = false + ) + { + // + // Validation ... + if (query.IsNull() || role.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Role Exists ... + var isExistsRole = await IsRoleExistsAsync(role); + if (!isExistsRole) + { + XException.NotFound.Throw(); + } + + // + // Normalize ... + query = query.NormalizeQuery(DataConfiguration.ToXDataServiceConfig()); + + // + #region Select Users Based on Specific Roles ... + var selectedUsers = new List(); + var usersEnumerator = GetUsersDbSet(query.ContainsDetail).AsAsyncEnumerable(); + await + foreach (var user in usersEnumerator) + { + // + if (user.ContainsUserSelectByParam(requestedUserSelectByParam)) + { + continue; + } + + // + // Set Default Value is True ... + var isInRole = false; + + // + // if Force Role ... + if (forceRole) + { + isInRole = await UserManager.IsInRoleAsync(user, role); + } + else + { + // + // Select User Top Role ... + var userRoles = await GetRoleNamesAsync( + checkIsBanned: false, + userSelectByParam: user.Id, + checkCanLoginPolicies: false + ); + isInRole = userRoles.HasRolePermissions(role); + } + + // + if (isInRole) + { + selectedUsers.Add(user.Id); + } + } + #endregion + + // + var items = await GetUserProfilesAsync( + selectedUsers, + 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 + { + Items = items, + Page = query.Page, + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + + /// + /// Query Specified User's Profile Images + /// + /// a user identifier + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XProfileImage + public async Task> QueryAvatars( + string userSelectByParam, + XQuery query + ) + { + // + // Validation ... + if (query.IsNull()) + { + XException.InvalidArgs.Throw(); + } + + // + // Normalize ... + query = query.NormalizeQuery(DataConfiguration.ToXDataServiceConfig()); + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: true, + checkIsBanned: true + ); + + // + var items = query.ContainsDetail ? + DbContext.Avatars + .Include(pf => pf.User) + .AsEnumerable() : + DbContext.Avatars + .AsEnumerable(); + items = items + .Where(pfi => pfi.UserId == xUser.Id); + + // + 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 + { + Items = items, + Page = query.Page, + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + + /// + /// Check specific user Confirmed Email or not + /// + /// a user identifier + /// a boolean value + public async Task IsConfirmedEmail( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + return xUser.EmailConfirmed; + } + + /// + /// Ceck specific user Confirmed Mobile or not + /// + /// a user identifier + /// a boolean value + public async Task IsConfirmedMobile( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + return xUser.PhoneNumberConfirmed; + } + #endregion + + // + #region Update Actions ... + /// + /// Update User Profile (FirstName/LastName/DateOfBirth) ... + /// + /// a user identifier + /// requested user's identifier + /// user update info, an instance of XProfileUpdateRequest + /// an instance of XUserProfileDto + public async Task ProfileUpdateAsync( + string userSelectByParam, + string requestedUserSelectByParam, + XProfileUpdateRequest model + ) + { + // + // Data Validation ... + ValidationProvider.NotEmpty( + userSelectByParam, + requestedUserSelectByParam); + ValidationProvider.NotNull(model); + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + var xRequestedUser = await ValidateUserExistsAndRetrieve( + requestedUserSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + await ValidateUserForDangerousAction(xUser, xRequestedUser); + + // + // FirstName ... + if (!model.FirstName.IsNullOrEmpty()) + { + xUser.FirstName = model.FirstName; + } + + // + // Last Name ... + if (!model.LastName.IsNullOrEmpty()) + { + xUser.LastName = model.LastName; + } + + // + // Date of Birth ... + if (model.DateOfBirth.HasValue) + { + xUser.DateOfBirth = model.DateOfBirth.Value; + } + + // + // Bio ... + xUser.Bio = model.Bio; + + // + // Cover Image ... + xUser.CoverImage = model.CoverImage; + + // + // Open To Search ... + xUser.OpenToSearch = model.OpenToSearch; + + // + var updateResult = await UpdateUserAsync(xUser); + if (!updateResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + var result = await GetUserProfileAsync(userSelectByParam, requestedUserSelectByParam); + return result; + } + + /// + /// Update User Profile + /// + /// a user identifier + /// requested user's identifier + /// user update info, an instance of XProfileUpdateRequest + /// an instance of XUserProfileDto + public async Task FullProfileUpdateAsync( + string userSelectByParam, + string requestedUserSelectByParam, + XProfileUpdateRequest model + ) + { + // + // Data Validation ... + ValidationProvider.NotEmpty( + userSelectByParam, + requestedUserSelectByParam); + ValidationProvider.NotNull(model); + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + var xRequestedUser = await ValidateUserExistsAndRetrieve( + requestedUserSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + forceAdmin: false + ); + + // + await ValidateUserForDangerousAction(xUser, xRequestedUser); + + // + #region Fill User with Models Value ... + // + // FirstName ... + xUser.FirstName = !model.FirstName.IsNullOrEmpty() ? + model.FirstName : + xUser.FirstName; + // + // LastName ... + xUser.LastName = !model.LastName.IsNullOrEmpty() ? + model.LastName : + xUser.LastName; + + // + // Email ... + xUser.Email = !model.Email.IsNullOrEmpty() ? + model.Email : + xUser.Email; + + // + // EmailConfirmed ... + if (model.EmailConfirmed.HasValue) + { + xUser.EmailConfirmed = model.EmailConfirmed.Value; + } + + // + // PhoneNumber ... + xUser.PhoneNumber = !model.PhoneNumber.IsNullOrEmpty() ? + model.PhoneNumber : + xUser.PhoneNumber; + + // + // PhoneNumberConfirmed ,,, + if (model.PhoneNumberConfirmed.HasValue) + { + xUser.PhoneNumberConfirmed = model.PhoneNumberConfirmed.Value; + } + + // + // DateOfBirth ... + if (model.DateOfBirth.HasValue) + { + xUser.DateOfBirth = model.DateOfBirth.Value; + } + + // + // CreationDate ... + if (model.CreationDate.HasValue) + { + xUser.CreationDate = model.CreationDate.Value; + } + + // + // LastLogin ... + if (model.LastLogin.HasValue) + { + xUser.LastLogin = model.LastLogin.Value; + } + + // + // Avatar ... + xUser.Avatar = !model.Avatar.IsNullOrEmpty() ? + model.Avatar : + xUser.Avatar; + + // + // Gender ... + if (model.Gender.HasValue) + { + xUser.Gender = model.Gender.Value; + } + + // + // IsEnable ... + if (model.IsEnable.HasValue) + { + xUser.IsEnable = model.IsEnable.Value; + } + + // + // IsBanned ... + if (model.IsBanned.HasValue) + { + xUser.IsBanned = model.IsBanned.Value; + } + + // + // Bio ... + xUser.Bio = model.Bio; + + // + // Cover Image ... + xUser.CoverImage = model.CoverImage; + + // + // Open To Search ... + xUser.OpenToSearch = model.OpenToSearch; + #endregion + + // + var updateResult = await UpdateUserAsync( + xUser, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + if (!updateResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + // Handle Roles ... + + // + // Remove User from All Roles ... + if (xUser.Roles.HasChild()) + { + // + var roleNames = await GetRoleNamesAsync( + xUser.Id, + checkIsBanned: false, + checkCanLoginPolicies: false + ); + + // + foreach (var role in roleNames) + { + // + var isRoleExists = await IsRoleExistsAsync(role); + if (isRoleExists) + { + // + var isInRole = await UserManager.IsInRoleAsync(xUser, role); + if (!isInRole) + { + // + var removeRoleResult = await UserManager.RemoveFromRoleAsync(xUser, role); + if (!removeRoleResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + } + } + } + } + + // + // Check Roles and Add to Exists ... + if (model.Roles.HasChild()) + { + // + // Check Exists Roles for Add ... + foreach (var role in model.Roles) + { + // + var isRoleExists = await IsRoleExistsAsync(role); + if (isRoleExists) + { + // + var isInRole = await UserManager.IsInRoleAsync(xUser, role); + if (!isInRole) + { + // + var addToRoleResult = await UserManager.AddToRoleAsync(xUser, role); + if (!addToRoleResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + } + } + } + } + + // + var result = await GetUserProfileAsync( + userSelectByParam, + requestedUserSelectByParam, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + return result; + } + #endregion + + // + #region Request For Actions ... + /// + /// Request Mobile Confirm + /// + /// specify destination language + /// an instance of XDevice + /// a user identifier + /// user's password + /// mobile number which need to confirm + /// an instance of XActionResponse + public async Task RequestConfirmMobile( + string lang, + XDevice device, + string userSelectByParam, + string password, + string mobileNumber = null + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + lang, + userSelectByParam, + password) + .AddNotNull(device) + .ValidateGroupAsync(); + + // + await ValidateDeviceForActions(device); + + // + // Prepare Request Context ... + var context = new XActionRequestContext + { + Lang = lang, + Device = device, + MobileNumber = mobileNumber + }; + + // + // Validate and Retrieve User ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: false, + checkIsBanned: false); + + // + context.UserName = user.UserName; + + // + await ValidateUserAndPassword( + userSelectByParam, + password, + false, + XException.ActionFailed.ToException()); + + // + ValidationProvider.MobileNumber(user.PhoneNumber); + if (!mobileNumber.IsNullOrEmpty() && + user.PhoneNumber == mobileNumber && + user.PhoneNumberConfirmed) + { + XException.MobileInUsed.Throw(); + } + + // + if (mobileNumber.IsNullOrEmpty()) + { + mobileNumber = user.PhoneNumber; + } + + // + // Remove All Prevoius Requests ... + await HandleRemoveExistsTokens(user.Id); + + // + // Prepare New Token Result , + // by Requesting an Action ... + var request = await ActionRequest( + lang, + device, + userSelectByParam, + XAction.RequestMobileVerificationCode, + context, + forceRenewToken: true + ); + + // + Logger.LogInformation($"ActionRequest: {request.ToJSON()}"); + + // + // Call Confirm Method of Registration ... + var result = await RequestMobileVerificationCode( + lang, + device, + request.Token, + mobileNumber, + checkMobileInUse: false); + + // + return result; + } + + /// + /// Request Email Confirma + /// + /// specify destination language + /// an instance of XDevice + /// a user identifier + /// user's password + /// email address which need to confirm + /// an instance of XActionResponse + public async Task RequestConfirmEmail( + string lang, + XDevice device, + string userSelectByParam, + string password, + string emailAddress = null + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + lang, + userSelectByParam, + password) + .AddNotNull(device) + .ValidateGroupAsync(); + + // + await ValidateDeviceForActions(device); + + // + // Prepare Request Context ... + var context = new XActionRequestContext + { + Lang = lang, + Device = device, + Email = emailAddress + }; + + // + // Validate and Retrieve User ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: false, + checkIsBanned: false); + + // + context.UserName = user.UserName; + + // + // Remove All Prevoius Requests ... + await HandleRemoveExistsTokens(user.Id); + + // + await ValidateUserAndPassword( + userSelectByParam, + password, + false, + XException.ActionFailed.ToException()); + + // + ValidationProvider.EmailAddress(user.Email); + if (!emailAddress.IsNullOrEmpty() && + user.Email == emailAddress && + user.EmailConfirmed) + { + XException.EmailInUsed.Throw(); + } + + // + if (emailAddress.IsNullOrEmpty()) + { + emailAddress = user.Email; + context.Email = user.Email; + } + + // + // Prepare New Token Result , + // by Requesting an Action ... + var request = await ActionRequest( + lang, + device, + userSelectByParam, + XAction.RequestEmailVerificationCode, + context, + forceRenewToken: true + ); + + // + // Call Confirm Method of Registration ... + var result = await RequestEmailVerificationCode( + lang, + device, + request.Token, + emailAddress, + checkEmailInUse: false); + + // + return result; + } + + /// + /// Request Reset Password + /// + /// specify destination language + /// an instance of XDevice + /// a user identifier + /// redirection url + /// an instance of XActionResponse + public async Task RequestResetPassword( + string lang, + XDevice device, + string userSelectByParam, + string returnUrl + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + lang, + userSelectByParam, + returnUrl) + .AddNotNull(device) + .AddUrl(returnUrl) + .ValidateGroupAsync(); + + // + await ValidateDeviceForActions(device); + + // + // Validate and Retrieve User ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: true, + checkIsBanned: true); + + // + // Prepare Request Context ... + var context = new XActionRequestContext + { + Lang = lang, + Device = device, + Email = user.Email, + UserName = user.UserName + }; + + // + // Remove All Prevoius Requests ... + await HandleCleanUserTokens(user); + + // + // Prepare New Token Result , + // by Requesting an Action ... + var result = await ActionRequest( + lang, + device, + user.Email, + XAction.RequestResetPassword, + context + ); + + // + // Prepare Message ... + var xMessage = GetResetPasswordMessage( + lang, + user.Email, + result.Token, + returnUrl); + + // + try + { + await MessageProvider.SendMailAsync(xMessage); + } + catch { } + + // + return result; + } + + /// + /// Request Registration Confrirm + /// + /// specify destination language + /// an instance of XDevice + /// a user identifier + /// user's password + /// redirection url + /// + public async Task RequestConfirmRegistration( + 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 and Retrieve it ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: false, + checkIsBanned: true, + ignoreDisabledUser: true); + + // + // Prepare Request Context ... + var context = new XActionRequestContext + { + Lang = lang, + Device = device, + Email = user.Email + }; + + // + await ValidateUserAndPassword( + userSelectByParam, + password, + false, + XException.ActionFailed.ToException()); + + // + // Remove All Prevoius Requests ... + await HandleRemoveExistsTokens(user.Id); + + // + // Prepare New Token Result , + // by Requesting an Action ... + var result = await ActionRequest( + lang, + device, + user.Email, + XAction.Finish, + context + ); + + // + // Prepare XMessage Instance ... + var xMessage = GetRegistrationConfirmMessage( + lang, + user.Email, + result.Token, + returnUrl); + + // + // Send Message ... + try + { + await MessageProvider.SendMailAsync(xMessage); + } + catch { } + } + #endregion + + // + #region Password Actions ... + /// + /// Change Password + /// + /// specify destination language + /// an instance of XDevice + /// a user identifier + /// user's password + /// user's new password + /// redirection url + /// + public async Task ChangePassword( + string lang, + XDevice device, + string userSelectByParam, + string password, + string newPassword, + string returnUrl + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + lang, + userSelectByParam, + password, + newPassword, + returnUrl) + .AddNotNull(device) + .ValidateGroupAsync(); + + // + // Password Unique ... + if (password == newPassword) + { + XException.PasswordsSame.Throw(); + } + + // + // Validate and Retrieve User ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: true, + checkIsBanned: true); + + // + await ValidateUserAndPassword( + userSelectByParam, + password); + + // + // Check Password Policies ... + await ValidatePasswordPolicies(user, newPassword); + + // + // Clean All User Tokens ... + await HandleCleanUserTokens(user); + + // + // Try to Change User Password ... + var passwordResetResult = await UserManager + .ChangePasswordAsync( + user, + password, + newPassword); + + // + // Check Result ... + if (!passwordResetResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + // Prepare Message ... + var xMessage = GetPasswordChangedMessage( + lang, + user.Email, + returnUrl, + throwException: false + ); + + // + // Send Message ... + try + { + await MessageProvider.SendMailAsync(xMessage); + } + catch { } + } + + /// + /// Reset User Password + /// + /// specify destination language + /// an instance of XDevice + /// a token which approved user action + /// user's new password + /// redirection url + /// + public async Task ResetPassword( + string lang, + XDevice device, + string actionToken, + string newPassword, + string returnUrl + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + lang, + actionToken, + newPassword, + returnUrl) + .AddNotNull(device) + .ValidateGroupAsync(); + + // + // Get Related Token to Registration Hash and Validate it, + // then Parse XActionRequest Instance from Token ... + var request = await ValidateAndParseActionHash(actionToken); + + // + // Get User Identifier ... + var userSelectByParam = GetUserSelectByParam(request); + + // + // Validate and Retrieve User ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: true, + checkIsBanned: true); + + // + // Check Password Policies ... + await ValidatePasswordPolicies(user, newPassword); + + // + // Clean All User Tokens ... + await HandleCleanUserTokens(user); + + // + // Try to Remove User Password ... + var removePasswordResult = await UserManager.RemovePasswordAsync(user); + if (!removePasswordResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + // try to Add New Password ... + var passwordResetResult = await UserManager.AddPasswordAsync(user, newPassword); + if (!passwordResetResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + // Prepare Message ... + var xMessage = GetPasswordChangedMessage( + lang, + user.Email, + returnUrl); + + // + // Send Message ... + try + { + await MessageProvider.SendMailAsync(xMessage); + } + catch { } + } + #endregion + + // + #region Profile Image Actions ... + /// + /// Add Profile Image to a User + /// + /// a user identifier + /// an specific File to upload, IFormFile + /// an instance of XUserProfileDto + public async Task AddAvatar( + string userSelectByParam, + IFormFile file + ) + { + // + 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 = xUser.Id, + Name = saveResult.FileName, + Path = saveResult.FilePath, + Thumb = saveResult.Thmbnail, + ThumbPath = saveResult.ThmbnailPath, + CreationDate = DateTime.UtcNow + }; + + // + xUser.Avatars.Add(xProfileImage); + var updateResult = await UpdateUserAsync( + xUser, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + + // + if (!updateResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + xUser.Avatar = xProfileImage.ThumbPath; + updateResult = await UpdateUserAsync( + xUser, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + + // + if (!updateResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + var result = await GetUserProfileAsync(userSelectByParam, userSelectByParam); + return result; + } + + /// + /// Add a Collection of Profile Images to a User + /// + /// a user identifier + /// a collection of Files to upload, IFormFileCollection + /// an instance of XUserProfileDto + public async Task AddAvatars( + string userSelectByParam, + IFormFileCollection files + ) + { + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + exception: XException.NotFound.ToException() + ); + + // + foreach (var file in files) + { + // + // Handle Saving File and Attach it to User ... + var saveResult = await StorageProvider.HandleProfileImageSave(file); + + // + var xProfileImage = new XProfileImage + { + UserId = xUser.Id, + Name = saveResult.FileName, + Path = saveResult.FilePath, + Thumb = saveResult.Thmbnail, + ThumbPath = saveResult.ThmbnailPath, + CreationDate = DateTime.UtcNow + }; + + // + xUser.Avatars.Add(xProfileImage); + } + + // + var updateResult = await UpdateUserAsync( + xUser, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + + // + if (!updateResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + var result = await GetUserProfileAsync(userSelectByParam, userSelectByParam); + return result; + } + + /// + /// Set Specific Profie Image as Current Profile Image + /// + /// a user identifier + /// an integer which reperesent AvatarId to set as current Avatar + /// an instance of XUserProfileDto + public async Task SetAvatar( + string userSelectByParam, + int id + ) + { + // + ValidationProvider.NotEmpty(userSelectByParam); + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + exception: XException.NotFound.ToException() + ); + + // + var xProfileImage = xUser.Avatars.FirstOrDefault(pf => pf.Id == id); + if (xProfileImage.IsNull()) + { + XException.NotFound.Throw(); + } + + // + xUser.Avatar = xProfileImage.ThumbPath; + var updateResult = await UpdateUserAsync( + xUser, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + + // + if (!updateResult.Succeeded) + { + XException.ActionFailed.Throw(); + } + + // + var result = await GetUserProfileAsync(userSelectByParam, userSelectByParam); + return result; + } + + /// + /// Remove a Collection of User's Profile Images + /// + /// a user identifier + /// a comma seperated list of avatarIds to remove + /// an instance of XUserProfileDto + public async Task RemoveAvatar( + string userSelectByParam, + ICollection ids + ) + { + // + ValidationProvider.NotEmpty(userSelectByParam); + + // + var xUser = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: false, + checkIsBanned: false, + ignoreDisabledUser: true, + exception: XException.NotFound.ToException() + ); + + // + var xProfileImages = xUser.Avatars + .Where(pf => ids.Contains(pf.Id)); + if (xProfileImages.HasChild()) + { + // + xUser.Avatars = xUser.Avatars + .Except(xProfileImages) + .ToList(); + + // + // Creat a List of Physical Files to Remove ... + var xFilePathsForRemove = xProfileImages.Select(pf => pf.Thumb).ToList(); + xFilePathsForRemove.AddRange(xProfileImages.Select(pf => pf.Name)); + + // + StorageProvider.DeleteFiles( + xFilePathsForRemove, + forceFileExists: false); + + // + var isCurrentProfileImage = xProfileImages.Any(pfi => pfi.ThumbPath == xUser.Avatar); + if (isCurrentProfileImage) + { + xUser.Avatar = ""; + } + + // + await UpdateUserAsync(xUser); + } + + // + var result = await GetUserProfileAsync(userSelectByParam, userSelectByParam); + return result; + } + #endregion + + // + #region Confirmations ... + /// + /// Confirm Registration + /// + /// specify destination language + /// an instance of XDevice + /// a token which approved user action + /// redirection url + /// + public async Task ConfirmRegistration( + string lang, + XDevice device, + string actionToken, + string returnUrl + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty( + lang, + actionToken, + returnUrl) + .AddNotNull(device) + .ValidateGroupAsync(); + + // + // Check Device Validation ... + await ValidateDeviceForActions(device); + + // + // Get Related Token to Registration Hash and Validate it, + // then Parse XActionRequest Instance from Token ... + var request = await ValidateAndParseActionHash(actionToken); + if (request.Action != XAction.Finish) + { + XException.ActionFailed.Throw(); + } + + // + // Extract UserSelectByParam from XActionRequest ... + var userSelectByParam = GetUserSelectByParam( + request, + forceNotNull: true); + + // + // Validate user and Retrieve it ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: false, + ignoreDisabledUser: true, + checkIsBanned: true); + + // + // Make User Enabled ... + var xMessage = GetRegistrationFinishedMessage( + lang, + user.Email, + returnUrl); + + // + user.IsEnable = true; + user.EmailConfirmed = true; + + // + await UpdateUserAsync( + user, + checkCanLoginPolicies: false, + checkIsBanned: false + ); + + // + // Remove Token ... + await RemoveTokenByHash(actionToken); + + // + // Remove All Prevoius Requests ... + await HandleRemoveExistsTokens(user.Id); + + // + await RemoveVerificationCodeRequestByDevice(device); + + // + // Send Mail ... + try + { + await MessageProvider.SendMailAsync(xMessage); + } + catch { } + } + + /// + /// Confirm Mobile Number + /// + /// specify destination language + /// an instance of XDevice + /// a token which approved user action + /// recieved verification code + /// an instance of XActionResponse + public async Task ConfirmMobileNumber( + string lang, + XDevice device, + string actionToken, + string verificationCode + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty(lang, actionToken, verificationCode) + .AddNotNull(device) + .ValidateGroupAsync(); + + // + // Check Device Validation ... + await ValidateDeviceForActions(device); + + // + // Get Related Token to Registration Hash and Validate it, + // then Parse XActionRequest Instance from Token ... + var request = await ValidateAndParseActionHash(actionToken); + ValidationProvider.MobileNumber(request.Context.MobileNumber); + + // + // Validate two Device Must Same ... + ValidateRequestAndGiveDevices(device, request.Context.Device); + + // + // Check XActionRequest Action Must be Request Mobile Validation ... + if (request.Action != XAction.RequestMobileVerificationCode) + { + XException.InvalidData.Throw(); + } + + // + // Extract UserSelectByParam from XActionRequest ... + var userSelectByParam = GetUserSelectByParam(request); + + // + // since device must Requested before, + // Retrieve XVerification instance by Verification Code from Db + // and Validate it ... + XVerificationRequest xVerificationRequest = await ValidateAndRetrieveVerificationRequest(verificationCode); + + // + // Check User Added To Db or Not ... + // var isUserExists = await IsUserExistsAsync(userSelectByParam); + 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); + + // + // Update Exists User's Mobile Confirmation ... + user.PhoneNumberConfirmed = true; + + // + // Save Changes to Db ... + await UpdateUserAsync(user, checkCanLoginPolicies: false, checkIsBanned: false); + + // + request.Context.UserId = user.Id; + } + + // + // Update Request Context MobileNumber Verification Code + // and it's Confirmation Status ... + request.Context.MobileVerificationCode = verificationCode; + request.Context.MobileVerified = true; + + // + // Prepare New Token Result , + // by Requesting an Action ... + var result = await ActionRequest( + lang, + device, + userSelectByParam, + XAction.ConfirmMobileNumber, + request.Context, + forceCheckUserExists: isAddedUser + ); + + // + // Remove Previous Token ... + await RemoveTokenByHash(actionToken); + + try + { + // + // Remove XVerificationRequest instance from Db ... + await RemoveVerificationRequest(verificationCode); + } + catch { } + + // + // Return Result ... + return result; + } + + /// + /// Confirm Email Address + /// + /// specify destination language + /// an instance of XDevice + /// a token which approved user action + /// recieved verification code + /// an instance of XActionResponse + public async Task ConfirmEmailAddress( + string lang, + XDevice device, + string actionToken, + string verificationCode + ) + { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder() + .AddNotEmpty(lang, actionToken, verificationCode) + .AddNotNull(device) + .ValidateGroupAsync(); + + // + // Check Device Validation ... + await ValidateDeviceForActions(device); + + // + // Get Related Token to Registration Hash and Validate it, + // then Parse XActionRequest Instance from Token ... + var request = await ValidateAndParseActionHash(actionToken); + ValidationProvider.EmailAddress(request.Context.Email); + + // + // Validate two Device Must Same ... + ValidateRequestAndGiveDevices(device, request.Context.Device); + + // + // Check XActionRequest Action Must be Request Mobile Validation ... + if (request.Action != XAction.RequestEmailVerificationCode) + { + XException.InvalidData.Throw(); + } + + // + // Extract UserSelectByParam from XActionRequest ... + var userSelectByParam = GetUserSelectByParam(request); + + // + // since device must Requested before, + // Retrieve XVerification instance by Verification Code from Db + // and Validate it ... + XVerificationRequest xVerificationRequest = await ValidateAndRetrieveVerificationRequest(verificationCode); + + // + // 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); + + // + // Update Exists User's Email Confirmation ... + user.EmailConfirmed = true; + + // + // Save Changes to Db ... + await UpdateUserAsync(user, checkCanLoginPolicies: false, checkIsBanned: false); + + // + request.Context.UserId = user.Id; + } + + // + // Update Request Context Email Address Verification Code + // and it's Confirmation Status ... + request.Context.EmailVerificationCode = verificationCode; + request.Context.EmailVerified = true; + + // + // Prepare New Token Result , + // by Requesting an Action ... + var result = await ActionRequest( + lang, + device, + userSelectByParam, + XAction.ConfirmEmailAddress, + request.Context, + forceCheckUserExists: isAddedUser + ); + + // + // Remove Previous Token ... + await RemoveTokenByHash(actionToken); + + // + // Remove XVerificationRequest instance from Db ... + await RemoveVerificationRequest(verificationCode); + + // + // Return Result ... + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Query.cs + +using System.Linq; +using System.Threading.Tasks; +using xCommons.Extensions; +using xDataService.Extensions; +using xExceptions.Constants; +using xIdentityModels.Extensions; +using xIds.Extensions; +using xModels.Dtos; + +namespace xIds.Providers +{ + public partial class XIdentityManager + { + /// + /// Retrieve Users as Query Model for Query Service ... + /// + /// + /// + /// + /// + /// + public async Task> 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 + { + Items = items + .Select(u => u.Id), + Page = query.Page, + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return await Task.Run(() => result); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Registration.cs + +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 ... + /// + /// Invite a User to Register on Dashboard + /// + /// specify destination language + /// an instance of XDevice + /// which email address is going to invite + /// return url for invitation user to redirect + /// an instance of XActionResponse + public async Task 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; + } + + /// + /// Recieve some Basic Informations and Start Registration Proccess + /// if they Valid + /// + /// Registration Proccess Starts with Invoking this Action + /// + /// specify destination language + /// an instance of XDevice + /// optional, if user invited, this is the invitation token + /// user's FirstName + /// user's LastName + /// user's dob date + /// user's Mobile Number + /// user's Email address + /// an instance of XActionResponse + public async Task 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; + } + + /// + /// Add User Account Info + /// + /// specify destination language + /// an instance of XDevice + /// a token which approved user action + /// user name + /// assigne password + /// an instance of XActionResponse + public async Task 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; + } + + /// + /// Attach Profile Image + /// + /// a token which approved user action + /// an instance of IFormFile for user's Avatar + /// an instance of XActionResponse + public async Task 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; + } + + /// + /// Finishing Registration + /// + /// specify destination language + /// an instance of XDevice + /// user identifier + /// assigne password + /// return url for invitation user to redirect + /// + 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Request.cs + +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 ... + /// + /// Request an Action + /// + /// specify destination language + /// an instance of XDevice + /// specified user's identifier + /// a member of XAction which represent Action Step + /// an instance of XActionRequestContext which provides required informations for specified step + /// specify checking context, default is false + /// specify check user exists, default is true + /// specify check user and device relation, default is false + /// specifies force renew Action Token if it's expired, default is false + /// an instance of XActionResponse + private async Task 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.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 { 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; + } + + /// + /// Request a Mobile Verification Code + /// + /// specify destination language + /// an instance of XDevice + /// a token which approved user action + /// the mobile number which is going to request a verification code + /// check mobile number is in use or not, default is true + /// an instance of XActionResponse + private async Task 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.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; + } + + /// + /// Request a Email Verification Code + /// + /// specify destination language + /// an instance of XDevice + /// a token which approved user action + /// the email address which is going to request a verification code + /// check email address is in use or not, default is true + /// an instance of XActionResponse + public async Task 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.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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Role.cs + +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 ... + /// + /// Check a Role exists or not + /// + /// role name + /// a boolean value + public async Task IsRoleExistsAsync( + string roleName + ) + { + // + if (roleName.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + return await RoleManager.RoleExistsAsync(roleName); + } + + /// + /// Create a new Role + /// + /// role name + /// an instance of IdentityResult + public async Task CreateRoleAsync( + string roleName + ) + { + // + var isExists = await RoleManager + .RoleExistsAsync(roleName); + if (isExists) + { + XException.Duplicate.Throw(); + } + + // + var result = await RoleManager + .CreateAsync(new IdentityRole(roleName)); + + // + return result; + } + + /// + /// Get a Role + /// + /// role name + /// an instance of IdentityRole + public async Task GetRoleAsync( + string roleName + ) + { + // + var isExists = await IsRoleExistsAsync(roleName); + if (!isExists) + { + XException.NotFound.Throw(); + } + + // + var result = await RoleManager + .FindByNameAsync(roleName); + + // + return result; + } + + /// + /// Remove a User From Role + /// + /// an instance of XUser + /// role name + /// an instance of IdentityResult + public async Task RemoveFromRoleAsync( + XUser user, + string roleName + ) + { + return await UserManager.RemoveFromRoleAsync(user, roleName); + } + + /// + /// Remove a User From Roles + /// + /// an instance of XUser + /// a collection of role names + /// an instance of IdentityResult + public async Task RemoveFromRolesAsync( + XUser user, + IEnumerable roleNames + ) + { + return await UserManager.RemoveFromRolesAsync(user, roleNames); + } + + /// + /// Assign a User to Specific Role + /// + /// an instance of XUser + /// role name + /// an instance of IdentityResult + public async Task 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; + } + + /// + /// Retrieve Role Name by it's ID + /// + /// role id + /// role name as string + public async Task GetRoleNameAsync( + string roleId + ) + { + // + // Validate Args ... + if (roleId.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + var role = await RoleManager + .FindByIdAsync(roleId); + + // + return role.Name.ToNormalString(); + } + + /// + /// Get a List Of User Roles + /// + /// user identifier + /// + /// + /// a collection of role names + public async Task> 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(); + foreach (var role in user.Roles) + { + // + var roleName = await GetRoleNameAsync(role.RoleId); + roleName = roleName.ToNormalString(); + + // + result.Add(roleName); + } + + // + return result; + } + + /// + /// Check a User is Adminr not + /// + /// user identifier + /// a boolean value + public async Task IsAdmin( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true, + checkCanLoginPolicies: true); + + // + var result = await IsAdmin(user); + + // + return result; + } + + /// + /// Check a User is Adminr not + /// + /// an instance of XUser + /// a boolean value + public async Task 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Search.cs + +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 ... + /// + /// Query User Profiles + /// + /// requested user's identifier + /// how to filter results based on XQuery structure + /// an instance of XQueryResult of XUserProfileDto + public async Task> 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 + { + Items = items, + Page = query.Page, + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Seed.cs + +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 ... + /// + /// Create Default Roles based on Identity Configuration + /// + /// + 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)); + } + } + } + + /// + /// Create a user Based on User Descriptor + /// + /// an instance of XIdentityUserDescriptor + /// + 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Token.cs + +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 ... + /// + /// Check a Token Entity Exists or not + /// + /// an instance of XDevice + /// specifies Action Step by a member of XAction + /// a boolean value + private async Task 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; + } + + /// + /// Check a Token Entity Exists or not + /// + /// specified user's identifier + /// specifies Action Step by a member of XAction + /// a boolean value + private async Task 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; + } + + /// + /// Check a Token Entity Exists or not + /// + /// represent specified action token + /// a boolean value + private async Task IsTokenExistsByToken( + string token + ) + { + // + var result = await DbContext.Tokens + .AnyAsync(xt => xt.Token == token); + + // + return result; + } + + /// + /// Check a Token Entity Exists or not + /// + /// represent specified action token hash + /// a boolean value + private async Task IsTokenExistsByHash( + string hash + ) + { + // + var result = await DbContext.Tokens + .AnyAsync(xt => xt.Hash == hash); + + // + return result; + } + + /// + /// Check a Token Entity Exists or not + /// + /// an instance of XToken + /// a boolean value + private async Task 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; + } + + /// + /// Check a Token Entity Exists or not + /// + /// specifies token id + /// a boolean value + private async Task IsTokenExistsById( + int id + ) + { + return await DbContext.Tokens.AnyAsync(t => t.Id == id); + } + + /// + /// Retrieve Token Entity + /// + /// an instance of XDevice + /// specifies Action Step by a member of XAction + /// an instance of XToken + private async Task 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; + } + + /// + /// Retrieve Token Entity + /// + /// specified user's identifier + /// specifies Action Step by a member of XAction + /// an instance of XToken + private async Task 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; + } + + /// + /// Retrieve a Token Entity + /// + /// represent specified action token + /// an instance of XToken + private async Task GetTokenByToken( + string token + ) + { + // + var isExists = await IsTokenExistsByToken(token); + if (!isExists) + { + return null; + } + + // + var result = await DbContext.Tokens + .FirstOrDefaultAsync(xt => xt.Token == token); + + // + return result; + } + + /// + /// Retrieve Related Hash to a Token + /// + /// represent specified action token + /// hash string + private async Task GetRelatedHashByToken( + string token + ) + { + // + var item = await GetTokenByToken(token); + if (item == null) + { + return null; + } + + // + return item.Hash; + } + + /// + /// Retrieve a Token Entity + /// + /// represent specified action token hash + /// an instance of XToken + private async Task 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; + } + + /// + /// Retrieve Related Token to a Hash string + /// + /// represent specified action token hash + /// token string + private async Task GetRelatedTokenByHash( + string hash + ) + { + // + var item = await GetTokenByHash(hash); + if (item == null) + { + return null; + } + + // + return item.Token; + } + + /// + /// Retrieve a Token Expiration Date + /// + /// represent specified action token + /// an instance of DateTime + private DateTime GetTokenExpirationDate( + string token + ) + { + // + ValidationProvider.NotEmpty(token); + + // + var result = IdentityHelper.GetTokenExpirationDate(token); + + // + return result; + } + + /// + /// Remove a Token Entity + /// + /// represent specified action token + /// + 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(); + } + + /// + /// Remove a Token Entity + /// + /// represent specified action token hash + /// + 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(); + } + + /// + /// Remove a Token Entity + /// + /// an instance of XDevice + /// + 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(); + } + } + + /// + /// add a token Entity + /// + /// an instance of XToken + /// an instance of XToken + private async Task 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; + } + + /// + /// Hash a Request Token + /// + /// an instance of XActionRequestToken + /// + /// + /// an instance of XToken + private async Task 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; + } + + /// + /// Generate Token Hash string + /// + /// represent specified action token + /// check token validation parameters, default is true + /// hash string + private string ToHash( + string token, + bool checkValidation = true + ) + { + // + ValidationProvider.NotEmpty(token); + + // + if (checkValidation) + { + ValidateToken(token); + } + + // + var result = token.ToMd5String().ToNormalString(); + + // + return result; + } + + /// + /// Generate Token string + /// + /// represent specified action token + /// token string + private string ToTokenString( + SecurityToken token + ) + { + // + ValidationProvider.NotNull(token, IdentityHelper); + + // + var result = IdentityHelper.ToTokenString(token); + + // + return result; + } + + /// + /// Generate Token string + /// + /// represent specified action token + /// token string + private string ToTokenString( + JwtSecurityToken token + ) + { + // + ValidationProvider.NotNull(token, IdentityHelper); + + // + var result = IdentityHelper.ToTokenString(token); + + // + return result; + } + + /// + /// Generate Security Token + /// + /// represent specified action token + /// an instance of SecurityToken + private SecurityToken ToSecurityToken( + string token + ) + { + // + ValidationProvider.NotEmpty(token); + + // + var result = IdentityHelper.ToSecurityToken(token); + + // + return result; + } + + /// + /// Generate Security Token + /// + /// an instance of XActionRequestToken + /// an instance of SecurityToken + private SecurityToken ToSecurityToken( + XActionRequestToken request + ) + { + // + ValidationProvider.NotNull(request); + + // + ValidateActionRequest(request); + + // + var result = IdentityHelper.ToSecurityToken(request); + if (result == null) + { + XException.InvalidToken.Throw(); + } + + // + return result; + } + + /// + /// Cleanup all exists tokens related to specified User + /// + /// specified user's identifier + /// + private async Task HandleRemoveExistsTokens( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + var xTokens = new List(); + var xTokenEnumerator = DbContext.Tokens.AsAsyncEnumerable(); + await + foreach (var xToken in xTokenEnumerator) + { + // + if (xToken.UserSelectByParam == userSelectByParam && + ObjectHelper.ToEnumerableValues().Contains(xToken.Type)) + { + xTokens.Add(xToken); + } + } + + // + if (xTokens.HasChild()) + { + DbContext.Tokens.RemoveRange(xTokens); + await DbContext.SaveChangesAsync(); + } + } + + /// + /// Cleanup all tokens related to specified User + /// + /// a collection of user identifiers + /// + private async Task HandleCleanUserTokens( + ICollection userSelectByParams + ) + { + // + // Validate Args ... + if (!userSelectByParams.HasChild()) + { + return; + } + + // + foreach (var userSelectByParam in userSelectByParams) + { + await HandleRemoveExistsTokens(userSelectByParam); + } + } + + /// + /// Cleanup all tokens related to specified User + /// + /// an instance of XUser + /// + private async Task HandleCleanUserTokens( + XUser user + ) + { + // + if (user == null) + { + return; + } + + // + var userSelectByParams = GenerateUserSelectByParams(user); + + // + await HandleCleanUserTokens(userSelectByParams); + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+User.cs + +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 ... + /// + /// Check a User Exists or not + /// + /// specified user's identifier + /// a boolean value + public async Task 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; + } + + /// + /// Check a User Selector is Available for Registration or Not + /// + /// specified user's identifier + /// a boolean value + public async Task 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; + } + + /// + /// Get a User object + /// + /// specified user's identifier + /// specifies returned object contains all Navigation Properties or not, default is false + /// an instance of XUser + public async Task 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; + } + + /// + /// Retrieve User Names based on UserIds + /// + /// a collection of user identifiers + /// a collection of Usernames + public async Task> GetUserNamesAsync( + IEnumerable 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; + } + + /// + /// get user ids and retrieve corresponding user names + /// + /// an instance of XUserNameIdRequest which represent required UserIds collection + /// a collection of XUserNameIdResponse instances + public async Task> 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; + } + + /// + /// Create a User + /// + /// an instance of XUser + /// user's password + /// check a user can log in in system or not, default is false + /// check user is banned or not, default is false + /// an instance of IdentityResult + public async Task 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.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; + } + + /// + /// Update a User Information + /// + /// an instance of XUser + /// check a user can log in in system or not, default is true + /// check user is banned or not, default is true + /// an instance of IdentityResult + public async Task 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; + } + + /// + /// Generate JWT Claims froma DashboardUser object + /// + /// an instance of XUser + /// check a user can log in in system or not, default is true + /// check user is banned or not, default is true + /// a collection of Claim instances + public async Task ToJwtClaims( + XUser user, + bool checkCanLoginPolicies = true, + bool checkIsBanned = true + ) + { + // + // Get New Instance of Claims ... + var claims = new List { + // + // 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(); + } + + /// + /// Check User and Password relation + /// + /// an instance of XUser + /// user's password + /// an instance of XDevice + /// specify destination language + /// specify account lockout on failure log in + /// specify log in force without checking XDevice and lang relation + /// an instance of SignInResult + public async Task 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: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+Validators.cs + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using xCommons.Extensions; +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 User Validators ... + /// + /// Validate User Name for Registration based on Policies + /// + /// provided user name + /// a boolean value + private bool ValidateUserName( + string userName + ) + { + // + // Check min/max Length ... + if (userName.IsNullOrEmpty() || + userName.Length < Configuration.Policy.User.MinLength || + userName.Length > Configuration.Policy.User.MaxLength) + { + return false; + } + + // + // Check Allowed UserName Characters ... + if (!UserManager.Options.User.AllowedUserNameCharacters.IsNullOrEmpty() && + userName.Any(c => !UserManager.Options.User.AllowedUserNameCharacters.Contains(c))) + { + return false; + } + + // + // Check invalid UserNames ... + if (Configuration.Policy.User.InvalidUserNames + .HasChild() && Configuration.Policy.User.InvalidUserNames + .Any(u => userName + .ToNormalString() + .Contains(u.ToNormalString()))) + { + return false; + } + + // + return true; + } + + /// + /// Validate a User Exists + /// + /// specifies user identifier + /// specify Excetion to thrown on failure + /// + private async Task ValidateUserExistsAsync( + string userSelectByParam, + Exception exception = null + ) + { + // + ValidationProvider + .NotEmpty(userSelectByParam); + + // + if (exception == null) + { + exception = XException.NotFound.ToException(); + } + + // + // Check User Exists ... + var isExists = await IsUserExistsAsync(userSelectByParam); + if (!isExists) + { + throw exception; + } + } + + /// + /// Validate a User Not Exists + /// + /// specifies user identifier + /// specify Excetion to thrown on failure + /// + private async Task ValidateUserNotExists( + string userSelectByParam, + Exception exception = null + ) + { + // + ValidationProvider + .NotEmpty(userSelectByParam); + + // + if (exception == null) + { + exception = XException.UserRegisteredBefore.ToException(); + } + + // + // Check User Exists ... + var isExists = await IsUserExistsAsync(userSelectByParam); + if (isExists) + { + throw exception; + } + } + + /// + /// Validate User Exists and Some Usefull Checks + /// + /// specifies user identifier + /// specifies returned object contains all Navigation Properties or not, default is false + /// check a user can log in in system or not, default is false + /// check user is banned or not, default is true + /// specify do not thrown Exception if user is Disabled, default is false + /// specify requested person is Admin role, default is false + /// specify Excetion to thrown on failure + /// an instance of XUser + public async Task ValidateUserExistsAndRetrieve( + string userSelectByParam, + bool containDetails = false, + bool checkCanLoginPolicies = false, + bool checkIsBanned = true, + bool ignoreDisabledUser = false, + bool forceAdmin = false, + Exception exception = null + ) + { + // + ValidationProvider + .NotEmpty(userSelectByParam); + ValidationProvider.NotNull( + Configuration, + Configuration.Policy, + Configuration.Policy.SignIn); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidData.ToException(); + } + + // + // Check User Exists ... + await ValidateUserExistsAsync(userSelectByParam); + + // + // Retrieve User ... + var result = await GetUserAsync(userSelectByParam, containDetails); + if (result == null) + { + throw exception; + } + + // + // Check Force Admin ... + if (forceAdmin) + { + // + var isAdmin = await IsAdmin(result); + if (!isAdmin) + { + XException.NotAuthorized.Throw(); + } + } + + // + // Check User not Bann ... + if (checkIsBanned && + result.IsBanned) + { + XException.UserBanned.Throw(); + } + + // + var requireConfirmedEmail = Configuration.Policy.SignIn.RequireConfirmedEmail; + var requireConfirmedPhoneNumber = Configuration.Policy.SignIn.RequireConfirmedPhoneNumber; + var requireEnabled = Configuration.Policy.SignIn.RequiredEnabled; + + // + var isEnabled = result.IsEnable; + var isMobileConfirmed = result.PhoneNumberConfirmed; + var isEmailConfirmed = result.EmailConfirmed; + + // + if (checkCanLoginPolicies) + { + // + // Check IsEnabled ... + if (!ignoreDisabledUser && + requireEnabled && + !isEnabled) + { + XException.UserDisabled.Throw(); + } + + // + // Check Email Confirmed ... + if (requireConfirmedEmail && + !isEmailConfirmed) + { + XException.EmailNotConfirmed.Throw(); + } + + // + // Check Mobile Confirmed ... + if (requireConfirmedPhoneNumber && + !isMobileConfirmed) + { + XException.MobileNotConfirmed.Throw(); + } + } + + // + // Return Result ... + return result; + } + + /// + /// Validate a Device for User Actions + /// + /// an instance of XDevice + /// + private async Task ValidateDeviceForActions( + XDevice device + ) + { + // + ValidationProvider + .NotNull(device); + + // + // Check is Device Banned or not ... + var isDeviceBanned = await IsDeviceBanned(device); + if (isDeviceBanned) + { + // + // Retrieve Banned Device ... + var xBannedDevice = await GetBannedDevice(device); + if (xBannedDevice == null) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Banned Device Time out Passed or not ... + var isDelayTimePassed = IsDelayTimePassed(xBannedDevice); + if (!isDelayTimePassed) + { + // + var passedTime = GetPassedTime(xBannedDevice); + + // + XException.DeviceBanned + .AddContentToException(passedTime.ToString()); + } + + // + // if Delay Passed of Banned Device + // the device must Removed ... + await BannedDeviceRemove(xBannedDevice); + } + } + + /// + /// Validate a DateOfBirth for Registration + /// + /// users dob date, an instance of DateTime + /// specify Excetion to thrown on failure + /// + private void ValidateDateOfBirth( + DateTime dateOfBirth, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(dateOfBirth); + + // + if (exception.IsNull()) + { + exception = XException.InvalidDate.ToException(); + } + + // + var currentDate = DateTime.UtcNow; + if (dateOfBirth >= currentDate) + { + throw exception; + } + + // + var zeroDate = (new DateTime(1, 1, 1)).ToUniversalTime(); + var minAge = Configuration.Policy.User.MinAgeForRegistration; + var maxAge = Configuration.Policy.User.MaxAgeForRegistration; + + // + var timeSpan = currentDate - dateOfBirth; + var age = (zeroDate + timeSpan).Year - 1; + + // + if (age > maxAge || age < minAge) + { + throw exception; + } + } + + /// + /// Validate a Given User Can Registere + /// + /// specifies user identifier + /// + private async Task ValidateCanRegister( + string userSelectByParam + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(userSelectByParam); + + // + var canRegister = await CanRegister(userSelectByParam); + if (!canRegister) + { + XException.Unavailable.Throw(); + } + } + + /// + /// Validate Password by Gicen Policies + /// + /// an instance of XUser + /// user's password + /// specify Excetion to thrown on failure + /// + private async Task ValidatePasswordPolicies( + XUser user, + string password, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(user); + ValidationProvider.NotEmpty(password); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidPassword.ToException(); + } + + // + // Check new Password Validation ... + var passwordValidator = new PasswordValidator(); + var checkPasswordResult = await passwordValidator.ValidateAsync(UserManager, user, password); + if (!checkPasswordResult.Succeeded) + { + throw exception; + } + } + + /// + /// Validate a User for Dangerous Actions + /// + /// an instance of XUser + /// specified which user request Action by an instance of XUser + /// + private async Task ValidateUserForDangerousAction( + XUser user, + XUser requester + ) + { + // + // Validate ARgs ... + ValidationProvider.NotNull(user, requester); + + // + var requesterRoleNames = await GetRoleNamesAsync(requester.Id); + + // + var isSame = user.Id == requester.Id; + var isRequesterAdmin = requesterRoleNames.Any(r => r.ToNormalString() == "admin"); + if (!isSame && !isRequesterAdmin) + { + XException.NotAllowed.Throw(); + } + } + #endregion + + // + #region Device Validators ... + /// + /// Validate a Device for Specified User + /// + /// specifies user identifier + /// an instance of XDevice + /// specify Excetion to thrown on failure + /// + private async Task ValidateUserAndDeviceRelation( + string userSelectByParam, + XDevice device, + Exception exception = null + ) + { + // + ValidationProvider.NotEmpty(userSelectByParam); + ValidationProvider.NotNull(device); + + // + if (exception == null) + { + exception = XException.InvalidDevice.ToException(); + } + + // + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: true); + + // + var result = await IsDeviceRelateDToUser( + userSelectByParam, + device + ); + if (!result) + { + throw exception; + } + } + #endregion + + // + #region Token Validators ... + /// + /// Validate a Token + /// + /// token string + /// specify Excetion to thrown on failure + private void ValidateToken( + string token, + Exception exception = null + ) + { + // + ValidationProvider.NotEmpty(token); + + // + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + var result = IdentityHelper.ValidateToken(token); + if (!result) + { + throw exception; + } + } + + /// + /// Validate a Token is Specified to a Device and User + /// + /// an instance of XDevice + /// specifies Action type by a member of XAction + /// specify Excetion to thrown on failure + /// an instance of XToken + private async Task ValidateAndRetieveTokenByDeviceAndType( + XDevice device, + XAction type, + Exception exception = null + ) + { + // + ValidationProvider.NotNull(device); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.NotFound.ToException(); + } + + // + var isExists = await IsTokenExistsByDeviceAndType(device, type); + if (!isExists) + { + throw exception; + } + + // + var item = await GetTokenByDeviceAndType(device, type); + if (item == null) + { + throw exception; + } + + // + return item; + } + + /// + /// Validate a Token By User and Specified Type + /// + /// specifies user identifier + /// specifies Action type by a member of XAction + /// specify Excetion to thrown on failure + /// an instance of XToken + private async Task ValidateAndRetieveTokenByUserAndType( + string userSelectByParam, + XAction type, + Exception exception = null + ) + { + // + ValidationProvider.NotEmpty(userSelectByParam); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidData.ToException(); + } + + // + var isExists = await IsTokenExistsByUserAndType(userSelectByParam, type); + if (!isExists) + { + throw exception; + } + + // + var item = await GetTokenByUserAndType(userSelectByParam, type); + if (item == null) + { + throw exception; + } + + // + return item; + } + + /// + /// Check Token Validation + /// + /// token string + /// a boolean value + private bool IsValidToken( + string token + ) + { + // + ValidationProvider.NotEmpty(token); + + // + var result = IdentityHelper.ValidateToken(token); + + // + return result; + } + + /// + /// Retrieve Action Request Based on Action Token + /// + /// token string + /// specify Excetion to thrown on failure + /// an instance of XActionRequestToken + private XActionRequestToken ValidateAndParseActionToken( + string token, + Exception exception = null + ) + { + // + ValidationProvider.NotEmpty(token); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + ValidateToken(token); + + // + var result = IdentityHelper.ParseActionRequestToken(token); + if (result == null) + { + throw exception; + } + + // + ValidateActionRequest(result); + + // + return result; + } + + /// + /// Parse Action Request from Token + /// + /// token string + /// an instance of XActionRequestToken + private XActionRequestToken ParseActionRequest( + string token + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(token); + + // + var result = IdentityHelper + .ParseActionRequestToken(token); + + // + return result; + } + + /// + /// Parse Action Hash by Corresponding Token + /// + /// token hash string + /// an instance of XActionRequestToken + private async Task ParseActionHash( + string hash + ) + { + // + ValidationProvider.NotEmpty(hash); + + // + var isHashExists = await IsTokenExistsByHash(hash); + if (!isHashExists) + { + return null; + } + + // + var xToken = await GetTokenByHash(hash); + if (xToken == null) + { + return null; + } + + // + var isValidToken = IsValidToken(xToken.Token); + if (!isValidToken) + { + return null; + } + + // + var result = ParseActionRequest(xToken.Token); + + // + return result; + } + + /// + /// Validate and Parse Action Hash + /// + /// token hash string + /// specifies chack token Validation Parameter, default is true + /// specifies check action Validations, default is true + /// specify Excetion to thrown on failure + /// an instance of XActionRequestToken + private async Task ValidateAndParseActionHash( + string hash, + bool forceTokenValidation = true, + bool forceActionValidation = true, + Exception exception = null + ) + { + // + ValidationProvider.NotEmpty(hash); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + var token = await ValidateAndRetrieveRelatedToken(hash, forceTokenValidation); + + // + var result = await ParseActionHash(hash); + if (result == null) + { + throw exception; + } + + // + if (forceActionValidation) + { + ValidateActionRequest(result); + } + + // + return result; + } + + /// + /// Validate Action Request + /// + /// an instance of XActionRequestToken + /// specify Excetion to thrown on failure + private void ValidateActionRequest( + XActionRequestToken request, + Exception exception = null + ) + { + // + ValidationProvider + .NotNull(request); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidData.ToException(); + } + + // + var isValid = IsValidActionRequest(request); + if (!isValid) + { + throw exception; + } + } + + /// + /// Check Action Request Validation + /// + /// an instance of XActionRequestToken + /// a boolean value + private bool IsValidActionRequest( + XActionRequestToken request + ) + { + // + // Validate Args ... + if (request == null || + Configuration.IdentitySecretKey.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + var result = request.Validate(Configuration.IdentitySecretKey); + + // + return result; + } + + /// + /// Check Token and Act Based on it + /// + /// token string + /// specify Excetion to thrown on Issue failure + /// specify Excetion to thrown on failure + /// + private async Task ValidateAndHandleToken( + string token, + bool issueExcepion = false, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(token); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + var isValidToken = IsValidToken(token); + + // + if (!isValidToken) + { + // + await RemoveTokenByToken(token); + + // + if (issueExcepion) + { + throw exception; + } + } + } + + /// + /// Validate a Hash Exists or not + /// + /// token hash string + /// specify Excetion to thrown on failure + /// + private async Task ValidateHashExists( + string hash, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(hash); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + var isExists = await IsTokenExistsByHash(hash); + if (!isExists) + { + throw exception; + } + } + + /// + /// Validate a Token Exists + /// + /// token string + /// specify Excetion to thrown on failure + /// + private async Task ValidateTokenExists( + string token, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(token); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + var isExists = await IsTokenExistsByToken(token); + if (!isExists) + { + throw exception; + } + } + + /// + /// Validate a Hash and Retrieve it's Corresponding Token + /// + /// token hash string + /// specifies chack token Validation Parameter, default is true + /// specify Excetion to thrown on Issue failure + /// specify Excetion to thrown on failure + /// token string + private async Task ValidateAndRetrieveRelatedToken( + string hash, + bool forceTokenValidation = true, + bool issueExcepion = false, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(hash); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + await ValidateHashExists(hash); + + // + var token = await GetRelatedTokenByHash(hash); + if (token.IsNullOrEmpty()) + { + throw exception; + } + + // + if (forceTokenValidation) + { + await ValidateAndHandleToken(token, issueExcepion); + } + + // + return token; + } + + /// + /// Validate a Token and Retrieve it's Corresponding Hash + /// + /// token string + /// specifies chack token Validation Parameter, default is true + /// specify Excetion to thrown on failure + /// hash string + private async Task ValidateAndRetrieveRelatedHash( + string token, + bool forceTokenValidation = true, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty(token); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidToken.ToException(); + } + + // + if (forceTokenValidation) + { + await ValidateAndHandleToken(token); + } + + // + await ValidateTokenExists(token); + + // + var hash = await GetRelatedHashByToken(token); + if (hash.IsNullOrEmpty()) + { + throw exception; + } + + // + return hash; + } + + /// + /// Validate Invitation Hash + /// + /// registration invitation hash string + /// thrown Exception if user Exists, default is true + /// + private async Task ValidateInvitationHash( + string invitationHash, + bool forceUserExists = true + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(Configuration); + + // + if (Configuration.RegistrationJustWithInvite && + invitationHash.IsNullOrEmpty()) + { + XException.NotAllowed.Throw(); + } + + // + // Validate Invitation Token if it isn't null ... + if (!invitationHash.IsNullOrEmpty()) + { + // + await ValidateHashExists(invitationHash); + + // + // Token Validation also done in this task ... + var relatedToken = await ValidateAndRetrieveRelatedToken(invitationHash); + + // + var inviteAction = ValidateAndParseActionToken(relatedToken); + ValidationProvider.NotNull(inviteAction); + + // + var inviteUerSelectByParam = GetUserSelectByParam(inviteAction); + var inviteUserSelectBy = GetUserSelectByType(inviteUerSelectByParam); + if (inviteUserSelectBy != XUserSelectBy.Email) + { + XException.InvalidToken.Throw(); + } + + // + if (forceUserExists) + { + // + await ValidateUserNotExists( + inviteUerSelectByParam, + XException.EmailInUsed.ToException()); + } + } + } + #endregion + + // + #region Profile Validators ... + /// + /// Validate a Request is Corresponding to Specific Device + /// + /// an instance of XDevice + /// specify requested device by an instance of XDevice + /// specify Excetion to thrown on failure + private void ValidateRequestAndGiveDevices( + XDevice device, + XDevice requestDevice, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(device, requestDevice); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidDevice.ToException(); + } + + // + var isDevicesSame = device.IsSameAs(requestDevice); + + // + if (!isDevicesSame) + { + throw exception; + } + } + + /// + /// Check Validation of a User SelectBy Param and Password + /// + /// specifies user identifier + /// user's password + /// check a user can log in in system or not, default is true + /// specify Excetion to thrown on failure + /// + private async Task ValidateUserAndPassword( + string userSelectByParam, + string password, + bool checkCanLoginPolicies = true, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotEmpty( + userSelectByParam, + password + ); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.LoginFailed.ToException(); + } + + // + // Retrieve User and Validate User Exists ... + var user = await ValidateUserExistsAndRetrieve( + userSelectByParam, + containDetails: false, + checkCanLoginPolicies: checkCanLoginPolicies, + ignoreDisabledUser: false, + checkIsBanned: true, + exception: exception); + + // + // Check Given Password Follows Policies ... + await ValidatePasswordPolicies(user, password, exception); + + // + // Check Password for User ... + var checkPasswordResult = await UserManager.CheckPasswordAsync(user, password); + if (!checkPasswordResult) + { + throw exception; + } + } + #endregion + + // + #region Friendship Validators ... + /// + /// Validate Friendship Requested Before or not + /// + /// specifies a user to check as source by an instance of XUser + /// specifies a user to check as dest by an instance of XUser + private void ValidateRequestBefore( + XUser source, + XUser dest + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(source, dest); + + // + var isRequested = IsFollowRequested(source, dest); + if (!isRequested) + { + XException.NotFound.Throw(); + } + } + + /// + /// Validate a Friendship Not Requested Before + /// + /// specifies a user to check as source by an instance of XUser + /// specifies a user to check as dest by an instance of XUser + private void ValidateNotRequestBefore( + XUser source, + XUser dest + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(source, dest); + + // + var isRequested = IsFollowRequested(source, dest); + if (isRequested) + { + XException.Duplicate.Throw(); + } + } + + /// + /// Validate User Can Send Following Request to Dest User + /// + /// specifies a user to check as dest by an instance of XUser + /// specifies a user to check as source by an instance of XUser + private void ValidateFollowingRequest( + XUser accepterUser, + XUser requesterUser + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(accepterUser, requesterUser); + + // + var isRequest = IsFollowRequested(requesterUser, accepterUser, XFriendshipState.Pending); + if (!isRequest) + { + XException.NotFound.Throw(); + } + } + + /// + /// Validate a User can Accept a Following Request or Not + /// + /// specifies a user to check as dest by an instance of XUser + /// specifies a user to check as source by an instance of XUser + private void ValidateFollowingRequestForAccept( + XUser accepterUser, + XUser requesterUser + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(accepterUser, requesterUser); + + // + var isRequestPending = IsFollowRequested(requesterUser, accepterUser, XFriendshipState.Pending); + var isRequestRejected = IsFollowRequested(requesterUser, accepterUser, XFriendshipState.Rejected); + if (!isRequestPending && + !isRequestRejected) + { + XException.NotFound.Throw(); + } + } + + /// + /// Validate a Request is not Passed for Same Users + /// + /// specifies a user to check as source by an instance of XUser + /// specifies a user to check as dest by an instance of XUser + private void ValidateNotSameUsers( + XUser source, + XUser dest + ) + { + // + // Validate ARgs ... + ValidationProvider.NotNull(source, dest); + + // + var isSame = source.Id == dest.Id; + if (isSame) + { + XException.ActionFailed.Throw(); + } + } + + /// + /// Check a user is in Followers of another + /// + /// specifies a user to check as source by an instance of XUser + /// specifies a user to check as dest by an instance of XUser + private void ValidateIsFollowers( + XUser source, + XUser dest + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(source, dest); + + // + ValidateNotSameUsers(source, dest); + ValidateRequestBefore(dest, source); + + // + var isFollower = IsFollowRequested(dest, source, XFriendshipState.Accepted) || + IsFollowRequested(dest, source, XFriendshipState.Blocked); + if (!isFollower) + { + XException.NotFound.Throw(); + } + } + + /// + /// Check a user is in Followings of another + /// + /// specifies a user to check as source by an instance of XUser + /// specifies a user to check as dest by an instance of XUser + private void ValidateIsFollowings( + XUser source, + XUser dest + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(source, dest); + + // + ValidateNotSameUsers(source, dest); + ValidateRequestBefore(source, dest); + + // + var isFollowing = IsFollowRequested(source, dest, XFriendshipState.Accepted); + if (!isFollowing) + { + XException.NotFound.Throw(); + } + } + + /// + /// Validate a User Blocked another + /// + /// specifies a user to check as source by an instance of XUser + /// specifies a user to check as dest by an instance of XUser + private void ValidateIsBlock( + XUser source, + XUser dest + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(source, dest); + + // + ValidateNotSameUsers(source, dest); + ValidateRequestBefore(dest, source); + + // + var isFollower = IsFollowRequested(dest, source, XFriendshipState.Blocked); + if (!isFollower) + { + XException.NotFound.Throw(); + } + } + #endregion + + // + #region XActionRequest Validators ... + /// + /// Validate a Device for Request + /// + /// an instance of XDevice + /// specify requested device by an instance of XDevice + /// specify Excetion to thrown on failure + private void ValidateRequestAndGivenDevices( + XDevice device, + XDevice requestDevice, + Exception exception = null + ) + { + // + // Validate Args ... + ValidationProvider.NotNull(device, requestDevice); + + // + // Prepare Default Exception ... + if (exception == null) + { + exception = XException.InvalidDevice.ToException(); + } + + // + var isDevicesSame = device.IsSameAs(requestDevice); + + // + if (!isDevicesSame) + { + throw exception; + } + } + #endregion + + // + #region XVerification Request ... + /// + /// Validate Verification Request Exists by Device + /// + /// an instance of XDevice + /// specify Excetion to thrown on failure + /// + private async Task ValidateVerificationRequestExists( + XDevice device, + Exception exception = null + ) + { + // + ValidationProvider.NotNull(device); + + // + if (exception == null) + { + exception = XException.InvalidDevice.ToException(); + } + + // + var isExists = await IsDeviceRequestedForVerificationCodeBefor(device); + if (!isExists) + { + throw exception; + } + } + + /// + /// Validate Verification Request Exists by VerificationCode + /// + /// Confirm Verification Code + /// specify Excetion to thrown on failure + /// + private async Task ValidateVerificationRequestExists( + string verificationCode, + Exception exception = null + ) + { + // + ValidationProvider.NotEmpty(verificationCode); + + // + if (exception == null) + { + exception = XException.InvalidVerificationCode.ToException(); + } + + // + var isExists = await IsVerificationCodeRequested(verificationCode); + if (!isExists) + { + throw exception; + } + } + + /// + /// Validate and Retrive Verification Request for Given Device + /// + /// an instance of XDevice + /// specify Excetion to thrown on failure + /// an instance of XVerificationRequest + private async Task ValidateAndRetrieveVerificationRequest( + XDevice device, + Exception exception = null + ) + { + // + ValidationProvider.NotNull(device); + + // + if (exception == null) + { + exception = XException.InvalidDevice.ToException(); + } + + // + await ValidateVerificationRequestExists(device); + + // + var result = await GetVerificationRequest(device); + if (result == null) + { + throw exception; + } + + // + return result; + } + + /// + /// Validate and Retrieve Verification Request for Given VerificationCode + /// + /// Confirm Verification Code + /// specify Excetion to thrown on failure + /// an instance of XVerificationRequest + private async Task ValidateAndRetrieveVerificationRequest( + string verificationCode, + Exception exception = null + ) + { + // + ValidationProvider.NotEmpty(verificationCode); + + // + if (exception == null) + { + exception = XException.InvalidVerificationCode.ToException(); + } + + // + await ValidateVerificationRequestExists(verificationCode); + + // + var result = await GetVerificationRequest(verificationCode); + if (result == null) + { + throw exception; + } + + // + return result; + } + + /// + /// Handle Prepare Verification Request + /// + /// Confirm Verification Code + /// an instance of XActionRequestToken + /// an instance of XVerificationRequest + private async Task HandleVerificationRequestPreparation( + string verificationCode, + XActionRequestToken request + ) + { + // + ValidationProvider + .NotEmpty(verificationCode); + + // + // Check Action Request Validation ... + ValidateActionRequest(request); + + // + var device = request.Context.Device; + + // + // Check is Device Requested Before or not ... + XVerificationRequest xVerificationRequest = null; + var isDeviceRequested = await IsDeviceRequestedForVerificationCodeBefor(device); + if (!isDeviceRequested) + { + // + xVerificationRequest = new XVerificationRequest + { + VerificationCode = verificationCode, + NumberOfTries = 1, + LastTryOn = DateTime.UtcNow, + Device = device, + Request = request + }; + + // + await AddVerificationRequest(xVerificationRequest); + } + else + { + // + // Get Verification Request ... + xVerificationRequest = await ValidateAndRetrieveVerificationRequest(device); + } + + // + return xVerificationRequest; + } + #endregion + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Providers\XIdentityManager\XIdentityManager+VerificationCode.cs + +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 ... + /// + /// Check a Device Requeste for Verification Code before or not + /// + /// an instance of XDevice + /// a boolean value + private async Task 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; + } + + /// + /// Check a Verification Code Requested before or not + /// + /// Confirm Verification Code + /// a boolean value + private async Task 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; + } + + /// + /// Retrieve Verification Request + /// + /// an instance of XDevice + /// an instance of XVerificationRequest + private async Task 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; + } + + /// + /// Retrieve Verification Request + /// + /// Confirm Verification Code + /// an instance of XVerificationRequest + private async Task 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; + } + + /// + /// Remove Exists Verification Code Request + /// + /// an instance of XDevice + /// save changes on DbContext, default is true + /// + 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(); + } + } + + /// + /// Remove Exists Verification Code Request + /// + /// Confirm Verification Code + /// save changes on DbContext, default is true + /// + 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(); + } + } + + /// + /// Add Verification Code Request + /// + /// an instance of XVerificationRequest + /// save changes on DbContext, default is true + /// an instance of XVerificationRequest + private async Task 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 + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Stores\XPersistedGrantStore.cs + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.EntityFramework.DbContexts; +using IdentityServer4.EntityFramework.Mappers; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Microsoft.EntityFrameworkCore; + +namespace xIds.Stores +{ + public class XPersistedGrantStore : IPersistedGrantStore + { + private readonly PersistedGrantDbContext dbContext; + + public XPersistedGrantStore(PersistedGrantDbContext dbContext) + { + this.dbContext = dbContext; + } + + public async Task> GetAllAsync(string subjectId) + { + // + var result = (await dbContext.PersistedGrants + .Where(g => g.SubjectId == subjectId) + .AsNoTracking() + .Select(res => res.ToModel()) + .ToListAsync()).AsEnumerable(); + + // + return result; + } + + public async Task GetAsync(string key) + { + // + var result = await dbContext.PersistedGrants + .FirstOrDefaultAsync(g => g.Key == key); + + // + return result.ToModel(); + } + + public async Task RemoveAllAsync(string subjectId, string clientId) + { + // + var items = await dbContext.PersistedGrants + .Where(g => + g.SubjectId == subjectId && + g.ClientId == clientId) + .ToListAsync(); + + // + dbContext.PersistedGrants.RemoveRange(items); + + // + await dbContext.SaveChangesAsync(); + } + + public async Task RemoveAllAsync(string subjectId, string clientId, string type) + { + // + var items = await dbContext.PersistedGrants + .Where(g => + g.SubjectId == subjectId && + g.ClientId == clientId && + g.Type == type) + .ToListAsync(); + + // + dbContext.PersistedGrants.RemoveRange(items); + + // + await dbContext.SaveChangesAsync(); + } + + public async Task RemoveAsync(string key) + { + // + var items = await dbContext.PersistedGrants + .Where(g => g.Key == key) + .ToListAsync(); + + // + dbContext.PersistedGrants.RemoveRange(items); + + // + await dbContext.SaveChangesAsync(); + } + + public async Task IsExistsAsync(string key) + { + return await this.dbContext.PersistedGrants.AnyAsync(g => g.Key == key); + } + + public async Task IsExistsAsync(PersistedGrant grant) + { + return await IsExistsAsync(grant.Key); + } + + public async Task AddOrUpdateAsync(PersistedGrant grant) + { + // + var isExists = await IsExistsAsync(grant); + if (isExists) + { + await UpdateAsync(grant); + } + else + { + await AddAsync(grant); + } + } + + public async Task AddAsync(PersistedGrant grant) + { + // + var item = grant.ToEntity(); + await dbContext.PersistedGrants.AddAsync(item); + await dbContext.SaveChangesAsync(); + } + + public async Task UpdateAsync(PersistedGrant grant) + { + // + var item = grant.ToEntity(); + var entity = dbContext.Attach(item); + entity.State = EntityState.Modified; + await dbContext.SaveChangesAsync(); + } + + public async Task StoreAsync(PersistedGrant grant) + { + await AddOrUpdateAsync(grant); + } + + // + // TODO: Complete this ... + public Task> GetAllAsync(PersistedGrantFilter filter) + { + throw new System.NotImplementedException(); + } + + // + // TODO: Complete this ... + public Task RemoveAllAsync(PersistedGrantFilter filter) + { + throw new System.NotImplementedException(); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xIds\Validators\XResourceOwnerPasswordValidator.cs + +using System; +using System.Threading.Tasks; +using IdentityServer4.Events; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Validation; +using Microsoft.Extensions.Logging; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityModels.Navigations; +using xIds.Interfaces; +using static IdentityModel.OidcConstants; + +namespace xIds.Validators +{ + public class XResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator + { + private readonly IXIdentityManager identityManager; + private readonly IEventService events; + private readonly ILogger logger; + + public XResourceOwnerPasswordValidator( + IXIdentityManager identityProvider, + IEventService events, + ILogger logger + ) + { + this.identityManager = identityProvider; + this.events = events; + this.logger = logger; + } + + /// + /// Validates the resource owner password credential + /// by providing UserName/Email or PhoneNumber + /// + /// The context. + /// + public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) + { + // + var clientId = context.Request?.Client?.ClientId; + var xUser = await identityManager.GetUserAsync(context.UserName, true); + + // + // Check Force Data ... + var isForced = true; + var isForcedStr = context.Request?.Raw["force"]; + if (!isForcedStr.IsNullOrEmpty()) + { + isForced = isForcedStr.FromJSON(); + } + + // + XDevice device = null; + var language = context.Request?.Raw["language"]; + var deviceStr = context.Request?.Raw["device"]; + if (deviceStr.IsNullOrEmpty() && isForced) + { + // + logger.LogError($"Device not Found ..."); + + // + await events + .RaiseAsync( + new UserLoginFailureEvent( + xUser.UserName, + XException.InvalidDevice.ToXError().Message, + false, + clientId + )); + return; + } + device = isForced ? deviceStr.FromJSON() : null; + + // + XException exception; + if (!xUser.IsNull()) + { + // + var result = await identityManager + .CheckPasswordSignInAsync( + xUser, + context.Password, + device, + language, + true, + isForced + ); + if (result.Succeeded) + { + // + logger.LogInformation("Credentials validated for username: {username}", xUser.UserName); + + // + // Update User Last Login ... + xUser.LastLogin = DateTime.UtcNow; + await identityManager.UpdateUserAsync(xUser); + + // + await events.RaiseAsync( + new UserLoginSuccessEvent( + xUser.UserName, xUser.Id, xUser.UserName, false, clientId + )); + + // + context.Result = new GrantValidationResult(xUser.Id, AuthenticationMethods.Password); + return; + } + else if (result.IsLockedOut) + { + // + logger.LogInformation("Authentication failed for username: {username}, reason: locked out", xUser.UserName); + + // + exception = XException.AccountLockedOut; + await events + .RaiseAsync( + new UserLoginFailureEvent( + xUser.UserName, + exception.ToXError().Message, + false, + clientId + )); + } + else if (result.IsNotAllowed) + { + // + logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", xUser.UserName); + + // + exception = XException.NotAllowed; + await events + .RaiseAsync( + new UserLoginFailureEvent( + xUser.UserName, + exception.ToXError().Message, + false, + clientId + )); + } + else + { + // + logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", xUser.UserName); + + // + exception = XException.LoginFailed; + await events + .RaiseAsync( + new UserLoginFailureEvent( + xUser.UserName, + exception.ToXError().Message, + false, + clientId + )); + + // + + } + } + else + { + // + logger.LogInformation("No user found matching username: {username}", xUser.UserName); + + // + exception = XException.InvalidUserName; + await events + .RaiseAsync( + new UserLoginFailureEvent( + xUser.UserName, + exception.ToXError().Message, + false, + clientId + )); + } + + // + Exception ecs = exception.ToException(); + if (exception == XException.AccountLockedOut && xUser.LockoutEnd.HasValue) + { + // + var passedTime = xUser.LockoutEnd.Value.UtcDateTime; + ecs = XException.AccountLockedOut + .AddContentToException(passedTime.ToString()); + } + + // + context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, ecs.Message); + } + } +} diff --git a/Documents/Resources/Json/Application.md b/Documents/Resources/Json/Application.md index f5ee354..ff67400 100644 --- a/Documents/Resources/Json/Application.md +++ b/Documents/Resources/Json/Application.md @@ -52,7 +52,37 @@ Update: ## ApiKey -## Update +### Create + +Model: + +```json +{ + "rateLimit": 0, + "expirationMinutes": 0, + "scopes": [ + "string" + ], + "allowedIPs": [ + "string" + ] +} +``` + +Sample: + +```json +{ + "rateLimit": 50, + "expirationMinutes": 40, + "scopes": [ + "manage" + ], + "allowedIPs": [] +} +``` + +### Update Model: diff --git a/Modules/xCommons b/Modules/xCommons index 788283f..69f238f 160000 --- a/Modules/xCommons +++ b/Modules/xCommons @@ -1 +1 @@ -Subproject commit 788283f3a8905932a91f6e8ce85a70e6ca072b2e +Subproject commit 69f238f5b1ff1cbef39e38522b8a2f54edae3fba diff --git a/Modules/xIdentityModels b/Modules/xIdentityModels index 565d7fb..bcb6d7a 160000 --- a/Modules/xIdentityModels +++ b/Modules/xIdentityModels @@ -1 +1 @@ -Subproject commit 565d7fbaffa19542948d08c5377d65eecbf79279 +Subproject commit bcb6d7a1351a5626d1bc0055dd43496e242079b6 diff --git a/xIds b/xIds index c4e430a..5085fc5 160000 --- a/xIds +++ b/xIds @@ -1 +1 @@ -Subproject commit c4e430a98f7b065853809c1674d60122e3c1399d +Subproject commit 5085fc54a4849bbf8976772b5311029a6260d3c5