Compare commits

..
7 Commits
Author SHA1 Message Date
saherelm cd2b06b913 add required extensions for DI Registration ... 2026-10-06 21:31:36 +03:30
saherelm 48395457e6 last ... 2026-10-06 17:47:27 +03:30
saherelm e379faaa07 last ... 2026-10-05 22:30:29 +03:30
saherelm ac0ebe601e last ... 2026-10-05 17:37:28 +03:30
saherelm 0e9680dc95 last ... 2026-10-04 22:02:13 +03:30
saherelm b7fba89157 last ... 2026-10-04 18:11:46 +03:30
saherelm 0f0e078c8e kast ... 2026-09-03 10:34:48 +03:30
18 changed files with 4119 additions and 59 deletions
+38
View File
@@ -0,0 +1,38 @@
namespace xIds.Configurations
{
/// <summary>
/// Configuration settings for API Key management, including expiration, rate limiting, and audit logging.
/// </summary>
public class XApiKeyConfiguration
{
/// <summary>
/// Generated Api Keys Prefix ...
/// </summary>
public string ApiKeyPrefix { get; set; }
/// <summary>
/// Default expiration in minutes (e.g., 43200 = 30 days)
/// </summary>
public int DefaultExpirationMinutes { get; set; } = 43200;
/// <summary>
/// Maximum allowed expiration in minutes (e.g., 525600 = 1 year)
/// </summary>
public int MaxExpirationMinutes { get; set; } = 525600;
/// <summary>
/// Maximum ApiKeys per Application
/// </summary>
public int MaxKeysPerApplication { get; set; } = 10;
/// <summary>
/// Enable audit logging
/// </summary>
public bool EnableAuditLog { get; set; } = false;
/// <summary>
/// Default rate limit (requests per minute)
/// </summary>
public int DefaultRateLimit { get; set; } = 60;
}
}
+1
View File
@@ -4,6 +4,7 @@ namespace xIds.Constants
{ {
public const string DB_SEED_NODE_NAME = "DbSeeder"; public const string DB_SEED_NODE_NAME = "DbSeeder";
public const string PROVIDER_NODE_NAME = "DbProvider"; public const string PROVIDER_NODE_NAME = "DbProvider";
public const string APIKEY_NODE_NAME = "ApiKeyConfiguration";
public const string IDENTITY_NODE_NAME = "IdentityConfiguration"; public const string IDENTITY_NODE_NAME = "IdentityConfiguration";
public const string DATA_SERVICE_NODE_NAME = "DataServiceConfiguration"; public const string DATA_SERVICE_NODE_NAME = "DataServiceConfiguration";
public const string IDENTITY_RESOURCE_NODE_NAME = "IdentityResourceConfiguration"; public const string IDENTITY_RESOURCE_NODE_NAME = "IdentityResourceConfiguration";
+133 -33
View File
@@ -1,31 +1,33 @@
using System.Collections.Generic; using xIds.Helpers;
using IdentityServer4;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using xCommons.Authorization;
using xCommons.Constants;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityHelper;
using xIdentityModels;
using xIdentityModels.Configurations;
using xIds.Configurations;
using xIds.Constants; using xIds.Constants;
using xIds.DbContext; using xIds.DbContext;
using xIds.Extensions;
using xIds.Helpers;
using xIds.Interfaces;
using xIds.Providers; using xIds.Providers;
using IdentityServer4;
using xIdentityHelper;
using xIdentityModels;
using xIds.Extensions;
using xIds.Interfaces;
using xIds.Validators; using xIds.Validators;
using xCommons.Constants;
using xMessageService.DI; using xMessageService.DI;
using xMessageService.Interfaces;
using xStorageService.DI; using xStorageService.DI;
using xCommons.Extensions;
using xIds.Configurations;
using xExceptions.Constants;
using xCommons.Authorization;
using System.Collections.Generic;
using xMessageService.Interfaces;
using xStorageService.Interfaces; using xStorageService.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Identity;
using xIdentityModels.Configurations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using xIdentityService.Constants;
namespace xIds.DI namespace xIds.DI
{ {
@@ -80,7 +82,7 @@ namespace xIds.DI
} }
/// <summary> /// <summary>
/// Retrive XDataService Configurations /// Retrive XDataService Configurations ...
/// </summary> /// </summary>
/// <param name="source"></param> /// <param name="source"></param>
/// <param name="connectionName"></param> /// <param name="connectionName"></param>
@@ -107,6 +109,26 @@ namespace xIds.DI
return result; return result;
} }
/// <summary>
/// Retrieve XApiKeyConfiguration Configurations ...
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static XApiKeyConfiguration GetXApiKeyConfiguration(this IConfiguration source)
{
//
var section = source
.GetSection(Constants.ConfigurationNodeNames.APIKEY_NODE_NAME);
var result = section.Get<XApiKeyConfiguration>();
if (result.IsNull())
{
result = new XApiKeyConfiguration();
}
//
return result;
}
/// <summary> /// <summary>
/// Register XDataService Configuration /// Register XDataService Configuration
/// </summary> /// </summary>
@@ -127,6 +149,67 @@ namespace xIds.DI
services.AddSingleton<XDataServiceConfiguration>(dataServiceConfiguration); services.AddSingleton<XDataServiceConfiguration>(dataServiceConfiguration);
} }
/// <summary>
/// Add Specified ApiKey Configurations Instance ...
/// </summary>
/// <param name="source"></param>
/// <param name="configuration"></param>
public static void AddXApiKeyConfiguration(
this IServiceCollection source,
XApiKeyConfiguration configuration
)
{
//
if (configuration.IsNull())
{
return;
}
//
source.AddSingleton(configuration);
}
/// <summary>
/// Extract APpi KLey Configurations from app settings
/// and Register in Services ...
/// </summary>
/// <param name="source"></param>
/// <param name="configuration"></param>
public static void AddXApiKeyConfiguration(
this IServiceCollection source,
IConfiguration configuration
)
{
//
var config = configuration.GetXApiKeyConfiguration();
if (config.IsNull())
{
return;
}
//
source.AddXApiKeyConfiguration(config);
}
/// <summary>
/// Register Application Provider ...
/// Managing Applications/ApiKeys/ApiKeyUsages ...
/// </summary>
/// <param name="source"></param>
/// <param name="configuration"></param>
public static void AddXApplicationProvider(
this IServiceCollection source,
IConfiguration configuration
)
{
//
source.AddXApiKeyConfiguration(configuration);
//
source.AddMemoryCache();
source.AddScoped<IXApplicationProvider, XApplicationProvider>();
}
/// <summary> /// <summary>
/// Register All Requirements For XIdentityServer Usage /// Register All Requirements For XIdentityServer Usage
/// </summary> /// </summary>
@@ -316,24 +399,41 @@ namespace xIds.DI
/// </summary> /// </summary>
/// <param name="services"></param> /// <param name="services"></param>
/// <param name="configuration"></param> /// <param name="configuration"></param>
/// <param name="addApiKeyAuthentication"></param>
public static void AddXIdentityServerAuthentication( public static void AddXIdentityServerAuthentication(
this IServiceCollection services, this IServiceCollection services,
IConfiguration configuration IConfiguration configuration,
bool addApiKeyAuthentication = false
) )
{ {
// //
services.AddXIdentityResourceConfiguration(configuration); services.AddXIdentityResourceConfiguration(configuration);
services.AddAuthentication()
.AddJwtBearer(options =>
{
//
options.SaveToken = true;
options.RequireHttpsMetadata = false;
// //
options.ForwardDefault = XAuthentication.IDENTITY_SERVER_LOCAL_API; var authBuilder = services.AddAuthentication();
})
.AddLocalApi(); //
// Add JWT Bearer ...
authBuilder.AddJwtBearer(options =>
{
//
options.SaveToken = true;
options.RequireHttpsMetadata = false;
//
options.ForwardDefault = XAuthentication.IDENTITY_SERVER_LOCAL_API;
})
.AddLocalApi();
//
if (addApiKeyAuthentication)
{
//
authBuilder.AddScheme<AuthenticationSchemeOptions, XApiKeyAuthenticationHandler>(
XAuthenticationScheme.XApiKey.GetStringValue(),
options => {}
);
}
} }
/// <summary> /// <summary>
+24 -3
View File
@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using xIdentityModels; using xIdentityModels;
using xIdentityModels.Models; using xIdentityModels.Models;
using xIdentityModels.Navigations; using xIdentityModels.Navigations;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
namespace xIds.DbContext namespace xIds.DbContext
{ {
@@ -13,7 +13,10 @@ namespace xIds.DbContext
#region Props/DbSets ... #region Props/DbSets ...
public DbSet<XToken> Tokens { get; set; } public DbSet<XToken> Tokens { get; set; }
public DbSet<XDevice> Devices { get; set; } public DbSet<XDevice> Devices { get; set; }
public DbSet<XApiKey> ApiKeys { get; set; }
public DbSet<XProfileImage> Avatars { get; set; } public DbSet<XProfileImage> Avatars { get; set; }
public DbSet<XApiKeyUsage> ApiKeyUsages { get; set; }
public DbSet<XApplication> Applications { get; set; }
public DbSet<XBannedDevice> BannedDevices { get; set; } public DbSet<XBannedDevice> BannedDevices { get; set; }
public DbSet<XFriendshipFollower> Followers { get; set; } public DbSet<XFriendshipFollower> Followers { get; set; }
public DbSet<XFriendshipFollowing> Followings { get; set; } public DbSet<XFriendshipFollowing> Followings { get; set; }
@@ -52,6 +55,24 @@ namespace xIds.DbContext
.HasForeignKey(ur => ur.UserId) .HasForeignKey(ur => ur.UserId)
.IsRequired(); .IsRequired();
}); });
//
// Configure XApplication ...
modelBuilder.Entity<XApplication>(e =>
{
e.HasIndex(a => a.Name).IsUnique();
e.HasMany(a => a.ApiKeys)
.WithOne(k => k.Application)
.HasForeignKey(k => k.ApplicationId);
});
//
// Configure XApiKey ...
modelBuilder.Entity<XApiKey>(e =>
{
e.HasIndex(k => k.KeyHash).IsUnique();
e.HasIndex(k => k.ExpiresAt);
});
} }
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using xIds.Interfaces;
using xIds.Providers; using xIds.Providers;
using xIds.Interfaces;
using Microsoft.Extensions.DependencyInjection;
namespace xIds.Extensions namespace xIds.Extensions
{ {
+77
View File
@@ -0,0 +1,77 @@
using System;
using System.Text;
using System.Security.Cryptography;
namespace xIds.Helpers
{
/// <summary>
/// Api Key Generating Helper Class ...
/// </summary>
public static class XApiKeyHelper
{
/// <summary>
/// Compute Hash of Specified Content using SHA256 ...
/// </summary>
/// <param name="plain"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Generate an Api Key ...
/// </summary>
/// <returns></returns>
public static (string plainKey, string hash, string prefix) Generate(string keyPrefix)
{
//
// Generate 32 bytes of cryptographically secure random data
var randomBytes = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomBytes);
}
//
// Format: xapp_{base64url}
var plainKey = $"{keyPrefix}_{Convert.ToBase64String(randomBytes)
.Replace("+", "-").Replace("/", "_").TrimEnd('=')}";
//
// Compute Hash ...
var hash = ComputeHash(plainKey);
//
// Prefix for quick identification (first 12 chars)
var prefix = plainKey.Substring(0, 16) + "...";
//
return (plainKey, hash, prefix);
}
/// <summary>
/// Verify Generating Api Key ...
/// </summary>
/// <param name="plainKey"></param>
/// <param name="storedHash"></param>
/// <returns></returns>
public static bool Verify(
string plainKey,
string storedHash
)
{
//
var hash = ComputeHash(plainKey);
return hash == storedHash;
}
}
}
+8 -8
View File
@@ -1,13 +1,13 @@
using System; using System;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using xCommons.Extensions;
using xCommons.Providers;
using xExceptions.Constants;
using xIdentityModels.Configurations;
using xIdentityModels.Extensions;
using xIdentityModels.Models;
using xIds.Interfaces; using xIds.Interfaces;
using xCommons.Providers;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Models;
using xIdentityModels.Extensions;
using Microsoft.IdentityModel.Tokens;
using xIdentityModels.Configurations;
using System.IdentityModel.Tokens.Jwt;
namespace xIds.Helpers namespace xIds.Helpers
{ {
+698
View File
@@ -0,0 +1,698 @@
using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xIdentityModels.Dtos;
using System.Threading.Tasks;
using xIdentityModels.Models;
using System.Linq.Expressions;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query;
namespace xIds.Interfaces
{
/// <summary>
/// a Service For Manage Security Applications and also Api Key(s) ...
/// </summary>
public interface IXApplicationProvider
{
//
#region Application ...
/// <summary>
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApplicationDto> AddApplicationAsync(
XApplicationDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Update Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApplicationDto> UpdateApplicationAsync(
int id,
XApplicationDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Remove Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="softDelete"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApplicationDto> RemoveApplicationAsync(
int id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve an Item by Providing it's ID ...
/// </summary>
/// <param name="id"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApplicationDto> GetApplicationAsync(
int id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve all Items ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApplicationDto>> GetAllApplicationAsync(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve all Items as Async Enumerable ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IAsyncEnumerable<XApplicationDto> GetAllApplicationAsAsyncEnumerable(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Find an Item By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApplicationDto> FindOneApplicationAsync(
Expression<Func<XApplication, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Find a Collection of Items By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApplicationDto>> FindManyApplicationAsync(
Expression<Func<XApplication, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve Items based on XQuery Pagination Structure ...
/// </summary>
/// <param name="query"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XQueryResult<XApplicationDto>> QueryApplicationAsync(
XQuery query,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Expression<Func<XApplication, bool>> predicate = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
#endregion
//
#region ApiKey ...
/// <summary>
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> AddApiKeyAsync(
XApiKeyDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Update Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> UpdateApiKeyAsync(
Guid id,
XApiKeyDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Remove Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="softDelete"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> RemoveApiKeyAsync(
Guid id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve an Item by Providing it's ID ...
/// </summary>
/// <param name="id"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> GetApiKeyAsync(
Guid id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve all Items ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyDto>> GetAllApiKeyAsync(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve all Items as Async Enumerable ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IAsyncEnumerable<XApiKeyDto> GetAllApiKeyAsAsyncEnumerable(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Find an Item By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> FindOneApiKeyAsync(
Expression<Func<XApiKey, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Find a Collection of Items By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyDto>> FindManyApiKeyAsync(
Expression<Func<XApiKey, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve Items based on XQuery Pagination Structure ...
/// </summary>
/// <param name="query"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XQueryResult<XApiKeyDto>> QueryApiKeyAsync(
XQuery query,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Expression<Func<XApiKey, bool>> predicate = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
#endregion
//
#region ApiKeyUsage ...
/// <summary>
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyUsageDto> AddApiKeyUsageAsync(
XApiKeyUsageDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Update Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyUsageDto> UpdateApiKeyUsageAsync(
long id,
XApiKeyUsageDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Remove Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="softDelete"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyUsageDto> RemoveApiKeyUsageAsync(
long id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve an Item by Providing it's ID ...
/// </summary>
/// <param name="id"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyUsageDto> GetApiKeyUsageAsync(
long id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve all Items ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyUsageDto>> GetAllApiKeyUsageAsync(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve all Items as Async Enumerable ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
IAsyncEnumerable<XApiKeyUsageDto> GetAllApiKeyUsageAsAsyncEnumerable(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Find an Item By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyUsageDto> FindOneApiKeyUsageAsync(
Expression<Func<XApiKeyUsage, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Find a Collection of Items By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyUsageDto>> FindManyApiKeyUsageAsync(
Expression<Func<XApiKeyUsage, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve Items based on XQuery Pagination Structure ...
/// </summary>
/// <param name="query"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XQueryResult<XApiKeyUsageDto>> QueryApiKeyUsageAsync(
XQuery query,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Expression<Func<XApiKeyUsage, bool>> predicate = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
);
#endregion
//
#region Custom Actions ...
/// <summary>
/// Generate a New ApiKey and Add It to DB ...
/// </summary>
/// <param name="applicationId"></param>
/// <param name="expiration"></param>
/// <param name="scopes"></param>
/// <param name="allowedIPs"></param>
/// <param name="rateLimit"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> CreateApiKey(
int applicationId,
TimeSpan? expiration = null,
IEnumerable<string> scopes = null,
IEnumerable<string> allowedIPs = null,
int? rateLimit = null,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Revoke Specified Api Key ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<bool> RevokeApiKey(
Guid apiKeyId,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Renew Specified Api Key ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="newExpiration"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> RotateApiKey(
Guid apiKeyId,
TimeSpan? newExpiration = null,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Retrieve All Api Keys for Specified Application ...
/// </summary>
/// <param name="applicationId"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyDto>> GetApplicationApiKeys(
int applicationId,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Validate Specified Api Key in based on ClientIP ...
/// </summary>
/// <param name="apiKey"></param>
/// <param name="clientIP"></param>
/// <param name="requiredScope"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyValidationResult> ValidateApiKey(
string apiKey,
string clientIP,
string requiredScope = null,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
);
#endregion
//
#region Enrichers ...
/// <summary>
/// Enrich Api Key ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> EnrichAsync(
XApiKey source,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich Api Key ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyDto> EnrichAsync(
XApiKeyDto source,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich Api Key Usage ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyUsageDto> EnrichAsync(
XApiKeyUsage source,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich Application ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApplicationDto> EnrichAsync(
XApplication source,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich Api Key Usage ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApiKeyUsageDto> EnrichAsync(
XApiKeyUsageDto source,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich Application ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XApplicationDto> EnrichAsync(
XApplicationDto source,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich a Collection of Api Key(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyDto>> EnrichRangeAsync(
IEnumerable<XApiKey> sources,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich a Collection of Api Key(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyDto>> EnrichRangeAsync(
IEnumerable<XApiKeyDto> sources,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich a Collection of Api Key Usage(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyUsageDto>> EnrichRangeAsync(
IEnumerable<XApiKeyUsage> sources,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich a Collection of Application(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApplicationDto>> EnrichRangeAsync(
IEnumerable<XApplication> sources,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich a Collection of Api Key Usage(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApiKeyUsageDto>> EnrichRangeAsync(
IEnumerable<XApiKeyUsageDto> sources,
CancellationToken cancellationToken = default
);
/// <summary>
/// Enrich a Collection of Application(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XApplicationDto>> EnrichRangeAsync(
IEnumerable<XApplicationDto> sources,
CancellationToken cancellationToken = default
);
#endregion
}
}
+101
View File
@@ -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
{
/// <summary>
/// an Authentication Handler for ApiKey's based App's ...
/// </summary>
public class XApiKeyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
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<AuthenticationSchemeOptions> options
) : base(options, logger, encoder, clock)
{
this.applicationProvider = applicationProvider;
}
/// <summary>
/// Handle Authentication ...
/// </summary>
/// <returns></returns>
protected override async Task<AuthenticateResult> 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);
}
}
}
+323
View File
@@ -0,0 +1,323 @@
using System;
using System.Linq;
using xModels.Dtos;
using xIds.DbContext;
using xIds.Interfaces;
using xCommons.Extensions;
using xIds.Configurations;
using xIdentityModels.Dtos;
using xExceptions.Constants;
using System.Threading.Tasks;
using xIdentityModels.Models;
using System.Linq.Expressions;
using IdentityServer4.Extensions;
using xIdentityModels.Extensions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Caching.Memory;
namespace xIds.Providers
{
/// <summary>
/// a Service For Manage Security Applications and also Api Key(s) ...
/// </summary>
public partial class XApplicationProvider : IXApplicationProvider
{
private readonly IMemoryCache cache;
private readonly string permittedRole;
private readonly XIdentityDbContext dbContext;
private readonly IXIdentityManager identityManager;
private readonly XDataServiceConfiguration configuration;
private readonly XApiKeyConfiguration apiKeyConfiguration;
public XApplicationProvider(
IMemoryCache cache,
XIdentityDbContext dbContext,
IXIdentityManager identityManager,
XDataServiceConfiguration configuration,
XApiKeyConfiguration apiKeyConfiguration,
string permittedRole = "admin"
)
{
//
this.cache = cache;
this.dbContext = dbContext;
this.permittedRole = permittedRole;
this.configuration = configuration;
this.identityManager = identityManager;
this.apiKeyConfiguration = apiKeyConfiguration;
}
/// <summary>
/// Retrieve Owner ...
/// </summary>
/// <param name="ownerId"></param>
/// <returns></returns>
public async Task<XPersonDto> GetOwner(string ownerId)
{
//
if (ownerId.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
var user = await identityManager.GetUserAsync(ownerId);
XPersonDto result = user.ToXPerson();
if (result.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
return result;
}
/// <summary>
/// Check Specifed User Info is Owned for Action or not ...
/// </summary>
/// <param name="source"></param>
/// <param name="userInfo"></param>
/// <returns></returns>
public bool IsOwned(
XApplication source,
XUserClaimsInfoDto userInfo = null
)
{
//
var result =
!source.IsNullOrDefault() &&
!userInfo.IsNullOrDefault() &&
(userInfo.UserId == source.OwnerId ||
userInfo.Roles
.Any(r =>
r.ToNormalString() == permittedRole.ToNormalString()));
//
return result;
}
/// <summary>
/// Check Specifed User Info is Owned for Action or not ...
/// </summary>
/// <param name="source"></param>
/// <param name="userInfo"></param>
/// <returns></returns>
public bool IsOwned(
XApplicationDto source,
XUserClaimsInfoDto userInfo = null
)
{
//
var result =
!source.IsNullOrDefault() &&
!userInfo.IsNullOrDefault() &&
(userInfo.UserId == source.OwnerId ||
userInfo.Roles
.Any(r =>
r.ToNormalString() == permittedRole.ToNormalString()));
//
return result;
}
/// <summary>
/// Normalize an XQuery based on Data Service Configurations ...
/// </summary>
/// <param name="source"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public XQuery NormalizeQuery(
XQuery source,
XDataServiceConfiguration configuration //
)
{
//
if (source.IsNull())
{
source = new XQuery();
}
//
// Page Size Normalization ...
if (source.PageSize < configuration.PagingConfiguration.MinAvailablePageSize)
{
source.PageSize = configuration.PagingConfiguration.MinAvailablePageSize;
}
if (source.PageSize > configuration.PagingConfiguration.MaxAvailablePageSize)
{
source.PageSize = configuration.PagingConfiguration.MaxAvailablePageSize;
}
//
return source;
}
/// <summary>
/// Prepare a Queryable of Items ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <returns></returns>
public IQueryable<XApplication> GetApplicationsQeryable(
Expression<Func<XApplication, bool>> predicate = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null
)
{
//
// Global Result ...
var result = dbContext.Applications.AsQueryable();
//
// Handle Filtering ...
if (!predicate.IsNull())
{
result = result.Where(predicate);
}
//
// Handle Including ...
if (!includeBuilder.IsNull())
{
result = includeBuilder(result);
}
//
// Handle Ordering ...
if (!orderBuilder.IsNull())
{
result = orderBuilder(result);
}
//
// Return result ...
return result;
}
/// <summary>
/// Prepare a Queryable of Items ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <returns></returns>
public IQueryable<XApiKey> GetApiKeysQeryable(
Expression<Func<XApiKey, bool>> predicate = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null
)
{
//
// Global Result ...
var result = dbContext.ApiKeys.AsQueryable();
//
// Handle Filtering ...
if (!predicate.IsNull())
{
result = result.Where(predicate);
}
//
// Handle Including ...
if (!includeBuilder.IsNull())
{
result = includeBuilder(result);
}
//
// Handle Ordering ...
if (!orderBuilder.IsNull())
{
result = orderBuilder(result);
}
//
// Return result ...
return result;
}
/// <summary>
/// Prepare a Queryable of Items ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <returns></returns>
public IQueryable<XApiKeyUsage> GetApiKeyUsagesQeryable(
Expression<Func<XApiKeyUsage, bool>> predicate = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null
)
{
//
// Global Result ...
var result = dbContext.ApiKeyUsages.AsQueryable();
//
// Handle Filtering ...
if (!predicate.IsNull())
{
result = result.Where(predicate);
}
//
// Handle Including ...
if (!includeBuilder.IsNull())
{
result = includeBuilder(result);
}
//
// Handle Ordering ...
if (!orderBuilder.IsNull())
{
result = orderBuilder(result);
}
//
// Return result ...
return result;
}
/// <summary>
/// Handle Rate Limit Checking ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="limitPerMinute"></param>
/// <returns></returns>
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;
}
}
}
@@ -0,0 +1,756 @@
using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xIdentityModels.Dtos;
using xExceptions.Constants;
using System.Threading.Tasks;
using xIdentityModels.Models;
using System.Linq.Expressions;
using xDataService.Extensions;
using System.Collections.Generic;
using xIdentityModels.Extensions;
using Microsoft.EntityFrameworkCore;
using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace xIds.Providers
{
public partial class XApplicationProvider
{
//
#region ApiKey ...
/// <summary>
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> AddApiKeyAsync(
XApiKeyDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (!item.IsValidForAdd())
{
XException.InvalidArgs.Throw();
}
//
// Permission ...
if (userInfo.IsNullOrDefault())
{
XException.NotAllowed.Throw();
}
//
// Check Application Exists ...
var application = await GetApplicationAsync(
userInfo: userInfo,
includeBuilder: null,
id: item.ApplicationId,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (application.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Validate Owner ...
if (!IsOwned(application, userInfo))
{
XException.NotAllowed.Throw();
}
//
// Check COunt of API Keys ...
var appApiKeysCount = await dbContext.ApiKeys.CountAsync(
cancellationToken: cancellationToken,
predicate: x => x.ApplicationId == item.ApplicationId
);
if (appApiKeysCount >= apiKeyConfiguration.MaxKeysPerApplication)
{
XException.NotAllowed.Throw();
}
//
item.CreatedOn = DateTime.UtcNow;
//
var entity = item.MapConvert<XApiKey, XApiKeyDto>();
var entry = await dbContext.ApiKeys.AddAsync(
entity: entity,
cancellationToken: cancellationToken
);
var changes = await dbContext.SaveChangesAsync(cancellationToken);
//
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNullOrDefault()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Update Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> UpdateApiKeyAsync(
Guid id,
XApiKeyDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (
id != item.Id ||
!id.IsDefaultGuid() ||
!item.IsValidForAdd() ||
item.IsNullOrDefault() ||
userInfo.IsNullOrDefault()
)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Exists Entity ...
var exists = await dbContext.ApiKeys
.FirstOrDefaultAsync(x => x.Id == id && !x.Deleted);
if (exists.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Permission Checking ...
var application = await GetApplicationAsync(
userInfo: userInfo,
includeBuilder: null,
id: item.ApplicationId,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (application.IsNullOrDefault())
{
XException.NotFound.Throw();
}
if (!IsOwned(application, userInfo))
{
XException.NotAllowed.Throw();
}
//
// Update Data on Exists Entity ...
exists = exists.UpdateData(
updateWith: item,
propertyWhiteList: [],
propertyBlackList: []
);
//
var entry = dbContext.ApiKeys.Attach(exists);
entry.State = EntityState.Modified;
//
var changes = await dbContext.SaveChangesAsync(cancellationToken);
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNullOrDefault()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Remove Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="softDelete"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> RemoveApiKeyAsync(
Guid id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (
id.IsDefaultGuid() ||
userInfo.IsNullOrDefault()
)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Exists Entity ...
var exists = await dbContext.ApiKeys
.FirstOrDefaultAsync(x => x.Id == id && !x.Deleted);
if (exists.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Permission Checking ...
var application = await GetApplicationAsync(
userInfo: userInfo,
includeBuilder: null,
id: exists.ApplicationId,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (application.IsNullOrDefault())
{
XException.NotFound.Throw();
}
if (!IsOwned(application, userInfo))
{
XException.NotAllowed.Throw();
}
//
EntityEntry<XApiKey> entry = null;
if (softDelete)
{
//
exists.Deleted = true;
entry = dbContext.ApiKeys.Update(exists);
}
else
{
entry = dbContext.ApiKeys.Remove(exists);
}
//
var changes = await dbContext.SaveChangesAsync(cancellationToken);
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNull()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve an Item by Providing it's ID ...
/// </summary>
/// <param name="id"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> GetApiKeyAsync(
Guid id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (id.IsDefaultGuid() ||
userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
// Prepare Condition ...
Expression<Func<XApiKey, bool>> predicate = x =>
!ignoreSoftDeleteds || !x.Deleted;
//
// Retrieve Queryable ...
var queryable = GetApiKeysQeryable(
orderBuilder: null,
predicate: predicate,
includeBuilder: includeBuilder
);
//
var entity = await queryable.FirstOrDefaultAsync(
predicate: x => x.Id == id,
cancellationToken: cancellationToken
);
if (entity.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Permission Checking ...
var application = await GetApplicationAsync(
userInfo: userInfo,
includeBuilder: null,
id: entity.ApplicationId,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (application.IsNullOrDefault())
{
XException.NotFound.Throw();
}
if (!IsOwned(application, userInfo))
{
XException.NotAllowed.Throw();
}
//
var result = await EnrichAsync(
source: entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve all Items ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyDto>> GetAllApiKeyAsync(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
var applications = await GetAllApplicationAsync(
userInfo: userInfo,
orderBuilder: null,
includeBuilder: null,
cancellationToken: cancellationToken,
ignoreSoftDeleteds: ignoreSoftDeleteds
);
applications = applications.Where(a => IsOwned(a, userInfo));
var appIds = applications
.Select(a => a.Id)
.ToList();
//
Expression<Func<XApiKey, bool>> predicate = x =>
appIds.Contains(x.ApplicationId) &&
!ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeysQeryable(
predicate: predicate,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder
);
//
var items = await queryable.ToListAsync(cancellationToken);
var result = await EnrichRangeAsync(
sources: items,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve all Items as Async Enumerable ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<XApiKeyDto> GetAllApiKeyAsAsyncEnumerable(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
[EnumeratorCancellation]
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
var applications = await GetAllApplicationAsync(
userInfo: userInfo,
orderBuilder: null,
includeBuilder: null,
cancellationToken: cancellationToken,
ignoreSoftDeleteds: ignoreSoftDeleteds
);
applications = applications.Where(a => IsOwned(a, userInfo));
var appIds = applications
.Select(a => a.Id)
.ToList();
//
Expression<Func<XApiKey, bool>> predicate = x =>
appIds.Contains(x.ApplicationId) &&
!ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeysQeryable(
predicate: predicate,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder
);
//
var enumerator = queryable.AsAsyncEnumerable();
await foreach (var item in enumerator)
{
//
// Handle Cancellation Token ...
if (cancellationToken.IsCancellationRequested)
{
yield break;
}
//
var dto = await EnrichAsync(
source: item,
cancellationToken: cancellationToken
);
yield return dto;
}
}
/// <summary>
/// Find an Item By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> FindOneApiKeyAsync(
Expression<Func<XApiKey, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
Expression<Func<XApiKey, bool>> _predicate = x => !ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeysQeryable(
orderBuilder: null,
predicate: _predicate,
includeBuilder: includeBuilder
);
//
var entity = (await queryable
.ToListAsync(cancellationToken))
.FirstOrDefault(x => predicate.Compile()(x));
//
// Permission Checking ...
var application = await GetApplicationAsync(
userInfo: userInfo,
includeBuilder: null,
id: entity.ApplicationId,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (application.IsNullOrDefault())
{
XException.NotFound.Throw();
}
if (!IsOwned(application, userInfo))
{
XException.NotAllowed.Throw();
}
//
var result = await EnrichAsync(
source: entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Find a Collection of Items By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyDto>> FindManyApiKeyAsync(
Expression<Func<XApiKey, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
Expression<Func<XApiKey, bool>> _predicate = x => !ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeysQeryable(
orderBuilder: null,
predicate: _predicate,
includeBuilder: includeBuilder
);
//
var entities = (await queryable
.ToListAsync(cancellationToken))
.Where(x => predicate.Compile()(x))
.ToList();
//
var appIds = entities
.Select(k => k.ApplicationId)
.ToList();
//
foreach (var appId in appIds)
{
//
// Permission Checking ...
var application = await GetApplicationAsync(
id: appId,
userInfo: userInfo,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (
application.IsNullOrDefault() ||
!IsOwned(application, userInfo))
{
entities = [.. entities.Where(e => e.ApplicationId != appId)];
}
}
//
var result = await EnrichRangeAsync(
sources: entities,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve Items based on XQuery Pagination Structure ...
/// </summary>
/// <param name="query"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XQueryResult<XApiKeyDto>> QueryApiKeyAsync(
XQuery query,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Expression<Func<XApiKey, bool>> predicate = null,
Func<IQueryable<XApiKey>, IOrderedQueryable<XApiKey>> orderBuilder = null,
Func<IQueryable<XApiKey>, IIncludableQueryable<XApiKey, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
// Normalize Query ...
query = NormalizeQuery(query, configuration);
//
Expression<Func<XApiKey, bool>> _predicate = x => !ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeysQeryable(
predicate: _predicate,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder
);
//
var items = (await queryable
.ToListAsync(cancellationToken))
.Where(x => predicate.Compile()(x));
//
var appIds = items
.Select(k => k.ApplicationId)
.ToList();
//
foreach (var appId in appIds)
{
//
// Permission Checking ...
var application = await GetApplicationAsync(
id: appId,
userInfo: userInfo,
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken
);
if (
application.IsNullOrDefault() ||
!IsOwned(application, userInfo))
{
items = [.. items.Where(e => e.ApplicationId != appId)];
}
}
//
var totalItemsCount = items.Count();
//
// Apply Filter ...
if (!query.Filter.IsNullOrEmpty())
{
//
items = items
.AsEnumerable()
.ApplyFilter(query.Filter);
}
int filteredItemsCount = items.Count();
//
// Count Pages ...
var totalPagesCount = query.CountPages(totalItemsCount);
var filteredPagesCount = query.CountPages(filteredItemsCount);
//
// Apply Paging and Sorting ...
if (totalItemsCount > 0 &&
filteredItemsCount > 0
)
{
//
// Apply Sorting ...
items = items
.ToList()
.ApplySorting(
query.SortBy,
query.IsAscending
);
//
// Apply Paging ...
items = items
.ToList()
.ApplyPaging(
query.Page,
query.PageSize
);
}
//
var resultItems = await EnrichRangeAsync(
sources: items,
cancellationToken: cancellationToken
);
//
// Prepare Result ...
var result = new XQueryResult<XApiKeyDto>
{
Page = query.Page,
Items = [.. resultItems],
PageSize = query.PageSize,
TotalPages = totalPagesCount,
TotalItems = totalItemsCount,
TotalFilteredPages = filteredPagesCount,
TotalFilteredItems = filteredItemsCount
};
//
return result;
}
#endregion
}
}
@@ -0,0 +1,526 @@
using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xIdentityModels.Dtos;
using xExceptions.Constants;
using System.Threading.Tasks;
using xIdentityModels.Models;
using System.Linq.Expressions;
using xDataService.Extensions;
using System.Collections.Generic;
using xIdentityModels.Extensions;
using Microsoft.EntityFrameworkCore;
using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace xIds.Providers
{
public partial class XApplicationProvider
{
//
#region ApiKeyUsage ...
/// <summary>
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyUsageDto> AddApiKeyUsageAsync(
XApiKeyUsageDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (!item.IsValidForAdd())
{
XException.InvalidArgs.Throw();
}
//
// Check ApiKey Exists ...
var apiKey = await dbContext.ApiKeys
.FirstOrDefaultAsync(x => x.Id == item.ApiKeyId);
if (apiKey.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Check apiKey Activation ...
if (!apiKey.IsActive())
{
XException.NotAllowed.Throw();
}
//
var entity = item.MapConvert<XApiKeyUsage, XApiKeyUsageDto>();
var entry = await dbContext.ApiKeyUsages.AddAsync(
entity: entity,
cancellationToken: cancellationToken
);
var changes = await dbContext.SaveChangesAsync(cancellationToken);
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNullOrDefault()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Update Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyUsageDto> UpdateApiKeyUsageAsync(
long id,
XApiKeyUsageDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (
id <= 0 ||
id != item.Id ||
!item.IsValidForAdd() ||
item.IsNullOrDefault()
)
{
XException.InvalidArgs.Throw();
}
//
// Check Item Exists ...
var exists = dbContext.ApiKeyUsages.FirstOrDefault(x => x.Id == id);
if (exists.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Update Data on Exists Entity ...
exists = exists.UpdateData(
updateWith: item,
propertyWhiteList: [],
propertyBlackList: [
nameof(XApiKeyUsage.ResponseTimeMs),
]
);
//
var entry = dbContext.ApiKeyUsages.Attach(exists);
entry.State = EntityState.Modified;
//
var changes = await dbContext.SaveChangesAsync(cancellationToken);
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNullOrDefault()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Remove Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="softDelete"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyUsageDto> RemoveApiKeyUsageAsync(
long id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (id <= 0)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Exists Entity ...
var exists = await dbContext.ApiKeyUsages
.FirstOrDefaultAsync(x => x.Id == id && !x.Deleted);
if (exists.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
EntityEntry<XApiKeyUsage> entry = null;
if (softDelete)
{
//
exists.Deleted = true;
entry = dbContext.ApiKeyUsages.Update(exists);
}
else
{
entry = dbContext.ApiKeyUsages.Remove(exists);
}
//
var changes = await dbContext.SaveChangesAsync(cancellationToken);
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNull()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve an Item by Providing it's ID ...
/// </summary>
/// <param name="id"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyUsageDto> GetApiKeyUsageAsync(
long id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (id <= 0)
{
XException.InvalidArgs.Throw();
}
//
// Prepare Condition ...
Expression<Func<XApiKeyUsage, bool>> predicate = x =>
!ignoreSoftDeleteds || !x.Deleted;
//
// Retrieve Queryable ...
var queryable = GetApiKeyUsagesQeryable(
orderBuilder: null,
predicate: predicate,
includeBuilder: includeBuilder
);
//
var entity = await queryable.FirstOrDefaultAsync(
predicate: x => x.Id == id,
cancellationToken: cancellationToken
);
if (entity.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
var result = await EnrichAsync(
source: entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve all Items ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyUsageDto>> GetAllApiKeyUsageAsync(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
Expression<Func<XApiKeyUsage, bool>> predicate = x =>
!ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeyUsagesQeryable(
predicate: predicate,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder
);
//
var items = await queryable.ToListAsync(cancellationToken);
var result = await EnrichRangeAsync(
sources: items,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve all Items as Async Enumerable ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<XApiKeyUsageDto> GetAllApiKeyUsageAsAsyncEnumerable(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
[EnumeratorCancellation]
CancellationToken cancellationToken = default
)
{
//
Expression<Func<XApiKeyUsage, bool>> predicate = x =>
!ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeyUsagesQeryable(
predicate: predicate,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder
);
//
var enumerator = queryable.AsAsyncEnumerable();
await foreach (var item in enumerator)
{
//
// Handle Cancellation Token ...
if (cancellationToken.IsCancellationRequested)
{
yield break;
}
//
var dto = await EnrichAsync(
source: item,
cancellationToken: cancellationToken
);
yield return dto;
}
}
/// <summary>
/// Find an Item By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyUsageDto> FindOneApiKeyUsageAsync(
Expression<Func<XApiKeyUsage, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
Expression<Func<XApiKeyUsage, bool>> _predicate = x => !ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeyUsagesQeryable(
orderBuilder: null,
predicate: _predicate,
includeBuilder: includeBuilder
);
//
var entity = (await queryable
.ToListAsync(cancellationToken))
.FirstOrDefault(x => predicate.Compile()(x));
//
var result = await EnrichAsync(
source: entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Find a Collection of Items By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyUsageDto>> FindManyApiKeyUsageAsync(
Expression<Func<XApiKeyUsage, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
Expression<Func<XApiKeyUsage, bool>> _predicate = x => !ignoreSoftDeleteds || !x.Deleted;
var queryable = GetApiKeyUsagesQeryable(
orderBuilder: null,
predicate: _predicate,
includeBuilder: includeBuilder
);
//
var entities = (await queryable
.ToListAsync(cancellationToken))
.Where(x => predicate.Compile()(x))
.ToList();
//
var result = await EnrichRangeAsync(
sources: entities,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve Items based on XQuery Pagination Structure ...
/// </summary>
/// <param name="query"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XQueryResult<XApiKeyUsageDto>> QueryApiKeyUsageAsync(
XQuery query,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Expression<Func<XApiKeyUsage, bool>> predicate = null,
Func<IQueryable<XApiKeyUsage>, IOrderedQueryable<XApiKeyUsage>> orderBuilder = null,
Func<IQueryable<XApiKeyUsage>, IIncludableQueryable<XApiKeyUsage, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Normalize Query ...
query = NormalizeQuery(query, configuration);
//
var items = await GetAllApiKeyUsageAsync(
userInfo: userInfo,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder,
cancellationToken: cancellationToken,
ignoreSoftDeleteds: ignoreSoftDeleteds
);
var totalItemsCount = items.Count();
//
// Apply Filter ...
if (!query.Filter.IsNullOrEmpty())
{
//
items = items
.ApplyFilter(query.Filter);
}
int filteredItemsCount = items.Count();
//
// Count Pages ...
var totalPagesCount = query.CountPages(totalItemsCount);
var filteredPagesCount = query.CountPages(filteredItemsCount);
//
// Apply Paging and Sorting ...
if (totalItemsCount > 0 &&
filteredItemsCount > 0
)
{
//
// Apply Sorting ...
items = items
.ToList()
.ApplySorting(
query.SortBy,
query.IsAscending
);
//
// Apply Paging ...
items = items
.ToList()
.ApplyPaging(
query.Page,
query.PageSize
);
}
//
// Prepare Result ...
var result = new XQueryResult<XApiKeyUsageDto>
{
Page = query.Page,
Items = [.. items],
PageSize = query.PageSize,
TotalPages = totalPagesCount,
TotalItems = totalItemsCount,
TotalFilteredPages = filteredPagesCount,
TotalFilteredItems = filteredItemsCount
};
//
return result;
}
#endregion
}
}
@@ -0,0 +1,622 @@
using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xIdentityModels.Dtos;
using xExceptions.Constants;
using System.Threading.Tasks;
using xIdentityModels.Models;
using System.Linq.Expressions;
using xDataService.Extensions;
using System.Collections.Generic;
using xIdentityModels.Extensions;
using xIdentityHelper.Extensions;
using Microsoft.EntityFrameworkCore;
using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace xIds.Providers
{
public partial class XApplicationProvider
{
//
#region Application ...
/// <summary>
/// Add Specified Item ...
/// </summary>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApplicationDto> AddApplicationAsync(
XApplicationDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (!item.IsValidForAdd())
{
XException.InvalidArgs.Throw();
}
//
// Permission ...
if (userInfo.IsNullOrDefault())
{
XException.NotAllowed.Throw();
}
//
item.OwnerId = userInfo.UserId;
item.CreatedOn = DateTime.UtcNow;
//
var entity = item.MapConvert<XApplication, XApplicationDto>();
if (entity.Id.IsValidIntId())
{
entity.Id = -1;
}
//
var entry = await dbContext.Applications.AddAsync(
entity: entity,
cancellationToken: cancellationToken
);
var changes = await dbContext.SaveChangesAsync(cancellationToken);
//
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNullOrDefault()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Update Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="item"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApplicationDto> UpdateApplicationAsync(
int id,
XApplicationDto item,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (
id != item.Id ||
!id.IsValidIntId() ||
item.IsNullOrDefault() ||
userInfo.IsNullOrDefault()
)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Exists Entity ...
var exists = await dbContext.Applications
.FirstOrDefaultAsync(x => x.Id == id && !x.Deleted);
if (exists.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Permission Checking ...
if (!IsOwned(exists, userInfo))
{
XException.NotAllowed.Throw();
}
//
item.UpdatedAt = DateTime.UtcNow;
//
// Update Data on Exists Entity ...
exists = exists.UpdateData(
updateWith: item,
propertyWhiteList: [],
propertyBlackList: [
nameof(XApplication.ApiKeys),
]
);
//
var entry = dbContext.Applications.Attach(exists);
entry.State = EntityState.Modified;
//
var changes = await dbContext.SaveChangesAsync(cancellationToken);
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNullOrDefault()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Remove Specified Item ...
/// </summary>
/// <param name="id"></param>
/// <param name="softDelete"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApplicationDto> RemoveApplicationAsync(
int id,
bool softDelete = true,
XUserClaimsInfoDto userInfo = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (
!id.IsValidIntId() ||
userInfo.IsNullOrDefault()
)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Exists Entity ...
var exists = await dbContext.Applications
.FirstOrDefaultAsync(x => x.Id == id && !x.Deleted);
if (exists.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Permission Checking ...
if (!IsOwned(exists, userInfo))
{
XException.NotAllowed.Throw();
}
//
EntityEntry<XApplication> entry = null;
if (softDelete)
{
//
exists.Deleted = true;
entry = dbContext.Applications.Update(exists);
}
else
{
entry = dbContext.Applications.Remove(exists);
}
//
var changes = await dbContext.SaveChangesAsync(cancellationToken);
if (
changes <= 0 ||
entry.IsNull() ||
entry.Entity.IsNull()
)
{
XException.ActionFailed.Throw();
}
//
var result = await EnrichAsync(
source: entry.Entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve an Item by Providing it's ID ...
/// </summary>
/// <param name="id"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApplicationDto> GetApplicationAsync(
int id,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (!id.IsValidIntId() ||
userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
// Prepare Condition ...
Expression<Func<XApplication, bool>> predicate = x =>
x.OwnerId != userInfo.UserId ||
!ignoreSoftDeleteds || !x.Deleted;
if (userInfo.IsAdmin())
{
//
predicate = x =>
!ignoreSoftDeleteds || !x.Deleted;
}
//
// Retrieve Queryable ...
var queryable = GetApplicationsQeryable(
orderBuilder: null,
predicate: predicate,
includeBuilder: includeBuilder
);
//
var entity = await queryable.FirstOrDefaultAsync(
predicate: x => x.Id == id,
cancellationToken: cancellationToken
);
//
var result = await EnrichAsync(
source: entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve all Items ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApplicationDto>> GetAllApplicationAsync(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
// Prepare Condition ...
Expression<Func<XApplication, bool>> predicate = x =>
x.OwnerId == userInfo.UserId &&
(!ignoreSoftDeleteds || !x.Deleted);
if (userInfo.IsAdmin())
{
//
predicate = x =>
!ignoreSoftDeleteds || !x.Deleted;
}
//
// Retrieve Queryable ...
var queryable = GetApplicationsQeryable(
predicate: predicate,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder
);
//
var items = await queryable.ToListAsync(cancellationToken);
var result = await EnrichRangeAsync(
sources: items,
cancellationToken: cancellationToken
);
//
return result;
}
/// <summary>
/// Retrieve all Items as Async Enumerable ...
/// </summary>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async IAsyncEnumerable<XApplicationDto> GetAllApplicationAsAsyncEnumerable(
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
[EnumeratorCancellation]
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
// Prepare Condition ...
Expression<Func<XApplication, bool>> predicate = x =>
x.OwnerId == userInfo.UserId &&
(!ignoreSoftDeleteds || !x.Deleted);
if (userInfo.IsAdmin())
{
//
predicate = x =>
!ignoreSoftDeleteds || !x.Deleted;
}
//
// Retrieve Queryable ...
var queryable = GetApplicationsQeryable(
predicate: predicate,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder
);
//
var enumerator = queryable.AsAsyncEnumerable();
await foreach (var item in enumerator)
{
//
// Handle Cancellation Token ...
if (cancellationToken.IsCancellationRequested)
{
yield break;
}
//
var dto = await EnrichAsync(
source: item,
cancellationToken: cancellationToken
);
yield return dto;
}
}
/// <summary>
/// Find an Item By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApplicationDto> FindOneApplicationAsync(
Expression<Func<XApplication, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
Expression<Func<XApplication, bool>> _predicate = x => !ignoreSoftDeleteds || !x.Deleted;
if (!userInfo.IsAdmin())
{
_predicate = x =>
x.OwnerId == userInfo.UserId &&
(!ignoreSoftDeleteds || !x.Deleted);
}
//
var queryable = GetApplicationsQeryable(
orderBuilder: null,
predicate: _predicate,
includeBuilder: includeBuilder
);
//
var entity = (await queryable
.ToListAsync(cancellationToken))
.FirstOrDefault(x => predicate.Compile()(x));
//
var result = await EnrichAsync(
source: entity,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Find a Collection of Items By Providing a Conditional Expression ...
/// </summary>
/// <param name="predicate"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApplicationDto>> FindManyApplicationAsync(
Expression<Func<XApplication, bool>> predicate,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Validate ...
if (userInfo.IsNullOrDefault())
{
XException.InvalidArgs.Throw();
}
//
Expression<Func<XApplication, bool>> _predicate = x => !ignoreSoftDeleteds || !x.Deleted;
if (!userInfo.IsAdmin())
{
_predicate = x =>
x.OwnerId == userInfo.UserId &&
(!ignoreSoftDeleteds || !x.Deleted);
}
//
var queryable = GetApplicationsQeryable(
orderBuilder: null,
predicate: _predicate,
includeBuilder: includeBuilder
);
//
var entities = (await queryable
.ToListAsync(cancellationToken))
.Where(x => predicate.Compile()(x));
//
var result = await EnrichRangeAsync(
sources: entities,
cancellationToken: cancellationToken
);
return result;
}
/// <summary>
/// Retrieve Items based on XQuery Pagination Structure ...
/// </summary>
/// <param name="query"></param>
/// <param name="ignoreSoftDeleteds"></param>
/// <param name="userInfo"></param>
/// <param name="predicate"></param>
/// <param name="orderBuilder"></param>
/// <param name="includeBuilder"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XQueryResult<XApplicationDto>> QueryApplicationAsync(
XQuery query,
bool ignoreSoftDeleteds = true,
XUserClaimsInfoDto userInfo = null,
Expression<Func<XApplication, bool>> predicate = null,
Func<IQueryable<XApplication>, IOrderedQueryable<XApplication>> orderBuilder = null,
Func<IQueryable<XApplication>, IIncludableQueryable<XApplication, object>> includeBuilder = null,
CancellationToken cancellationToken = default
)
{
//
// Normalize Query ...
query = NormalizeQuery(query, configuration);
//
var items = await GetAllApplicationAsync(
userInfo: userInfo,
orderBuilder: orderBuilder,
includeBuilder: includeBuilder,
cancellationToken: cancellationToken,
ignoreSoftDeleteds: ignoreSoftDeleteds
);
var totalItemsCount = items.Count();
//
// Apply Filter ...
if (!query.Filter.IsNullOrEmpty())
{
//
items = items
.ApplyFilter(query.Filter);
}
int filteredItemsCount = items.Count();
//
// Count Pages ...
var totalPagesCount = query.CountPages(totalItemsCount);
var filteredPagesCount = query.CountPages(filteredItemsCount);
//
// Apply Paging and Sorting ...
if (totalItemsCount > 0 &&
filteredItemsCount > 0
)
{
//
// Apply Sorting ...
items = items
.ToList()
.ApplySorting(
query.SortBy,
query.IsAscending
);
//
// Apply Paging ...
items = items
.ToList()
.ApplyPaging(
query.Page,
query.PageSize
);
}
//
// Prepare Result ...
var result = new XQueryResult<XApplicationDto>
{
Page = query.Page,
Items = [.. items],
PageSize = query.PageSize,
TotalPages = totalPagesCount,
TotalItems = totalItemsCount,
TotalFilteredPages = filteredPagesCount,
TotalFilteredItems = filteredItemsCount
};
//
return result;
}
#endregion
}
}
@@ -0,0 +1,482 @@
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 ...
/// <summary>
/// Generate a New ApiKey and Add It to DB ...
/// </summary>
/// <param name="applicationId"></param>
/// <param name="expiration"></param>
/// <param name="scopes"></param>
/// <param name="allowedIPs"></param>
/// <param name="rateLimit"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> CreateApiKey(
int applicationId,
TimeSpan? expiration = null,
IEnumerable<string> scopes = null,
IEnumerable<string> 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;
}
/// <summary>
/// Revoke Specified Api Key ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> 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;
}
/// <summary>
/// Renew Specified Api Key ...
/// </summary>
/// <param name="apiKeyId"></param>
/// <param name="newExpiration"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> 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;
}
/// <summary>
/// Retrieve All Api Keys for Specified Application ...
/// </summary>
/// <param name="applicationId"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyDto>> 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;
}
/// <summary>
/// Validate Specified Api Key in based on ClientIP ...
/// </summary>
/// <param name="apiKey"></param>
/// <param name="clientIP"></param>
/// <param name="requiredScope"></param>
/// <param name="userInfo"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyValidationResult> 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<string>();
var allowedScopes = (keyModel.AllowedScopes ?? string.Empty)
.FromJSON<IEnumerable<string>>();
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
}
}
@@ -0,0 +1,302 @@
using System.Linq;
using System.Threading;
using xCommons.Extensions;
using xIdentityModels.Dtos;
using System.Threading.Tasks;
using xIdentityModels.Models;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace xIds.Providers
{
public partial class XApplicationProvider
{
//
#region Enrichers ...
/// <summary>
/// Enrich Api Key ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> EnrichAsync(
XApiKey source,
CancellationToken cancellationToken = default
)
{
//
var result = source.MapConvert<XApiKeyDto, XApiKey>();
result = await EnrichAsync(
source: result,
cancellationToken: cancellationToken
);
//
return result;
}
/// <summary>
/// Enrich Api Key ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyDto> EnrichAsync(
XApiKeyDto source,
CancellationToken cancellationToken = default
)
{
//
if (
!source.IsNullOrDefault() &&
!source.Application.IsNullOrDefault()
)
{
//
var application = await dbContext.Applications
.FirstOrDefaultAsync(
x => x.Id == source.ApplicationId,
cancellationToken: cancellationToken
);
if (!application.IsNullOrDefault())
{
//
var dto = await EnrichAsync(
source: application,
cancellationToken: cancellationToken
);
if (!dto.IsNullOrDefault())
{
source.Application = dto;
}
}
}
//
return source;
}
/// <summary>
/// Enrich Api Key Usage ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyUsageDto> EnrichAsync(
XApiKeyUsage source,
CancellationToken cancellationToken = default
)
{
//
var result = source.MapConvert<XApiKeyUsageDto, XApiKeyUsage>();
result = await EnrichAsync(
source: result,
cancellationToken: cancellationToken
);
//
return result;
}
/// <summary>
/// Enrich Application ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApplicationDto> EnrichAsync(
XApplication source,
CancellationToken cancellationToken = default
)
{
//
var result = source.MapConvert<XApplicationDto, XApplication>();
result = await EnrichAsync(
source: result,
cancellationToken: cancellationToken
);
//
return result;
}
/// <summary>
/// Enrich Api Key Usage ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApiKeyUsageDto> EnrichAsync(
XApiKeyUsageDto source,
CancellationToken cancellationToken = default
)
{
//
// There is not any Additional Enrichment ...
var result = await Task.FromResult(source);
return result;
}
/// <summary>
/// Enrich Application ...
/// </summary>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XApplicationDto> EnrichAsync(
XApplicationDto source,
CancellationToken cancellationToken = default
)
{
//
if (
!source.IsNullOrDefault() &&
!source.OwnerId.IsNullOrEmpty()
)
{
//
var owner = await GetOwner(source.OwnerId);
if (!owner.IsNullOrDefault())
{
source.Owner = owner;
}
}
//
return source;
}
/// <summary>
/// Enrich a Collection of Api Key(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyDto>> EnrichRangeAsync(
IEnumerable<XApiKey> sources,
CancellationToken cancellationToken = default
)
{
//
var tasks = sources
.Select(s => EnrichAsync(s, cancellationToken));
//
var result = await Task.WhenAll(tasks);
//
return result;
}
/// <summary>
/// Enrich a Collection of Api Key(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyDto>> EnrichRangeAsync(
IEnumerable<XApiKeyDto> sources,
CancellationToken cancellationToken = default
)
{
//
var tasks = sources
.Select(s => EnrichAsync(s, cancellationToken));
//
var result = await Task.WhenAll(tasks);
//
return result;
}
/// <summary>
/// Enrich a Collection of Api Key Usage(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyUsageDto>> EnrichRangeAsync(
IEnumerable<XApiKeyUsage> sources,
CancellationToken cancellationToken = default
)
{
//
var tasks = sources
.Select(s => EnrichAsync(s, cancellationToken));
//
var result = await Task.WhenAll(tasks);
//
return result;
}
/// <summary>
/// Enrich a Collection of Application(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApplicationDto>> EnrichRangeAsync(
IEnumerable<XApplication> sources,
CancellationToken cancellationToken = default
)
{
//
var tasks = sources
.Select(s => EnrichAsync(s, cancellationToken));
//
var result = await Task.WhenAll(tasks);
//
return result;
}
/// <summary>
/// Enrich a Collection of Api Key Usage(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApiKeyUsageDto>> EnrichRangeAsync(
IEnumerable<XApiKeyUsageDto> sources,
CancellationToken cancellationToken = default
)
{
//
var tasks = sources
.Select(s => EnrichAsync(s, cancellationToken));
//
var result = await Task.WhenAll(tasks);
//
return result;
}
/// <summary>
/// Enrich a Collection of Application(s) ...
/// </summary>
/// <param name="sources"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XApplicationDto>> EnrichRangeAsync(
IEnumerable<XApplicationDto> sources,
CancellationToken cancellationToken = default
)
{
//
var tasks = sources
.Select(s => EnrichAsync(s, cancellationToken));
//
var result = await Task.WhenAll(tasks);
//
return result;
}
#endregion
}
}
+15 -11
View File
@@ -1,20 +1,20 @@
using System; using System;
using xIds.DI;
using System.IO; using System.IO;
using xIds.Constants;
using xIds.Providers;
using Newtonsoft.Json;
using xIds.Interfaces;
using System.Reflection; using System.Reflection;
using xStorageService.DI;
using xCommons.Extensions;
using xCommons.Configurations;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Microsoft.Extensions.Configuration;
using xCommons.Configurations; using Microsoft.Extensions.DependencyInjection;
using xCommons.Extensions;
using xStorageService.DI;
using xIds.Constants;
using xIds.DI;
using xIds.Interfaces;
using xIds.Providers;
namespace xIds namespace xIds
{ {
@@ -57,6 +57,10 @@ namespace xIds
// services.AddInMemoryXIdentityServer (Configuration); // services.AddInMemoryXIdentityServer (Configuration);
services.AddEfSupportXIdentityServer(Configuration); services.AddEfSupportXIdentityServer(Configuration);
//
// Services Application and Api Key Management Services ...
services.AddXApplicationProvider(Configuration);
// //
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore); .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
@@ -71,7 +75,7 @@ namespace xIds
// //
// Register Security Provider ... // Register Security Provider ...
services.AddSingleton<IXSecurityProvider, XSecurityProvider>(); services.AddSingleton<IXSecurityProvider, XSecurityProvider>();
} }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger) public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
+8
View File
@@ -36,6 +36,14 @@
"ThumbQuality": 72, "ThumbQuality": 72,
"MaxFileSize": 41943040 "MaxFileSize": 41943040
}, },
"ApiKeyConfiguration": {
"ApiPrefix": "xapp",
"DefaultRateLimit": 60,
"EnableAuditLog": false,
"MaxKeysPerApplication": "10",
"MaxExpirationMinutes": "525600",
"DefaultExpirationMinutes": "43200"
},
"DbSeeder": { "DbSeeder": {
"UpdateExists": false, "UpdateExists": false,
"Clients": [ "Clients": [
+3 -2
View File
@@ -36,6 +36,7 @@
<!-- Project Dependencies --> <!-- Project Dependencies -->
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Modules\xIdentityHelper\xIdentityHelper.csproj" /> <ProjectReference Include="..\Modules\xIdentityHelper\xIdentityHelper.csproj" />
<ProjectReference Include="..\Modules\xIdentityService\xIdentityService.csproj" />
</ItemGroup> </ItemGroup>
<!-- IdentityServer --> <!-- IdentityServer -->
@@ -72,9 +73,9 @@
<!-- For XML Documentation Support --> <!-- For XML Documentation Support -->
<PropertyGroup> <PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn> <NoWarn>$(NoWarn);1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Create Required Folders --> <!-- Create Required Folders -->