using System; using System.Linq; using xIds.Helpers; using System.Threading; using xCommons.Extensions; using xIdentityModels.Dtos; using xExceptions.Constants; using System.Threading.Tasks; using xIdentityModels.Models; using System.Collections.Generic; using xIdentityModels.Extensions; using Microsoft.EntityFrameworkCore; namespace xIds.Providers { public partial class XApplicationProvider { // #region Custom Actions ... /// /// Generate a New ApiKey and Add It to DB ... /// /// /// /// /// /// /// /// /// public async Task CreateApiKey( int applicationId, TimeSpan? expiration = null, IEnumerable scopes = null, IEnumerable allowedIPs = null, int? rateLimit = null, XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ) { // // Validate ... if ( userInfo.IsNullOrDefault() || !applicationId.IsValidIntId() ) { XException.InvalidArgs.Throw(); } // // Check Application Exists ... var application = await GetApplicationAsync( id: applicationId, userInfo: userInfo, includeBuilder: null, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (application.IsNullOrDefault()) { XException.NotFound.Throw(); } // // Validate Owner ... if (!IsOwned(application, userInfo)) { XException.NotAllowed.Throw(); } // // Normalizing ... if (!rateLimit.HasValue) { rateLimit = apiKeyConfiguration.DefaultRateLimit; } if (!expiration.HasValue) { expiration = TimeSpan.FromMinutes(apiKeyConfiguration.DefaultExpirationMinutes); } else if (expiration.Value > TimeSpan.FromMinutes(apiKeyConfiguration.MaxExpirationMinutes)) { expiration = TimeSpan.FromMinutes(apiKeyConfiguration.MaxExpirationMinutes); } // // Generate New Key ... (string plainKey, string keyHash, string prefix) = XApiKeyHelper.Generate(apiKeyConfiguration.ApiKeyPrefix); var expireAt = DateTime.UtcNow.Add(expiration.Value); // var _allowedScopes = scopes.ToJSON(); var _allowedIPs = allowedIPs.ToListString(); // // Instance Dto Item ... var result = new XApiKeyDto { RevokedAt = null, KeyHash = keyHash, LastUsedAt = null, KeyPrefix = prefix, ExpiresAt = expireAt, RevokedBy = string.Empty, AllowedIPs = _allowedIPs, CreatedOn = DateTime.UtcNow, ApplicationId = applicationId, AllowedScopes = _allowedScopes, RateLimitPerMinute = rateLimit.Value }; // // Add Item to Database ... result = await AddApiKeyAsync( item: result, userInfo: userInfo, cancellationToken: cancellationToken ); // return result; } /// /// Revoke Specified Api Key ... /// /// /// /// /// public async Task RevokeApiKey( Guid apiKeyId, XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ) { // // Validate ... if ( apiKeyId.IsDefaultGuid() || userInfo.IsNullOrDefault()) { XException.InvalidArgs.Throw(); } // // Retrieve ApiKey ... var item = await GetApiKeyAsync( id: apiKeyId, userInfo: userInfo, includeBuilder: null, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (item.IsNullOrDefault()) { XException.NotFound.Throw(); } // // Permission Checking ... var application = await GetApplicationAsync( userInfo: userInfo, includeBuilder: null, id: item.ApplicationId, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (application.IsNullOrDefault()) { XException.NotFound.Throw(); } if (!IsOwned(application, userInfo)) { XException.NotAllowed.Throw(); } // // Update Revoke Info ... item.RevokedAt = DateTime.UtcNow; item.RevokedBy = userInfo.UserId; // item = await UpdateApiKeyAsync( id: item.Id, item: item, userInfo: userInfo, cancellationToken: cancellationToken ); // var result = !item.IsNullOrDefault(); return result; } /// /// Renew Specified Api Key ... /// /// /// /// /// /// public async Task RotateApiKey( Guid apiKeyId, TimeSpan? newExpiration = null, XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ) { // // Validate ... if ( apiKeyId.IsDefaultGuid() || userInfo.IsNullOrDefault() ) { XException.InvalidArgs.Throw(); } // // Retrieve Item ... var result = await GetApiKeyAsync( id: apiKeyId, userInfo: userInfo, includeBuilder: null, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (result.IsNullOrDefault()) { XException.NotFound.Throw(); } // // Expired Date ... DateTime? expiredDate = null; if (!newExpiration.HasValue) { // newExpiration = TimeSpan .FromMinutes(apiKeyConfiguration.DefaultExpirationMinutes); expiredDate = DateTime.UtcNow.Add(newExpiration.Value); } if (!expiredDate.HasValue) { XException.InvalidArgs.Throw(); } // // Update Item ... result.ExpiresAt = expiredDate.Value; // result = await UpdateApiKeyAsync( item: result, id: result.Id, userInfo: userInfo, cancellationToken: cancellationToken ); // return result; } /// /// Retrieve All Api Keys for Specified Application ... /// /// /// /// /// public async Task> GetApplicationApiKeys( int applicationId, XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ) { // // Validate ... if ( userInfo.IsNullOrDefault() || !applicationId.IsValidIntId() ) { XException.InvalidArgs.Throw(); } // // Permission Checking ... var application = await GetApplicationAsync( userInfo: userInfo, includeBuilder: null, id: applicationId, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (application.IsNullOrDefault()) { XException.NotFound.Throw(); } if (!IsOwned(application, userInfo)) { XException.NotAllowed.Throw(); } // var result = await FindManyApiKeyAsync( userInfo: userInfo, includeBuilder: null, ignoreSoftDeleteds: true, cancellationToken: cancellationToken, orderBuilder: x => x.OrderBy(y => y.Id), predicate: x => x.ApplicationId == applicationId ); // return result; } /// /// Validate Specified Api Key in based on ClientIP ... /// /// /// /// /// /// /// public async Task ValidateApiKey( string apiKey, string clientIP, string requiredScope = null, XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ) { // var result = new XApiKeyValidationResult(); // // Validate ... var isValid = !apiKey.IsNullOrEmpty() && !clientIP.IsNullOrEmpty(); if (!isValid) { return result; } // // Detect Specified Api Key ... var apiKeyHash = XApiKeyHelper.ComputeHash(apiKey); var keyModel = await dbContext.ApiKeys .FirstOrDefaultAsync( ak => ak.KeyHash == apiKeyHash, cancellationToken: cancellationToken ); isValid = !keyModel.IsNullOrDefault(); if (!isValid) { // result.Errors.Add("ApiKey not found ..."); return result; } // // Here we Have to Parse Allowed Ips and Scopes ... var allowedIps = (keyModel.AllowedIPs ?? string.Empty) .ParseListString(); var allowedScopes = (keyModel.AllowedScopes ?? string.Empty) .FromJSON>(); result.Scopes = [..allowedScopes]; // // Check Application is Exists and Active ... var application = await dbContext.Applications .FirstOrDefaultAsync(x => x.Id == keyModel.ApplicationId); if (!application.IsNullOrDefault()) { result.OwnerId = application.OwnerId; result.ApplicationId = application.Id; } // // Check Application is Exists and Active or not ... var isApplicationActive = // // Application Exists ... !application.IsNullOrDefault() && // // Check Application is Active ... application.IsActive // ; if (!isApplicationActive) { result.Errors.Add("Related Application is Inactive ..."); } // // Check Key is Active or not ... var isKeyActive = // // Check Api Key is Active ... // Not Revoked ... // Not Expired ... keyModel.IsActive() // ; if (!isKeyActive) { // if (keyModel.IsExpired()) { result.Errors.Add("ApiKey is Expired ..."); } // if (keyModel.IsRevoked()) { result.Errors.Add("ApiKey is Revoked ..."); } } // // Checking Rate Limit ... var isRateLimitPassed = CheckRateLimit(keyModel.Id, keyModel.RateLimitPerMinute); if (!isRateLimitPassed) { result.Errors.Add("ApiKey Rate Limit Reached ..."); } // // Audit Log ... // // Check Scope and IP is Valid or not ... var isIpValid = !allowedIps.HasChild() || allowedIps.Contains(clientIP); var isScopeValid = !allowedScopes.HasChild() || requiredScope.IsNullOrEmpty() || allowedScopes.Contains(requiredScope); var isScopeIpValid = isIpValid && isScopeValid ; if (!isScopeIpValid) { // if (!isIpValid) { result.Errors.Add("Client IP not Allowed to Use Resource ..."); } // if (!isScopeValid) { result.Errors.Add("Required Scoped is Invalid ..."); } } // isValid = isKeyActive && isScopeIpValid && isRateLimitPassed && isApplicationActive; if (isValid) { result.Errors.Clear(); } // return result; } #endregion } }