From a8e257b8553c4185bc4cf6780ac68b5b187b2427 Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Thu, 25 Jan 2024 04:44:02 +0330 Subject: [PATCH] Initial Commit ... --- .gitignore | 8 + .../XIdentityServiceConfiguration.cs | 15 + Configuration/XProxyConfiguration.cs | 81 ++ Constants/ConfigurationNodeNames.cs | 6 + Constants/XApiAccountEndpoint.cs | 197 +++++ Constants/XAuthenticationScheme.cs | 9 + Constants/XParam.cs | 56 ++ Constants/XProxyConstants.cs | 7 + Constants/XResponseContentTypes.cs | 7 + Constants/XTokenType.cs | 12 + Controllers/XIBaseController.cs | 211 +++++ Controllers/XIBaseEntityController.cs | 486 ++++++++++ DI/XDIHelperExtension.cs | 179 ++++ .../AuthorizationPolicyBuilderExtensions.cs | 32 + Extensions/HttpContextExtensions.cs | 145 +++ Extensions/XTokenExtensions.cs | 370 ++++++++ Extensions/XUserClaimsInfoDtoExtensions.cs | 55 ++ Helpers/.gitkeep | 0 Interfaces/IXIdentityProvider.cs | 283 ++++++ Interfaces/IXProxyHelper.cs | 67 ++ Interfaces/IXSecurityProvider.cs | 10 + Interfaces/IXTokenProvider.cs | 15 + Middlewares/XIdentityTokenRefresher.cs | 182 ++++ Middlewares/XProxyMiddleware.cs | 827 ++++++++++++++++++ Partial/XIdentityProvider+Admin.cs | 129 +++ Partial/XIdentityProvider+Authentication.cs | 317 +++++++ Partial/XIdentityProvider+Friendship.cs | 803 +++++++++++++++++ Partial/XIdentityProvider+Profile.cs | 496 +++++++++++ Partial/XIdentityProvider+Registration.cs | 653 ++++++++++++++ Providers/XIdentityProvider.cs | 406 +++++++++ Providers/XInMemoryTokenProvider.cs | 151 ++++ Providers/XProxyHelper.cs | 215 +++++ README.md | 22 + Security/XSecurityProvider.cs | 147 ++++ nuget.config | 7 + xIdentityService.csproj | 39 + 36 files changed, 6645 insertions(+) create mode 100644 .gitignore create mode 100644 Configuration/XIdentityServiceConfiguration.cs create mode 100644 Configuration/XProxyConfiguration.cs create mode 100644 Constants/ConfigurationNodeNames.cs create mode 100644 Constants/XApiAccountEndpoint.cs create mode 100644 Constants/XAuthenticationScheme.cs create mode 100644 Constants/XParam.cs create mode 100644 Constants/XProxyConstants.cs create mode 100644 Constants/XResponseContentTypes.cs create mode 100644 Constants/XTokenType.cs create mode 100644 Controllers/XIBaseController.cs create mode 100644 Controllers/XIBaseEntityController.cs create mode 100644 DI/XDIHelperExtension.cs create mode 100644 Extensions/AuthorizationPolicyBuilderExtensions.cs create mode 100644 Extensions/HttpContextExtensions.cs create mode 100644 Extensions/XTokenExtensions.cs create mode 100644 Extensions/XUserClaimsInfoDtoExtensions.cs create mode 100644 Helpers/.gitkeep create mode 100644 Interfaces/IXIdentityProvider.cs create mode 100644 Interfaces/IXProxyHelper.cs create mode 100644 Interfaces/IXSecurityProvider.cs create mode 100644 Interfaces/IXTokenProvider.cs create mode 100644 Middlewares/XIdentityTokenRefresher.cs create mode 100644 Middlewares/XProxyMiddleware.cs create mode 100644 Partial/XIdentityProvider+Admin.cs create mode 100644 Partial/XIdentityProvider+Authentication.cs create mode 100644 Partial/XIdentityProvider+Friendship.cs create mode 100644 Partial/XIdentityProvider+Profile.cs create mode 100644 Partial/XIdentityProvider+Registration.cs create mode 100644 Providers/XIdentityProvider.cs create mode 100644 Providers/XInMemoryTokenProvider.cs create mode 100644 Providers/XProxyHelper.cs create mode 100644 README.md create mode 100644 Security/XSecurityProvider.cs create mode 100644 nuget.config create mode 100644 xIdentityService.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bd9d0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# +# DotNet ... +bin +obj + +# +# Natural Docs ... +Documentation/* diff --git a/Configuration/XIdentityServiceConfiguration.cs b/Configuration/XIdentityServiceConfiguration.cs new file mode 100644 index 0000000..f364f60 --- /dev/null +++ b/Configuration/XIdentityServiceConfiguration.cs @@ -0,0 +1,15 @@ +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; + } +} \ No newline at end of file diff --git a/Configuration/XProxyConfiguration.cs b/Configuration/XProxyConfiguration.cs new file mode 100644 index 0000000..b202fad --- /dev/null +++ b/Configuration/XProxyConfiguration.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using xModels.Base; + +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; } + } +} \ No newline at end of file diff --git a/Constants/ConfigurationNodeNames.cs b/Constants/ConfigurationNodeNames.cs new file mode 100644 index 0000000..0a3b4ac --- /dev/null +++ b/Constants/ConfigurationNodeNames.cs @@ -0,0 +1,6 @@ +namespace xIdentityService.Constants { + public partial struct ConfigurationNodeNames { + public const string XPROXY_CONFIGURATION_NODE_NAME = "ProxyConfiguration"; + public const string IDENTITY_SERVICE_NODE_NAME = "IdentityServiceConfiguration"; + } +} \ No newline at end of file diff --git a/Constants/XApiAccountEndpoint.cs b/Constants/XApiAccountEndpoint.cs new file mode 100644 index 0000000..b16fb0e --- /dev/null +++ b/Constants/XApiAccountEndpoint.cs @@ -0,0 +1,197 @@ +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 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}/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/{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/FollowersList")] + FollowersList, + + [StringValue ("Account/Friendship/{destUser}/FriendshipInfo")] + FriendshipInfo, + #endregion + + // + #region Admin ... + [StringValue ("Account/Ban")] + Ban, + + [StringValue ("Account/UnBan")] + UnBan, + + [StringValue ("Account/IsBanned/{userSelectByParam}")] + IsBanned, + #endregion + } +} \ No newline at end of file diff --git a/Constants/XAuthenticationScheme.cs b/Constants/XAuthenticationScheme.cs new file mode 100644 index 0000000..afa9971 --- /dev/null +++ b/Constants/XAuthenticationScheme.cs @@ -0,0 +1,9 @@ +using xExceptions.Attributes; + +namespace xIdentityService.Constants { + public enum XAuthenticationScheme { + [StringValue ("token")] + XToken, [StringValue ("introspection")] + XIntrospection, + } +} \ No newline at end of file diff --git a/Constants/XParam.cs b/Constants/XParam.cs new file mode 100644 index 0000000..722d23d --- /dev/null +++ b/Constants/XParam.cs @@ -0,0 +1,56 @@ +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 ("scope")] + XScope, + } +} \ No newline at end of file diff --git a/Constants/XProxyConstants.cs b/Constants/XProxyConstants.cs new file mode 100644 index 0000000..a44f072 --- /dev/null +++ b/Constants/XProxyConstants.cs @@ -0,0 +1,7 @@ +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"; + } +} \ No newline at end of file diff --git a/Constants/XResponseContentTypes.cs b/Constants/XResponseContentTypes.cs new file mode 100644 index 0000000..958078e --- /dev/null +++ b/Constants/XResponseContentTypes.cs @@ -0,0 +1,7 @@ +namespace xIdentityService.Constants { + public struct XResponseContentTypes { + public const string HTML = "text/html"; + public const string JSON = "application/json"; + public const string JavaScript = "text/javascript"; + } +} \ No newline at end of file diff --git a/Constants/XTokenType.cs b/Constants/XTokenType.cs new file mode 100644 index 0000000..4cf4e72 --- /dev/null +++ b/Constants/XTokenType.cs @@ -0,0 +1,12 @@ +namespace xIdentityService.Constants { + public enum XTokenType { + // + // Summary: + // Self-contained Json Web Token + Jwt, + // + // Summary: + // Reference token + Reference + } +} \ No newline at end of file diff --git a/Controllers/XIBaseController.cs b/Controllers/XIBaseController.cs new file mode 100644 index 0000000..2efad9e --- /dev/null +++ b/Controllers/XIBaseController.cs @@ -0,0 +1,211 @@ +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 xIdentityService.Interfaces; + +namespace xIdentityService.Controllers { + public abstract class XIBaseController : XBaseController { + private readonly IXIdentityProvider identityProvider; + + public XIBaseController ( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider + ) : base ( + logger, + appConfiguration, + validationProvider + ) { + this.identityProvider = identityProvider; + } + + // + #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 = 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 + } +} \ No newline at end of file diff --git a/Controllers/XIBaseEntityController.cs b/Controllers/XIBaseEntityController.cs new file mode 100644 index 0000000..5ee8551 --- /dev/null +++ b/Controllers/XIBaseEntityController.cs @@ -0,0 +1,486 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using xCommons.Attributes; +using xCommons.Configurations; +using xCommons.Extensions; +using xCommons.Providers; +using xDataService.Interfaces; +using xExceptions.Constants; +using xIdentityService.Interfaces; +using xModels.Base; +using xModels.Dtos; +using xModels.Interfaces; + +namespace xIdentityService.Controllers { + [Authorize] + [RequireXPowered (false)] + public abstract class XIBaseEntityController : XIBaseController, IXEntityControllerActions + where TEntity : XBaseEntity { + public readonly IXBaseRepository repository; + + protected XIBaseEntityController ( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider, + IXBaseRepository repository + ) : base ( + logger, + appConfiguration, + identityProvider, + validationProvider + ) { + // + this.repository = repository; + } + + // + #region Interface Implementations ... + // + #region Retrieve ... + [HttpGet ("{id}")] + public virtual async Task> Get ( + [FromRoute] TKey id, [FromQuery] bool ignoreSoftDeleteds = true, [FromQuery] bool containsDetail = false + ) { + // + try { + // + // Validate Args ... + ValidationProvider.NotNull (id); + + // + // Get Result ... + var result = await repository + .GetAsync ( + id, + ignoreSoftDeleteds : ignoreSoftDeleteds, + containsDetail : containsDetail + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpGet] + public virtual async Task>> GetAll ( + [FromQuery] bool ignoreSoftDeleteds = true, [FromQuery] bool containsDetail = false + ) { + // + try { + // + // Get Result ... + var result = await repository + .GetAllAsync ( + ignoreSoftDeleteds: ignoreSoftDeleteds, + containsDetail: containsDetail + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpGet ("FindOne/{query}")] + public virtual async Task> FindOne ( + [FromRoute] string query, [FromQuery] bool ignoreSoftDeleteds = true, [FromQuery] bool containsDetail = false + ) { + // + try { + // + // Validate Args ... + ValidationProvider.NotEmpty (query); + + // + // Get Result ... + var result = await repository + .FindOneAsync (t => + t.PropValuesContains (query), + ignoreSoftDeleteds : ignoreSoftDeleteds, + containsDetail : containsDetail + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpGet ("FindMany/{query}")] + public virtual async Task>> FindMany ( + [FromRoute] string query, [FromQuery] bool ignoreSoftDeleteds = true, [FromQuery] bool containsDetail = false + ) { + // + try { + // + // Validate Args ... + ValidationProvider.NotEmpty (query); + + // + // Get Result ... + var result = await repository + .FindManyAsync (t => + t.PropValuesContains (query), + ignoreSoftDeleteds : ignoreSoftDeleteds, + containsDetail : containsDetail + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpGet ("Query")] + public virtual async Task>> Query ( + [FromQuery] XQuery query, [FromQuery] bool ignoreSoftDeleteds = true + ) { + // + try { + // + // Validate Args ... + ValidationProvider.NotNull (query); + + // + // Get Result ... + var result = await repository + .QueryAsync ( + query, + ignoreSoftDeleteds : ignoreSoftDeleteds + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + // + // TODO: Fix this ... + // [HttpGet ("RequestPage")] + // public virtual async Task>> RequestPage ( + // [FromQuery] XPageRequest request, [FromQuery] bool ignoreSoftDeleteds = true, [FromQuery] bool containsDetail = false + // ) { + // // + // try { + // // + // // Validate Args ... + // ValidationProvider.NotNull (request); + + // // + // // Get Result ... + // var result = await repository + // .RequestPageAsync ( + // request, + // ignoreSoftDeleteds : ignoreSoftDeleteds, + // containsDetail : containsDetail + // ); + + // // + // return Ok (result + // .ToDynamicObject ()); + // } catch (Exception ex) { + // // + // var result = GetExceptionActionResult (ex); + // return result; + // } + // } + #endregion + + // + #region Add ... + [HttpPost] + public virtual async Task> Add ( + [FromBody] TEntity item + ) { + // + try { + // + // Validate Args ... + if (!ModelState.IsValid) { + XException.InvalidArgs.Throw (); + } + ValidationProvider.NotNull (item); + + // + // Get Result ... + var result = await repository + .AddAsync ( + item, + saveChanges : true + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpPost ("AddOrUpdate")] + public virtual async Task> AddOrUpdate ( + [FromBody] TEntity item + ) { + // + try { + // + // Validate Args ... + if (!ModelState.IsValid) { + XException.InvalidArgs.Throw (); + } + ValidationProvider.NotNull (item); + + // + // Get Result ... + var result = await repository + .AddOrUpdateAsync ( + item, + saveChanges : true + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpPost ("AddMany")] + public virtual async Task AddMany ( + [FromBody] XBaseRangeRequest request + ) { + // + try { + // + // Validate Args ... + if (!ModelState.IsValid) { + XException.InvalidArgs.Throw (); + } + await ValidationProvider + .GroupValidationBuilder () + .AddNotNull (request) + .AddNotZeroChilds (request.Items) + .ValidateGroupAsync (); + + // + // Get Result ... + await repository + .AddRangeAsync ( + request.Items, + saveChanges : true + ); + + // + return Ok (); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + #endregion + + // + #region Update ... + [HttpPut ("{id}")] + public virtual async Task> Update ( + [FromRoute] TKey id, [FromBody] TEntity item + ) { + // + try { + // + // Validate Args ... + if (!ModelState.IsValid) { + XException.InvalidArgs.Throw (); + } + await ValidationProvider + .GroupValidationBuilder () + .AddNotNull (id, item) + .ValidateGroupAsync (); + + // + // Get Result ... + var result = await repository + .UpdateAsync ( + id, + item, + saveChanges : true + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpPost ("UpdateMany")] + public virtual async Task> UpdateMany ( + [FromBody] XBaseRangeRequest request + ) { + // + try { + // + // Validate Args ... + if (!ModelState.IsValid) { + XException.InvalidArgs.Throw (); + } + await ValidationProvider + .GroupValidationBuilder () + .AddNotNull (request) + .AddNotZeroChilds (request.Items) + .ValidateGroupAsync (); + + // + // Get Result ... + var result = await repository + .UpdateRangeAsync ( + request.Items, + saveChanges : true + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + #endregion + + // + #region Exists ... + [HttpGet ("{id}/IsExists")] + public virtual async Task> IsExists ( + [FromRoute] TKey id, [FromQuery] bool ignoreSoftDeleteds = true + ) { + // + try { + // + // Validate Args ... + ValidationProvider.NotNull (id); + + // + // Get Result ... + var result = await repository + .IsExistsAsync ( + id, + ignoreSoftDeleteds : ignoreSoftDeleteds + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + #endregion + + // + #region Remove ... + [HttpDelete ("{id}")] + public virtual async Task> Remove ( + [FromRoute] TKey id, + bool softDelete = true + ) { + // + try { + // + // Validate Args ... + ValidationProvider.NotNull (id); + + // + // Get Result ... + var result = await repository + .RemoveAsync ( + id, + saveChanges : true, + softDelete : softDelete + ); + + // + return Ok (result + .ToDynamicObject ()); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + + [HttpPost ("RemoveMany")] + public virtual async Task RemoveMany ( + [FromBody] XBaseRangeRequest request, + bool softDelete = true + ) { + // + try { + // + // Validate Args ... + await ValidationProvider + .GroupValidationBuilder () + .AddNotNull (request) + .AddNotZeroChilds (request.Items) + .ValidateGroupAsync (); + + // + // Get Result ... + await repository + .RemoveRangeAsync ( + request.Items, + saveChanges : true, + softDelete : softDelete + ); + + // + return Ok (); + } catch (Exception ex) { + // + var result = GetExceptionActionResult (ex); + return result; + } + } + #endregion + #endregion + } +} \ No newline at end of file diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs new file mode 100644 index 0000000..64391f8 --- /dev/null +++ b/DI/XDIHelperExtension.cs @@ -0,0 +1,179 @@ +using System; +using IdentityModel.AspNetCore.OAuth2Introspection; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using xCommons.Authorization; +using xCommons.Extensions; +using xIdentityService.Configuration; +using xIdentityService.Constants; +using xIdentityService.Interfaces; +using xIdentityService.Providers; +using xIdentityService.Security; + +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 + } +} \ No newline at end of file diff --git a/Extensions/AuthorizationPolicyBuilderExtensions.cs b/Extensions/AuthorizationPolicyBuilderExtensions.cs new file mode 100644 index 0000000..f1d3ebc --- /dev/null +++ b/Extensions/AuthorizationPolicyBuilderExtensions.cs @@ -0,0 +1,32 @@ +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 (); + } + } +} \ No newline at end of file diff --git a/Extensions/HttpContextExtensions.cs b/Extensions/HttpContextExtensions.cs new file mode 100644 index 0000000..e58aa04 --- /dev/null +++ b/Extensions/HttpContextExtensions.cs @@ -0,0 +1,145 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using xCommons.Constants; +using xCommons.Extensions; +using xIdentityModels.Models; +using xIdentityService.Constants; + +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 + }; + } + } +} \ No newline at end of file diff --git a/Extensions/XTokenExtensions.cs b/Extensions/XTokenExtensions.cs new file mode 100644 index 0000000..3199a9c --- /dev/null +++ b/Extensions/XTokenExtensions.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using xCommons.Constants; +using xCommons.Extensions; +using xExceptions.Constants; +using xExceptions.Models; +using xIdentityModels.Models; +using xIdentityService.Interfaces; + +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; + } +} \ No newline at end of file diff --git a/Extensions/XUserClaimsInfoDtoExtensions.cs b/Extensions/XUserClaimsInfoDtoExtensions.cs new file mode 100644 index 0000000..535d5b0 --- /dev/null +++ b/Extensions/XUserClaimsInfoDtoExtensions.cs @@ -0,0 +1,55 @@ +using xCommons.Extensions; +using xIdentityModels.Constants; +using xIdentityModels.Models; + +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; + } + } +} \ No newline at end of file diff --git a/Helpers/.gitkeep b/Helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Interfaces/IXIdentityProvider.cs b/Interfaces/IXIdentityProvider.cs new file mode 100644 index 0000000..6aa5c1b --- /dev/null +++ b/Interfaces/IXIdentityProvider.cs @@ -0,0 +1,283 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Http; +using RestSharp; +using xHttpService.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +using xIdentityModels.Navigations; +using xModels.Dtos; + +namespace xIdentityService.Interfaces { + public partial interface IXIdentityProvider { + // + string BaseUrl { get; } + string RevisionSecretKey { 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 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 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 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> GetFollowingList (XTokenResponse tokens); + + Task> GetFollowersList (XTokenResponse tokens); + + Task GetFriendshipInfo ( + XTokenResponse tokens, + string destUser + ); + #endregion + + // + #region Admin ... + Task> Ban ( + XTokenResponse tokens, + XUserNameIdRequest model + ); + + Task> UnBan ( + XTokenResponse tokens, + XUserNameIdRequest model + ); + + Task IsBanned ( + XTokenResponse tokens, + string userSelectByParam + ); + #endregion + } +} \ No newline at end of file diff --git a/Interfaces/IXProxyHelper.cs b/Interfaces/IXProxyHelper.cs new file mode 100644 index 0000000..b5606bf --- /dev/null +++ b/Interfaces/IXProxyHelper.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +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); + } +} \ No newline at end of file diff --git a/Interfaces/IXSecurityProvider.cs b/Interfaces/IXSecurityProvider.cs new file mode 100644 index 0000000..090c342 --- /dev/null +++ b/Interfaces/IXSecurityProvider.cs @@ -0,0 +1,10 @@ +namespace xIdentityService.Interfaces +{ + public partial interface IXSecurityProvider + { + string Encrypt(string plainText); + string Decrypt(string cipherText); + string EncryptFromBytes(byte[] textBytes); + string DecryptFromBytes(byte[] cipherBytes); + } +} \ No newline at end of file diff --git a/Interfaces/IXTokenProvider.cs b/Interfaces/IXTokenProvider.cs new file mode 100644 index 0000000..67cdb13 --- /dev/null +++ b/Interfaces/IXTokenProvider.cs @@ -0,0 +1,15 @@ +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); + } +} \ No newline at end of file diff --git a/Middlewares/XIdentityTokenRefresher.cs b/Middlewares/XIdentityTokenRefresher.cs new file mode 100644 index 0000000..5e69992 --- /dev/null +++ b/Middlewares/XIdentityTokenRefresher.cs @@ -0,0 +1,182 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using xCommons.Constants; +using xCommons.Extensions; +using xIdentityModels.Extensions; +using xIdentityService.Interfaces; + +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 (); + } + } +} \ No newline at end of file diff --git a/Middlewares/XProxyMiddleware.cs b/Middlewares/XProxyMiddleware.cs new file mode 100644 index 0000000..be38524 --- /dev/null +++ b/Middlewares/XProxyMiddleware.cs @@ -0,0 +1,827 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using IdentityModel; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityService.Configuration; +using xIdentityService.Constants; +using xIdentityService.Interfaces; +using xIdentityService.Providers; +using xModels.Base; + +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 (); + } + } +} \ No newline at end of file diff --git a/Partial/XIdentityProvider+Admin.cs b/Partial/XIdentityProvider+Admin.cs new file mode 100644 index 0000000..1e63d8c --- /dev/null +++ b/Partial/XIdentityProvider+Admin.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using xCommons.Extensions; +using xHttpService.Constants; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +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 + } +} \ No newline at end of file diff --git a/Partial/XIdentityProvider+Authentication.cs b/Partial/XIdentityProvider+Authentication.cs new file mode 100644 index 0000000..40d2140 --- /dev/null +++ b/Partial/XIdentityProvider+Authentication.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using IdentityModel.Client; +using xCommons.Extensions; +using xExceptions.Constants; +using xHttpService.Constants; +using xIdentityModels.Extensions; +using xIdentityModels.Models; +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 + } +} \ No newline at end of file diff --git a/Partial/XIdentityProvider+Friendship.cs b/Partial/XIdentityProvider+Friendship.cs new file mode 100644 index 0000000..9a0ddc0 --- /dev/null +++ b/Partial/XIdentityProvider+Friendship.cs @@ -0,0 +1,803 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using xCommons.Extensions; +using xHttpService.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +using xIdentityModels.Navigations; +using xIdentityService.Constants; + +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; + } + + /// + /// 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; + } + + /// + /// 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; + } + + /// + /// 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; + } + + /// + /// 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; + } + #endregion + } +} \ No newline at end of file diff --git a/Partial/XIdentityProvider+Profile.cs b/Partial/XIdentityProvider+Profile.cs new file mode 100644 index 0000000..5f00b88 --- /dev/null +++ b/Partial/XIdentityProvider+Profile.cs @@ -0,0 +1,496 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using xCommons.Extensions; +using xHttpService.Constants; +using xHttpService.Extensions; +using xIdentityModels.Dtos; +using xIdentityModels.Models; +using xIdentityModels.Navigations; +using xIdentityService.Constants; +using xModels.Dtos; + +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 + } +} \ No newline at end of file diff --git a/Partial/XIdentityProvider+Registration.cs b/Partial/XIdentityProvider+Registration.cs new file mode 100644 index 0000000..418cfcd --- /dev/null +++ b/Partial/XIdentityProvider+Registration.cs @@ -0,0 +1,653 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using xCommons.Extensions; +using xExceptions.Constants; +using xHttpService.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Extensions; +using xIdentityModels.Models; +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 + } +} \ No newline at end of file diff --git a/Providers/XIdentityProvider.cs b/Providers/XIdentityProvider.cs new file mode 100644 index 0000000..d5c0db9 --- /dev/null +++ b/Providers/XIdentityProvider.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using RestSharp; +using RestSharp.Authenticators; +using xCommons.Constants; +using xCommons.Extensions; +using xCommons.Providers; +using xExceptions.Constants; +using xHttpService.Constants; +using xIdentityModels.Constants; +using xIdentityModels.Extensions; +using xIdentityModels.Models; +using xIdentityService.Configuration; +using xIdentityService.Constants; +using xIdentityService.Interfaces; + +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 string BaseUrl { get; } + public string RevisionSecretKey { get; } + + public bool ReadResponseAsString { get; set; } + public ILogger Logger { get; } + + public XIdentityProvider ( + IXTokenProvider tokenProvider, + IHostingEnvironment environment, + ILogger logger, + XIdentityServiceConfiguration identityConfiguration, + XValidationProvider validationProvider + ) { + // + BaseUrl = identityConfiguration.Authority; + RevisionSecretKey = identityConfiguration.XRevisionSecretKey; + + // + this.environment = environment; + Logger = logger; + + // + 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, + IDictionary @params = null, + bool addXPoweredValue = true, + IDictionary headers = 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 ()); + } + } + + // + // 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; + } + + /// + /// 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 + } +} \ No newline at end of file diff --git a/Providers/XInMemoryTokenProvider.cs b/Providers/XInMemoryTokenProvider.cs new file mode 100644 index 0000000..acfd816 --- /dev/null +++ b/Providers/XInMemoryTokenProvider.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using xCommons.Extensions; +using xIdentityModels.Models; +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; + } + } + } +} \ No newline at end of file diff --git a/Providers/XProxyHelper.cs b/Providers/XProxyHelper.cs new file mode 100644 index 0000000..6ac60b2 --- /dev/null +++ b/Providers/XProxyHelper.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Web; +using xCommons.Extensions; +using xExceptions.Constants; +using xIdentityService.Configuration; +using xIdentityService.Interfaces; + +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 + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ad3aef --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# xIdentityService + +it is a Part of xDashboard on SaherElm IT Center which provides: + +- Any Requirement to Connect XIdentityServer. +- etc. + +this module has following dependencies : + +- xCommons +- xModels +- xHttpService + +for configure and use this Module refer to DI.XDIHelperExtension.cs file. + +## Maintainer + +Hadi Khazaee asl + +[https://www.saherelm.ir](https://www.saherelm.ir) + +[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com) diff --git a/Security/XSecurityProvider.cs b/Security/XSecurityProvider.cs new file mode 100644 index 0000000..6afc1ba --- /dev/null +++ b/Security/XSecurityProvider.cs @@ -0,0 +1,147 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using xCommons.Extensions; +using xCommons.Helpers; +using xCommons.Providers; +using xIdentityService.Configuration; +using xIdentityService.Interfaces; + +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; + } + } +} \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..87b6eb0 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/xIdentityService.csproj b/xIdentityService.csproj new file mode 100644 index 0000000..7bcbee1 --- /dev/null +++ b/xIdentityService.csproj @@ -0,0 +1,39 @@ + + + + + netstandard2.0 + xDashboard.xIdentityService + 1.0.0 + Hadi Khazaee Asl + SaherElm IT Center + + provide an interface to connect xDashboard IdentityServer and do OAuth Actions to xDashboard + project. + + + + icon.png + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file