diff --git a/Configurations/XApiKeyConfiguration.cs b/Configurations/XApiKeyConfiguration.cs
index c67b3f7..fa84557 100644
--- a/Configurations/XApiKeyConfiguration.cs
+++ b/Configurations/XApiKeyConfiguration.cs
@@ -5,6 +5,11 @@ namespace xIds.Configurations
///
public class XApiKeyConfiguration
{
+ ///
+ /// Generated Api Keys Prefix ...
+ ///
+ public string ApiKeyPrefix { get; set; }
+
///
/// Default expiration in minutes (e.g., 43200 = 30 days)
///
diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs
index a39d80c..52559e8 100644
--- a/DI/XDIHelperExtension.cs
+++ b/DI/XDIHelperExtension.cs
@@ -25,7 +25,9 @@ using xIdentityModels.Configurations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
+using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
+using xIdentityService.Constants;
namespace xIds.DI
{
@@ -147,6 +149,64 @@ namespace xIds.DI
services.AddSingleton(dataServiceConfiguration);
}
+ ///
+ /// Add Specified ApiKey Configurations Instance ...
+ ///
+ ///
+ ///
+ public static void AddXApiKeyConfiguration(
+ this IServiceCollection source,
+ XApiKeyConfiguration configuration
+ )
+ {
+ //
+ if (configuration.IsNull())
+ {
+ return;
+ }
+
+ //
+ source.AddSingleton(configuration);
+ }
+
+ ///
+ /// Extract APpi KLey Configurations from app settings
+ /// and Register in Services ...
+ ///
+ ///
+ ///
+ public static void AddXApiKeyConfiguration(
+ this IServiceCollection source,
+ IConfiguration configuration
+ )
+ {
+ //
+ var config = configuration.GetXApiKeyConfiguration();
+ if (config.IsNull())
+ {
+ return;
+ }
+
+ //
+ source.AddXApiKeyConfiguration(config);
+ }
+
+ ///
+ /// Register Application Provider ...
+ /// Managing Applications/ApiKeys/ApiKeyUsages ...
+ ///
+ ///
+ ///
+ public static void AddXApplicationProvider(
+ this IServiceCollection source,
+ IConfiguration configuration
+ )
+ {
+ //
+ source.AddXApiKeyConfiguration(configuration);
+ source.AddScoped();
+ }
+
///
/// Register All Requirements For XIdentityServer Usage
///
@@ -336,24 +396,41 @@ namespace xIds.DI
///
///
///
+ ///
public static void AddXIdentityServerAuthentication(
this IServiceCollection services,
- IConfiguration configuration
+ IConfiguration configuration,
+ bool addApiKeyAuthentication = false
)
{
//
services.AddXIdentityResourceConfiguration(configuration);
- services.AddAuthentication()
- .AddJwtBearer(options =>
- {
- //
- options.SaveToken = true;
- options.RequireHttpsMetadata = false;
- //
- options.ForwardDefault = XAuthentication.IDENTITY_SERVER_LOCAL_API;
- })
- .AddLocalApi();
+ //
+ var authBuilder = services.AddAuthentication();
+
+ //
+ // Add JWT Bearer ...
+ authBuilder.AddJwtBearer(options =>
+ {
+ //
+ options.SaveToken = true;
+ options.RequireHttpsMetadata = false;
+
+ //
+ options.ForwardDefault = XAuthentication.IDENTITY_SERVER_LOCAL_API;
+ })
+ .AddLocalApi();
+
+ //
+ if (addApiKeyAuthentication)
+ {
+ //
+ authBuilder.AddScheme(
+ XAuthenticationScheme.XApiKey.GetStringValue(),
+ options => {}
+ );
+ }
}
///
diff --git a/Helpers/XApiKeyHelper.cs b/Helpers/XApiKeyHelper.cs
index 273bb22..89c00e3 100644
--- a/Helpers/XApiKeyHelper.cs
+++ b/Helpers/XApiKeyHelper.cs
@@ -9,11 +9,29 @@ namespace xIds.Helpers
///
public static class XApiKeyHelper
{
+ ///
+ /// Compute Hash of Specified Content using SHA256 ...
+ ///
+ ///
+ ///
+ public static string ComputeHash(string plain)
+ {
+ //
+ using var sha256 = SHA256.Create();
+ var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(plain));
+ var result = BitConverter.ToString(hashBytes)
+ .Replace("-", "")
+ .ToLower();
+
+ //
+ return result;
+ }
+
///
/// Generate an Api Key ...
///
///
- public static (string plainKey, string hash, string prefix) Generate()
+ public static (string plainKey, string hash, string prefix) Generate(string keyPrefix)
{
//
// Generate 32 bytes of cryptographically secure random data
@@ -25,24 +43,19 @@ namespace xIds.Helpers
//
// Format: xapp_{base64url}
- var plainKey = $"xapp_{Convert.ToBase64String(randomBytes)
+ var plainKey = $"{keyPrefix}_{Convert.ToBase64String(randomBytes)
.Replace("+", "-").Replace("/", "_").TrimEnd('=')}";
//
- // Hash with SHA256 for storage (never store plain key)
- using (var sha256 = SHA256.Create())
- {
- //
- var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(plainKey));
- var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
+ // Compute Hash ...
+ var hash = ComputeHash(plainKey);
- //
- // Prefix for quick identification (first 12 chars)
- var prefix = plainKey.Substring(0, 16) + "...";
+ //
+ // Prefix for quick identification (first 12 chars)
+ var prefix = plainKey.Substring(0, 16) + "...";
- //
- return (plainKey, hash, prefix);
- }
+ //
+ return (plainKey, hash, prefix);
}
///
@@ -57,16 +70,8 @@ namespace xIds.Helpers
)
{
//
- using (var sha256 = SHA256.Create())
- {
- //
- var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(plainKey));
- var computedHash = BitConverter.ToString(hashBytes)
- .Replace("-", "").ToLower();
-
- //
- return computedHash == storedHash;
- }
+ var hash = ComputeHash(plainKey);
+ return hash == storedHash;
}
}
}
\ No newline at end of file
diff --git a/Interfaces/IXApplicationProvider.cs b/Interfaces/IXApplicationProvider.cs
index 7feb598..2dbf7d3 100644
--- a/Interfaces/IXApplicationProvider.cs
+++ b/Interfaces/IXApplicationProvider.cs
@@ -194,7 +194,7 @@ namespace xIds.Interfaces
///
///
Task UpdateApiKeyAsync(
- int id,
+ Guid id,
XApiKeyDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
@@ -209,7 +209,7 @@ namespace xIds.Interfaces
///
///
Task RemoveApiKeyAsync(
- int id,
+ Guid id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
@@ -225,7 +225,7 @@ namespace xIds.Interfaces
///
///
Task GetApiKeyAsync(
- int id,
+ Guid id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func, IIncludableQueryable> includeBuilder = null,
@@ -348,7 +348,7 @@ namespace xIds.Interfaces
///
///
Task UpdateApiKeyUsageAsync(
- int id,
+ long id,
XApiKeyUsageDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
@@ -363,7 +363,7 @@ namespace xIds.Interfaces
///
///
Task RemoveApiKeyUsageAsync(
- int id,
+ long id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
@@ -379,7 +379,7 @@ namespace xIds.Interfaces
///
///
Task GetApiKeyUsageAsync(
- int id,
+ long id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func, IIncludableQueryable> includeBuilder = null,
@@ -547,12 +547,14 @@ namespace xIds.Interfaces
///
///
///
+ ///
///
///
///
- Task ValidateApiKey(
+ Task ValidateApiKey(
string apiKey,
string clientIP,
+ string requiredScope = null,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
@@ -632,7 +634,7 @@ namespace xIds.Interfaces
///
///
///
- Task EnrichRangeAsync(
+ Task> EnrichRangeAsync(
IEnumerable sources,
CancellationToken cancellationToken = default
);
@@ -643,7 +645,7 @@ namespace xIds.Interfaces
///
///
///
- Task EnrichRangeAsync(
+ Task> EnrichRangeAsync(
IEnumerable sources,
CancellationToken cancellationToken = default
);
@@ -654,7 +656,7 @@ namespace xIds.Interfaces
///
///
///
- Task EnrichRangeAsync(
+ Task> EnrichRangeAsync(
IEnumerable sources,
CancellationToken cancellationToken = default
);
@@ -665,7 +667,7 @@ namespace xIds.Interfaces
///
///
///
- Task EnrichRangeAsync(
+ Task> EnrichRangeAsync(
IEnumerable sources,
CancellationToken cancellationToken = default
);
@@ -676,7 +678,7 @@ namespace xIds.Interfaces
///
///
///
- Task EnrichRangeAsync(
+ Task> EnrichRangeAsync(
IEnumerable sources,
CancellationToken cancellationToken = default
);
@@ -687,7 +689,7 @@ namespace xIds.Interfaces
///
///
///
- Task EnrichRangeAsync(
+ Task> EnrichRangeAsync(
IEnumerable sources,
CancellationToken cancellationToken = default
);
diff --git a/Providers/XApiKeyAuthenticationHandler.cs b/Providers/XApiKeyAuthenticationHandler.cs
new file mode 100644
index 0000000..6d7c629
--- /dev/null
+++ b/Providers/XApiKeyAuthenticationHandler.cs
@@ -0,0 +1,101 @@
+using System.Linq;
+using IdentityModel;
+using xIds.Interfaces;
+using xCommons.Extensions;
+using System.Threading.Tasks;
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+using xIdentityService.Constants;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authentication;
+
+namespace xIds.Providers
+{
+ ///
+ /// an Authentication Handler for ApiKey's based App's ...
+ ///
+ public class XApiKeyAuthenticationHandler : AuthenticationHandler
+ {
+ private readonly IXApplicationProvider applicationProvider;
+
+ //
+ public string HeaderName { get; } = XHeader.ApiKey.GetStringValue();
+ public string AuthenticationScheme { get; } = XAuthenticationScheme.XApiKey.GetStringValue();
+
+ public XApiKeyAuthenticationHandler(
+ UrlEncoder encoder,
+ ISystemClock clock,
+ ILoggerFactory logger,
+ IXApplicationProvider applicationProvider,
+ IOptionsMonitor options
+ ) : base(options, logger, encoder, clock)
+ {
+ this.applicationProvider = applicationProvider;
+ }
+
+ ///
+ /// Handle Authentication ...
+ ///
+ ///
+ protected override async Task HandleAuthenticateAsync()
+ {
+ //
+ var apiKey =
+ !Request.Headers.ContainsKey(HeaderName)
+ ? string.Empty
+ : Request.Headers[HeaderName].ToString();
+ if (apiKey.IsNullOrEmpty())
+ {
+ return AuthenticateResult.NoResult();
+ }
+
+ //
+ var clientIP = Request.HttpContext.Connection.RemoteIpAddress?.ToString();
+
+ //
+ var validationResult = await applicationProvider.ValidateApiKey(
+ apiKey: apiKey,
+ clientIP: clientIP
+ );
+ var isValid = !validationResult.Errors.HasChild();
+ if (!isValid)
+ {
+ //
+ var message = validationResult.Errors.ToListString('\n');
+ return AuthenticateResult.Fail(message);
+ }
+
+ //
+ var claims = new[]
+ {
+ new Claim(ClaimTypes.Name, validationResult.OwnerId),
+ new Claim("application_id", validationResult.ApplicationId.ToString()),
+ new Claim("auth_type", "apikey"),
+ new Claim(JwtClaimTypes.Scope, "apikey"),
+ };
+
+ //
+ var scopeClaims = validationResult.Scopes
+ .Select(s => new Claim(JwtClaimTypes.Scope, s));
+
+ //
+ var identity = new ClaimsIdentity(
+ claims.Union(scopeClaims),
+ AuthenticationScheme
+ );
+
+ //
+ var principal = new ClaimsPrincipal(identity);
+
+ //
+ var ticket = new AuthenticationTicket(
+ principal,
+ AuthenticationScheme
+ );
+
+ //
+ return AuthenticateResult.Success(ticket);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Providers/XApplicationProvider.cs b/Providers/XApplicationProvider.cs
index 4216a72..7adc86a 100644
--- a/Providers/XApplicationProvider.cs
+++ b/Providers/XApplicationProvider.cs
@@ -13,6 +13,7 @@ using System.Linq.Expressions;
using IdentityServer4.Extensions;
using xIdentityModels.Extensions;
using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.Extensions.Caching.Memory;
namespace xIds.Providers
{
@@ -21,6 +22,7 @@ namespace xIds.Providers
///
public partial class XApplicationProvider : IXApplicationProvider
{
+ private readonly IMemoryCache cache;
private readonly string permittedRole;
private readonly XIdentityDbContext dbContext;
private readonly IXIdentityManager identityManager;
@@ -28,6 +30,7 @@ namespace xIds.Providers
private readonly XApiKeyConfiguration apiKeyConfiguration;
public XApplicationProvider(
+ IMemoryCache cache,
XIdentityDbContext dbContext,
IXIdentityManager identityManager,
XDataServiceConfiguration configuration,
@@ -36,6 +39,7 @@ namespace xIds.Providers
)
{
//
+ this.cache = cache;
this.dbContext = dbContext;
this.permittedRole = permittedRole;
this.configuration = configuration;
@@ -190,7 +194,7 @@ namespace xIds.Providers
// Return result ...
return result;
}
-
+
///
/// Prepare a Queryable of Items ...
///
@@ -233,7 +237,7 @@ namespace xIds.Providers
// Return result ...
return result;
}
-
+
///
/// Prepare a Queryable of Items ...
///
@@ -249,7 +253,7 @@ namespace xIds.Providers
{
//
// Global Result ...
- var result = dbContext.ApiKeyUsage.AsQueryable();
+ var result = dbContext.ApiKeyUsages.AsQueryable();
//
// Handle Filtering ...
@@ -275,6 +279,45 @@ namespace xIds.Providers
//
// Return result ...
return result;
- }
+ }
+
+ ///
+ /// Handle Rate Limit Checking ...
+ ///
+ ///
+ ///
+ ///
+ private bool CheckRateLimit(
+ Guid apiKeyId,
+ int limitPerMinute
+ )
+ {
+ //
+ var cacheKey = $"ratelimit:{apiKeyId}";
+ var currentCount = cache.GetOrCreate(
+ cacheKey,
+ entry =>
+ {
+ //
+ entry.SlidingExpiration = TimeSpan.FromMinutes(1);
+ return 0;
+ }
+ );
+
+ //
+ if (currentCount >= limitPerMinute) {
+ return false;
+ }
+
+ //
+ cache.Set(
+ cacheKey,
+ currentCount + 1,
+ TimeSpan.FromMinutes(1)
+ );
+
+ //
+ return true;
+ }
}
}
\ No newline at end of file
diff --git a/Providers/XApplicationProvider/XApplicationProvider+Custom.cs b/Providers/XApplicationProvider/XApplicationProvider+Custom.cs
index 70e646b..c738fa1 100644
--- a/Providers/XApplicationProvider/XApplicationProvider+Custom.cs
+++ b/Providers/XApplicationProvider/XApplicationProvider+Custom.cs
@@ -8,7 +8,8 @@ using xExceptions.Constants;
using System.Threading.Tasks;
using xIdentityModels.Models;
using System.Collections.Generic;
-using System.Security.Cryptography;
+using xIdentityModels.Extensions;
+using Microsoft.EntityFrameworkCore;
namespace xIds.Providers
{
@@ -85,7 +86,7 @@ namespace xIds.Providers
//
// Generate New Key ...
- (string plainKey, string keyHash, string prefix) = XApiKeyHelper.Generate();
+ (string plainKey, string keyHash, string prefix) = XApiKeyHelper.Generate(apiKeyConfiguration.ApiKeyPrefix);
var expireAt = DateTime.UtcNow.Add(expiration.Value);
//
@@ -112,7 +113,7 @@ namespace xIds.Providers
//
// Add Item to Database ...
result = await AddApiKeyAsync(
- item: item,
+ item: result,
userInfo: userInfo,
cancellationToken: cancellationToken
);
@@ -303,7 +304,7 @@ namespace xIds.Providers
{
XException.NotAllowed.Throw();
}
-
+
//
var result = await FindManyApiKeyAsync(
userInfo: userInfo,
@@ -323,18 +324,158 @@ namespace xIds.Providers
///
///
///
+ ///
///
///
///
- public async Task ValidateApiKey(
+ 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
}
diff --git a/Providers/XApplicationProvider/XApplicationProvider+Enricher.cs b/Providers/XApplicationProvider/XApplicationProvider+Enricher.cs
index 6447457..187d8e0 100644
--- a/Providers/XApplicationProvider/XApplicationProvider+Enricher.cs
+++ b/Providers/XApplicationProvider/XApplicationProvider+Enricher.cs
@@ -1,10 +1,11 @@
-using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading;
-using System.Threading.Tasks;
+using xCommons.Extensions;
using xIdentityModels.Dtos;
+using System.Threading.Tasks;
using xIdentityModels.Models;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
namespace xIds.Providers
{
@@ -22,7 +23,17 @@ namespace xIds.Providers
XApiKey source,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var result = source.MapConvert();
+ result = await EnrichAsync(
+ source: result,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
///
/// Enrich Api Key ...
@@ -34,7 +45,36 @@ namespace xIds.Providers
XApiKeyDto source,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ if (
+ !source.IsNullOrDefault() &&
+ !source.Application.IsNullOrDefault()
+ )
+ {
+ //
+ var application = await dbContext.Applications
+ .FirstOrDefaultAsync(
+ x => x.Id == source.ApplicationId,
+ cancellationToken: cancellationToken
+ );
+ if (!application.IsNullOrDefault())
+ {
+ //
+ var dto = await EnrichAsync(
+ source: application,
+ cancellationToken: cancellationToken
+ );
+ if (!dto.IsNullOrDefault())
+ {
+ source.Application = dto;
+ }
+ }
+ }
+
+ //
+ return source;
+ }
///
/// Enrich Api Key Usage ...
@@ -46,7 +86,17 @@ namespace xIds.Providers
XApiKeyUsage source,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var result = source.MapConvert();
+ result = await EnrichAsync(
+ source: result,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
///
/// Enrich Application ...
@@ -58,7 +108,17 @@ namespace xIds.Providers
XApplication source,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var result = source.MapConvert();
+ result = await EnrichAsync(
+ source: result,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ return result;
+ }
///
/// Enrich Api Key Usage ...
@@ -70,7 +130,12 @@ namespace xIds.Providers
XApiKeyUsageDto source,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ // There is not any Additional Enrichment ...
+ var result = await Task.FromResult(source);
+ return result;
+ }
///
/// Enrich Application ...
@@ -82,7 +147,24 @@ namespace xIds.Providers
XApplicationDto source,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ if (
+ !source.IsNullOrDefault() &&
+ !source.OwnerId.IsNullOrEmpty()
+ )
+ {
+ //
+ var owner = await GetOwner(source.OwnerId);
+ if (!owner.IsNullOrDefault())
+ {
+ source.Owner = owner;
+ }
+ }
+
+ //
+ return source;
+ }
///
/// Enrich a Collection of Api Key(s) ...
@@ -94,7 +176,17 @@ namespace xIds.Providers
IEnumerable sources,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var tasks = sources
+ .Select(s => EnrichAsync(s, cancellationToken));
+
+ //
+ var result = await Task.WhenAll(tasks);
+
+ //
+ return result;
+ }
///
/// Enrich a Collection of Api Key(s) ...
@@ -106,7 +198,17 @@ namespace xIds.Providers
IEnumerable sources,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var tasks = sources
+ .Select(s => EnrichAsync(s, cancellationToken));
+
+ //
+ var result = await Task.WhenAll(tasks);
+
+ //
+ return result;
+ }
///
/// Enrich a Collection of Api Key Usage(s) ...
@@ -118,7 +220,17 @@ namespace xIds.Providers
IEnumerable sources,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var tasks = sources
+ .Select(s => EnrichAsync(s, cancellationToken));
+
+ //
+ var result = await Task.WhenAll(tasks);
+
+ //
+ return result;
+ }
///
/// Enrich a Collection of Application(s) ...
@@ -130,7 +242,17 @@ namespace xIds.Providers
IEnumerable sources,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var tasks = sources
+ .Select(s => EnrichAsync(s, cancellationToken));
+
+ //
+ var result = await Task.WhenAll(tasks);
+
+ //
+ return result;
+ }
///
/// Enrich a Collection of Api Key Usage(s) ...
@@ -142,7 +264,17 @@ namespace xIds.Providers
IEnumerable sources,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var tasks = sources
+ .Select(s => EnrichAsync(s, cancellationToken));
+
+ //
+ var result = await Task.WhenAll(tasks);
+
+ //
+ return result;
+ }
///
/// Enrich a Collection of Application(s) ...
@@ -154,7 +286,17 @@ namespace xIds.Providers
IEnumerable sources,
CancellationToken cancellationToken = default
)
- { }
+ {
+ //
+ var tasks = sources
+ .Select(s => EnrichAsync(s, cancellationToken));
+
+ //
+ var result = await Task.WhenAll(tasks);
+
+ //
+ return result;
+ }
#endregion
}
}
\ No newline at end of file
diff --git a/Startup.cs b/Startup.cs
index 3c14006..000389d 100644
--- a/Startup.cs
+++ b/Startup.cs
@@ -1,20 +1,20 @@
using System;
+using xIds.DI;
using System.IO;
+using xIds.Constants;
+using xIds.Providers;
+using Newtonsoft.Json;
+using xIds.Interfaces;
using System.Reflection;
+using xStorageService.DI;
+using xCommons.Extensions;
+using xCommons.Configurations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using xCommons.Configurations;
-using xCommons.Extensions;
-using xStorageService.DI;
-using xIds.Constants;
-using xIds.DI;
-using xIds.Interfaces;
-using xIds.Providers;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
namespace xIds
{
@@ -57,6 +57,10 @@ namespace xIds
// services.AddInMemoryXIdentityServer (Configuration);
services.AddEfSupportXIdentityServer(Configuration);
+ //
+ // Services Application and Api Key Management Services ...
+ services.AddXApplicationProvider(Configuration);
+
//
services.AddControllers()
.AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
@@ -71,7 +75,7 @@ namespace xIds
//
// Register Security Provider ...
- services.AddSingleton();
+ services.AddSingleton();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger)
diff --git a/appsettings.Development.json b/appsettings.Development.json
index 267897f..de9781c 100644
--- a/appsettings.Development.json
+++ b/appsettings.Development.json
@@ -37,6 +37,7 @@
"MaxFileSize": 41943040
},
"ApiKeyConfiguration": {
+ "ApiPrefix": "xapp",
"DefaultRateLimit": 60,
"EnableAuditLog": false,
"MaxKeysPerApplication": "10",
diff --git a/xIds.csproj b/xIds.csproj
index 913d192..1b4f73b 100644
--- a/xIds.csproj
+++ b/xIds.csproj
@@ -36,6 +36,7 @@
+