Reworked encryption to use less heap allocated buffers for secrets.
Also some work on plugin system.
This commit is contained in:
parent
8782ef39c6
commit
054754f553
42 changed files with 1452 additions and 242 deletions
2
.editorconfig
Normal file
2
.editorconfig
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[*.cs]
|
||||
resharper_naming_rules.abbreviations = QL, DB
|
||||
|
|
@ -17,10 +17,8 @@ namespace IdentityShroud.Api.Tests.Apis;
|
|||
|
||||
public class ClientApiTests : IClassFixture<ApplicationFactory>
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
TypeInfoResolver = AppJsonSerializerContext.Default,
|
||||
};
|
||||
private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
|
||||
|
||||
private readonly ApplicationFactory _factory;
|
||||
|
||||
public ClientApiTests(ApplicationFactory factory)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using IdentityShroud.Api;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<Using Include="Xunit"/>
|
||||
<Using Include="NSubstitute"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IdentityShroud.Api\IdentityShroud.Api.csproj" />
|
||||
<ProjectReference Include="..\IdentityShroud.TestUtils\IdentityShroud.TestUtils.csproj" />
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ namespace IdentityShroud.Api.Mappers;
|
|||
[Mapper]
|
||||
public partial class ClientMapper
|
||||
{
|
||||
// skipping secret as we do not have the DEK
|
||||
[MapperIgnoreSource(nameof(Client.Secrets))]
|
||||
[MapperIgnoreTarget(nameof(ClientRepresentation.Secret))]
|
||||
public partial ClientRepresentation ToDto(Client client);
|
||||
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ public static class OpenIdEndpoints
|
|||
TokenEndpoint = baseUri + "/openid-connect/token",
|
||||
Issuer = baseUri,
|
||||
JwksUri = baseUri + "/openid-connect/jwks",
|
||||
}, AppJsonSerializerContext.Default.OpenIdConfiguration);
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<Results<Ok<JsonWebKeySet>, BadRequest>> OpenIdConnectJwks(
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using IdentityShroud.Api.Apis;
|
||||
using IdentityShroud.Core.Messages;
|
||||
using IdentityShroud.Core.Messages.Realm;
|
||||
|
||||
namespace IdentityShroud.Api;
|
||||
|
||||
[JsonSerializable(typeof(ClientRepresentation))]
|
||||
[JsonSerializable(typeof(ErrorDto))]
|
||||
[JsonSerializable(typeof(OpenIdConfiguration))]
|
||||
[JsonSerializable(typeof(RealmCreateRequest))]
|
||||
public partial class AppJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishAot>false</PublishAot>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<UserSecretsId>6b8ef434-0577-4a3c-8749-6b547d7787c5</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IdentityShroud.Core\IdentityShroud.Core.csproj" />
|
||||
<ProjectReference Include="..\IdentityShroud.GraphQL\IdentityShroud.GraphQL.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,78 +1,74 @@
|
|||
using FluentValidation;
|
||||
using IdentityShroud.Api;
|
||||
using IdentityShroud.Api.Mappers;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core;
|
||||
using IdentityShroud.Core.EFCore;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Security.Keys;
|
||||
using IdentityShroud.Core.Services;
|
||||
using IdentityShroud.GraphQL;
|
||||
using Serilog;
|
||||
using Serilog.Formatting.Json;
|
||||
|
||||
|
||||
// Initial logging until we can set it up from Configuration
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(new JsonFormatter())
|
||||
.CreateLogger();
|
||||
|
||||
var applicationBuilder = WebApplication.CreateSlimBuilder(args);
|
||||
ConfigureBuilder(applicationBuilder);
|
||||
var application = applicationBuilder.Build();
|
||||
ConfigureApplication(application);
|
||||
application.Run();
|
||||
namespace IdentityShroud.Api;
|
||||
|
||||
void ConfigureBuilder(WebApplicationBuilder builder)
|
||||
public class Program
|
||||
{
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
//services.AddControllers();
|
||||
services.ConfigureHttpJsonOptions(options =>
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
options.SerializerOptions.TypeInfoResolverChain.Insert(0, IdentityShroud.Api.AppJsonSerializerContext.Default);
|
||||
});
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(new JsonFormatter())
|
||||
.CreateLogger();
|
||||
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
services.AddOpenApi();
|
||||
services.AddScoped<Db>();
|
||||
services.AddScoped<IClientService, ClientService>();
|
||||
services.AddSingleton<IClock, ClockService>();
|
||||
services.AddSingleton<IDekEncryptionService, DekEncryptionService>();
|
||||
services.AddScoped<IDataEncryptionService, DataEncryptionService>();
|
||||
services.AddScoped<IRealmContext, RealmContext>();
|
||||
services.AddScoped<IKeyProviderFactory, KeyProviderFactory>();
|
||||
services.AddScoped<IKeyService, KeyService>();
|
||||
services.AddScoped<IRealmService, RealmService>();
|
||||
services.AddOptions<DbConfiguration>().Bind(configuration.GetSection("db"));
|
||||
services.AddSingleton<ISecretProvider, ConfigurationSecretProvider>();
|
||||
services.AddScoped<KeyMapper>();
|
||||
services.AddScoped<IRealmContext, RealmContext>();
|
||||
|
||||
services.AddValidatorsFromAssemblyContaining<RealmCreateRequestValidator>();
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddExceptionHandler<GlobalExceptionHandler>();
|
||||
services.AddProblemDetails();
|
||||
|
||||
builder.Host.UseSerilog((context, services, configuration) => configuration
|
||||
.Enrich.FromLogContext()
|
||||
//.Enrich.With<UserEnricher>()
|
||||
.ReadFrom.Configuration(context.Configuration));
|
||||
}
|
||||
|
||||
void ConfigureApplication(WebApplication app)
|
||||
{
|
||||
app.UseExceptionHandler();
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
var applicationBuilder = WebApplication.CreateSlimBuilder(args);
|
||||
ConfigureBuilder(applicationBuilder);
|
||||
var application = applicationBuilder.Build();
|
||||
ConfigureApplication(application);
|
||||
application.Run();
|
||||
}
|
||||
app.UseSerilogRequestLogging();
|
||||
app.MapApis();
|
||||
|
||||
// app.UseRouting();
|
||||
// app.MapControllers();
|
||||
}
|
||||
|
||||
public partial class Program { }
|
||||
private static void ConfigureBuilder(WebApplicationBuilder builder)
|
||||
{
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
services.AddOptions<DbConfiguration>().Bind(configuration.GetSection("db"));
|
||||
|
||||
// services.ConfigureHttpJsonOptions(options =>
|
||||
// {
|
||||
// options.SerializerOptions.TypeInfoResolverChain.Insert(0, IdentityShroud.Api.AppJsonSerializerContext.Default);
|
||||
// });
|
||||
|
||||
services.AddScoped<KeyMapper>();
|
||||
|
||||
services.AddValidatorsFromAssemblyContaining<RealmCreateRequestValidator>();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddOpenApi();
|
||||
services.AddExceptionHandler<GlobalExceptionHandler>();
|
||||
services.AddProblemDetails();
|
||||
|
||||
services
|
||||
.AddCore()
|
||||
.AddIdentityShroudGraphQL();
|
||||
|
||||
builder.Host.UseSerilog((context, services, configuration) => configuration
|
||||
.Enrich.FromLogContext()
|
||||
//.Enrich.With<UserEnricher>()
|
||||
.ReadFrom.Configuration(context.Configuration));
|
||||
}
|
||||
|
||||
private static void ConfigureApplication(WebApplication app)
|
||||
{
|
||||
app.UseExceptionHandler();
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
app.UseSerilogRequestLogging();
|
||||
app.MapApis();
|
||||
app.MapIdentityShroudGraphQL();
|
||||
|
||||
// app.UseRouting();
|
||||
// app.MapControllers();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "todos",
|
||||
"launchUrl": "graphql",
|
||||
"applicationUrl": "http://localhost:5249",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
|
|
|
|||
48
IdentityShroud.Core.Tests/Security/Jwt/RsaJwtSignerTests.cs
Normal file
48
IdentityShroud.Core.Tests/Security/Jwt/RsaJwtSignerTests.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System.Security.Cryptography;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Security.Keys;
|
||||
using IdentityShroud.Core.Services;
|
||||
|
||||
namespace IdentityShroud.Core.Tests.Security.Jwt;
|
||||
|
||||
public class RsaJwtSignerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Test()
|
||||
{
|
||||
// ISecretProvider secretProvider = Substitute.For<ISecretProvider>();
|
||||
// RealmSigningKey privateKey = new()
|
||||
// {
|
||||
// Id = default,
|
||||
// KeyType = KeyType.RSA,
|
||||
// Key = new EncryptedDek(KekId.NewId(), [1]),
|
||||
// CreatedAt = default,
|
||||
// RevokedAt = null,
|
||||
// Priority = 0,
|
||||
// PublicKeyParameters = null
|
||||
// };
|
||||
DecryptedSigningKey key = new();
|
||||
byte[] jwt = [];
|
||||
|
||||
RsaJwtSigner provider = new();
|
||||
provider.CalculateSignature(JwtSigAlgName.RS256, key, jwt);
|
||||
//
|
||||
// new DekEncryptionService(secretProvider), privateKey,
|
||||
// JwtSigAlgName.RS256);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1024)]
|
||||
[InlineData(2048)]
|
||||
[InlineData(4096)]
|
||||
public void EstimateKeySizeTests(int keySizeBits)
|
||||
{
|
||||
using var rsa = RSA.Create();
|
||||
rsa.KeySize = keySizeBits;
|
||||
byte[] b = rsa.ExportPkcs8PrivateKey();
|
||||
int estimate = DecryptedSigningKey.EstimatePkcs8ExportSize(keySizeBits);
|
||||
Assert.True(b.Length < estimate - 100);
|
||||
}
|
||||
}
|
||||
|
|
@ -25,8 +25,13 @@ public class DekEncryptionServiceTests
|
|||
|
||||
// act
|
||||
DekEncryptionService sut = new(secretProvider);
|
||||
|
||||
EncryptedDek cipher = sut.Encrypt(input.ToArray());
|
||||
byte[] result = sut.Decrypt(cipher);
|
||||
int decryptedSize = sut.GetDecryptedSize(cipher);
|
||||
Assert.Equal(input.Length, decryptedSize);
|
||||
|
||||
var result = new byte[decryptedSize];
|
||||
sut.Decrypt(cipher, result);
|
||||
|
||||
// verify
|
||||
Assert.Equal(input, result);
|
||||
|
|
@ -56,8 +61,10 @@ public class DekEncryptionServiceTests
|
|||
|
||||
// act
|
||||
DekEncryptionService sut = new(secretProvider);
|
||||
int decryptedSize = sut.GetDecryptedSize(secret);
|
||||
var result = new byte[decryptedSize];
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => sut.Decrypt(secret),
|
||||
() => sut.Decrypt(secret, result),
|
||||
ex => ex.Message.Contains("Decryption failed") ? null : "Expected Decryption failed in message");
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +96,8 @@ public class DekEncryptionServiceTests
|
|||
|
||||
// act
|
||||
DekEncryptionService sut = new(secretProvider);
|
||||
byte[] result = sut.Decrypt(secret);
|
||||
byte[] result = new byte[sut.GetDecryptedSize(secret)];
|
||||
sut.Decrypt(secret, result);
|
||||
|
||||
// verify
|
||||
Assert.Equal("Hello, World!"u8, result);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ public class EncryptionTests
|
|||
byte[] keyValue = Convert.FromBase64String("IGd9yUMusjNW0ezv8ink3QWlAHKFH45d21LyrbJTokw=");
|
||||
|
||||
// act
|
||||
byte[] result = Encryption.Decrypt(cipher, keyValue);
|
||||
byte[] result = new byte[Encryption.GetDecryptedLength(cipher)];
|
||||
Encryption.Decrypt(cipher, keyValue, result);
|
||||
|
||||
// verify
|
||||
Assert.Equal("Hello, World!"u8, result);
|
||||
|
|
|
|||
|
|
@ -7,5 +7,7 @@ namespace IdentityShroud.Core.Contracts;
|
|||
public interface IDekEncryptionService
|
||||
{
|
||||
EncryptedDek Encrypt(ReadOnlySpan<byte> plain);
|
||||
byte[] Decrypt(EncryptedDek input);
|
||||
|
||||
void Decrypt(EncryptedDek input, Span<byte> output);
|
||||
int GetDecryptedSize(EncryptedDek input);
|
||||
}
|
||||
38
IdentityShroud.Core/CoreServiceCollectionExtensions.cs
Normal file
38
IdentityShroud.Core/CoreServiceCollectionExtensions.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.EFCore;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Security.Keys;
|
||||
using IdentityShroud.Core.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IdentityShroud.Core;
|
||||
|
||||
public static class CoreServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddCore(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<Db>();
|
||||
|
||||
services.Scan(scan => scan
|
||||
.FromAssemblyOf<RealmService>()
|
||||
.AddClasses(classes => classes.AssignableTo<IJwtSigner>())
|
||||
.AsImplementedInterfaces()
|
||||
.WithSingletonLifetime());
|
||||
services.AddSingleton<IJwtSignerFactory, JwtSignerFactory>();
|
||||
|
||||
services.AddSingleton<IClock, ClockService>();
|
||||
services.AddSingleton<IDekEncryptionService, DekEncryptionService>();
|
||||
services.AddScoped<IDataEncryptionService, DataEncryptionService>();
|
||||
services.AddScoped<IRealmContext, RealmContext>();
|
||||
services.AddScoped<IKeyProviderFactory, KeyProviderFactory>();
|
||||
services.AddScoped<IKeyService, KeyService>();
|
||||
services.AddSingleton<ISecretProvider, ConfigurationSecretProvider>();
|
||||
|
||||
|
||||
services.AddScoped<IClientService, ClientService>();
|
||||
services.AddScoped<IRealmService, RealmService>();
|
||||
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,11 +15,16 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||
<PackageReference Include="Scrutor" Version="7.0.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="FluentResults" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IdentityShroud.PluginSupport\IdentityShroud.PluginSupport.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
66
IdentityShroud.Core/Model/DecryptedSigningKey.cs
Normal file
66
IdentityShroud.Core/Model/DecryptedSigningKey.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System.Security.Cryptography;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Security.Keys;
|
||||
|
||||
namespace IdentityShroud.Core.Model;
|
||||
|
||||
public sealed class DecryptedSigningKey : IDisposable
|
||||
{
|
||||
private readonly byte[] _keyData;
|
||||
private readonly int _keyLength;
|
||||
private bool _disposed;
|
||||
|
||||
public RealmSigningKeyId Id { get; }
|
||||
public KeyType KeyType { get; }
|
||||
public ReadOnlySpan<byte> KeyData => _disposed
|
||||
? throw new ObjectDisposedException(nameof(DecryptedSigningKey))
|
||||
: _keyData.AsSpan(0, _keyLength);
|
||||
|
||||
public DecryptedSigningKey(RealmSigningKey realmSigningKey, IDekEncryptionService encryptionService)
|
||||
{
|
||||
Id = realmSigningKey.Id;
|
||||
KeyType = realmSigningKey.KeyType;
|
||||
int keySize = encryptionService.GetDecryptedSize(realmSigningKey.Key);
|
||||
_keyData = GC.AllocateArray<byte>(keySize, pinned: true);
|
||||
_keyLength = keySize;
|
||||
encryptionService.Decrypt(realmSigningKey.Key, _keyData);
|
||||
}
|
||||
|
||||
public DecryptedSigningKey()
|
||||
{
|
||||
Id = RealmSigningKeyId.NewId();
|
||||
KeyType = KeyType.RSA;
|
||||
const int keySize = 2048;
|
||||
|
||||
using var rsa = RSA.Create();
|
||||
rsa.KeySize = keySize;
|
||||
int estimatedSize = EstimatePkcs8ExportSize(keySize);
|
||||
|
||||
Span<byte> temp = stackalloc byte[estimatedSize * 2];
|
||||
try
|
||||
{
|
||||
if (!rsa.TryExportPkcs8PrivateKey(temp, out int bytesWritten))
|
||||
throw new CryptographicException("Unable to export RSA private key.");
|
||||
|
||||
_keyData = GC.AllocateArray<byte>(bytesWritten, pinned: true);
|
||||
_keyLength = bytesWritten;
|
||||
temp[..bytesWritten].CopyTo(_keyData);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(temp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
CryptographicOperations.ZeroMemory(_keyData);
|
||||
}
|
||||
|
||||
// Note actual accurate coefficients would be *0.566 and +57.4
|
||||
public static int EstimatePkcs8ExportSize(int keySizeBits)
|
||||
=> ((keySizeBits * 6) / 10) + 150;
|
||||
}
|
||||
59
IdentityShroud.Core/Plugins/PluginLoader.cs
Normal file
59
IdentityShroud.Core/Plugins/PluginLoader.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using IdentityShroud.PluginSupport;
|
||||
|
||||
namespace IdentityShroud.Core.Plugins;
|
||||
|
||||
public static class PluginLoader
|
||||
{
|
||||
public static IEnumerable<IPlugin> LoadPlugins(string pluginsFolder)
|
||||
{
|
||||
if (!Directory.Exists(pluginsFolder))
|
||||
yield break;
|
||||
|
||||
foreach (var dll in Directory.EnumerateFiles(pluginsFolder, "*.dll"))
|
||||
{
|
||||
foreach (var plugin in LoadPluginDll(dll)) yield return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IPlugin> LoadPluginDll(string dll)
|
||||
{
|
||||
Assembly asm;
|
||||
try
|
||||
{
|
||||
asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(dll));
|
||||
}
|
||||
catch
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
IEnumerable<Type> pluginTypes;
|
||||
try
|
||||
{
|
||||
pluginTypes = asm.GetTypes()
|
||||
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && t is { IsInterface: false, IsAbstract: false });
|
||||
}
|
||||
catch
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var t in pluginTypes)
|
||||
{
|
||||
IPlugin? instance = null;
|
||||
try
|
||||
{
|
||||
instance = (IPlugin?)Activator.CreateInstance(t);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore bad plugin types
|
||||
}
|
||||
|
||||
if (instance != null)
|
||||
yield return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
IdentityShroud.Core/Plugins/PluginRegistry.cs
Normal file
18
IdentityShroud.Core/Plugins/PluginRegistry.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using IdentityShroud.PluginSupport;
|
||||
|
||||
namespace IdentityShroud.Core.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Note
|
||||
/// </summary>
|
||||
/// <typeparam name="TPlugin"></typeparam>
|
||||
public class PluginRegistry<TPlugin> where TPlugin : IPlugin
|
||||
{
|
||||
private ReadOnlyDictionary<string, TPlugin> _plugins;
|
||||
|
||||
public PluginRegistry(ReadOnlyDictionary<string, TPlugin> plugins)
|
||||
{
|
||||
_plugins = plugins;
|
||||
}
|
||||
}
|
||||
|
|
@ -35,36 +35,45 @@ public static class Encryption
|
|||
return result;
|
||||
}
|
||||
|
||||
public static byte[] Decrypt(ReadOnlyMemory<byte> input, ReadOnlySpan<byte> key)
|
||||
public static void Decrypt(ReadOnlyMemory<byte> input, ReadOnlySpan<byte> key, Span<byte> output)
|
||||
{
|
||||
var payload = input.Span;
|
||||
int versionNumber = (int)payload[0];
|
||||
if (versionNumber != 1)
|
||||
throw new ArgumentException("Invalid payload");
|
||||
|
||||
AlgVersion versionParams = _versions[versionNumber];
|
||||
|
||||
|
||||
if (payload.Length < 1 + versionParams.NonceSize + versionParams.TagSize)
|
||||
throw new ArgumentException("Payload is too short to contain nonce, ciphertext, and tag.", nameof(payload));
|
||||
AlgVersion versionParams = GetVersionParams(input);
|
||||
if (input.Length < 1 + versionParams.NonceSize + versionParams.TagSize)
|
||||
throw new ArgumentException("Cypher data is too short to be valid.", nameof(input));
|
||||
|
||||
var payload = input.Span;
|
||||
ReadOnlySpan<byte> nonce = payload.Slice(1, versionParams.NonceSize);
|
||||
ReadOnlySpan<byte> tag = payload.Slice(1 + versionParams.NonceSize, versionParams.TagSize);
|
||||
ReadOnlySpan<byte> cipher = payload.Slice(1 + versionParams.NonceSize + versionParams.TagSize);
|
||||
|
||||
byte[] plaintext = new byte[cipher.Length];
|
||||
|
||||
using var aes = new AesGcm(key, versionParams.TagSize);
|
||||
try
|
||||
{
|
||||
aes.Decrypt(nonce, cipher, tag, plaintext);
|
||||
aes.Decrypt(nonce, cipher, tag, output);
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
// Tag verification failed → tampering or wrong key/nonce.
|
||||
throw new InvalidOperationException("Decryption failed – authentication tag mismatch.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
public static int GetDecryptedLength(ReadOnlyMemory<byte> input)
|
||||
{
|
||||
AlgVersion versionParams = GetVersionParams(input);
|
||||
int length = input.Length - (1 + versionParams.NonceSize + versionParams.TagSize);
|
||||
if (length < 0)
|
||||
throw new ArgumentException("Cypher data is too short to be valid.", nameof(input));
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private static AlgVersion GetVersionParams(ReadOnlyMemory<byte> input)
|
||||
{
|
||||
var versionNumber = (int)input.Span[0];
|
||||
if (versionNumber != 1)
|
||||
throw new ArgumentException("Invalid payload");
|
||||
|
||||
return _versions[versionNumber];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
using System.Text.Json;
|
||||
using IdentityShroud.Core.Model;
|
||||
|
||||
namespace IdentityShroud.Core;
|
||||
|
||||
public interface IJwtSignatureProvider : IDisposable
|
||||
public interface IJwtSigner
|
||||
{
|
||||
/*
|
||||
Of the signature and MAC algorithms specified in JSON Web Algorithms
|
||||
|
|
@ -13,14 +14,7 @@ public interface IJwtSignatureProvider : IDisposable
|
|||
hash algorithm ("ES256"). Support for other algorithms and key sizes
|
||||
is OPTIONAL.
|
||||
*/
|
||||
IReadOnlyList<JwtSigAlgName> Algorithms { get; }
|
||||
|
||||
void WriteJwtHeaderFields(Utf8JsonWriter writer);
|
||||
/// <summary>
|
||||
/// Length of the binary signature in bytes.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
int GetSignatureLength();
|
||||
|
||||
void CalculateSignature(ReadOnlySpan<byte> jwt, Span<byte> signatureOut);
|
||||
|
||||
byte[] CalculateSignature(JwtSigAlgName algName, DecryptedSigningKey key, ReadOnlySpan<byte> jwt);
|
||||
}
|
||||
6
IdentityShroud.Core/Security/Jwt/IJwtSignerFactory.cs
Normal file
6
IdentityShroud.Core/Security/Jwt/IJwtSignerFactory.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace IdentityShroud.Core;
|
||||
|
||||
public interface IJwtSignerFactory
|
||||
{
|
||||
IJwtSigner Create(JwtSigAlgName algorithm);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using System.Buffers.Text;
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using IdentityShroud.Core.Model;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
namespace IdentityShroud.Core;
|
||||
|
|
@ -40,49 +41,48 @@ public static class JwtSignatureGenerator
|
|||
|
||||
}
|
||||
|
||||
public static class JwtCreator
|
||||
public class JwtService(IJwtSignerFactory signerFactory)
|
||||
{
|
||||
|
||||
public static byte[] CreateEncodedJwt(ReadOnlySpan<byte> payloadUtf8, IJwtSignatureProvider signatureProvider)
|
||||
public byte[] CreateEncodedJwt(ReadOnlySpan<byte> payloadUtf8, JwtSigAlgName algName, DecryptedSigningKey key)
|
||||
{
|
||||
MemoryStream memStream = new();
|
||||
Utf8JsonWriter writer = new(memStream);
|
||||
WriteJwtHeader(writer, signatureProvider);
|
||||
writer.Flush();
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
// LATER might be able to improve performance using ArrayPool
|
||||
|
||||
IJwtSigner signer = signerFactory.Create(algName);
|
||||
MemoryStream headerMemStream = new();
|
||||
Utf8JsonWriter headerWriter = new(headerMemStream);
|
||||
WriteJwtHeader(headerWriter, algName, key.Id.ToString());
|
||||
headerWriter.Flush();
|
||||
headerMemStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
int headerBase64Length = Base64Url.GetEncodedLength((int)memStream.Length);
|
||||
int headerBase64Length = Base64Url.GetEncodedLength((int)headerMemStream.Length);
|
||||
int payloadBase64Length = Base64Url.GetEncodedLength(payloadUtf8.Length);
|
||||
int signatureBase64Length = Base64Url.GetEncodedLength(signatureProvider.GetSignatureLength());
|
||||
int totalLength = headerBase64Length + 1 + payloadBase64Length + 1 + signatureBase64Length;
|
||||
|
||||
var completeJwt = new byte[totalLength];
|
||||
var jwtData = new byte[headerBase64Length + payloadBase64Length + 1];
|
||||
|
||||
//
|
||||
var byteArray = new byte[memStream.Length];
|
||||
memStream.ReadExactly(byteArray, 0, (int)memStream.Length);
|
||||
int written = Base64Url.EncodeToUtf8(byteArray, completeJwt);
|
||||
var byteArray = new byte[headerMemStream.Length];
|
||||
headerMemStream.ReadExactly(byteArray, 0, (int)headerMemStream.Length);
|
||||
int written = Base64Url.EncodeToUtf8(byteArray, jwtData);
|
||||
|
||||
if (written != headerBase64Length)
|
||||
throw new Exception("expected header length did not match bytes written");
|
||||
|
||||
completeJwt[headerBase64Length] = (byte)'.';
|
||||
jwtData[headerBase64Length] = (byte)'.';
|
||||
|
||||
written = Base64Url.EncodeToUtf8(payloadUtf8, completeJwt.AsSpan().Slice(headerBase64Length + 1, payloadBase64Length));
|
||||
written = Base64Url.EncodeToUtf8(payloadUtf8, jwtData.AsSpan().Slice(headerBase64Length + 1, payloadBase64Length));
|
||||
|
||||
if (written != payloadBase64Length)
|
||||
throw new Exception("expected payload length did not match bytes written");
|
||||
|
||||
completeJwt[headerBase64Length + 1 + payloadBase64Length] = (byte)'.';
|
||||
byte[] signature = signer.CalculateSignature(algName, key, jwtData.AsSpan());
|
||||
|
||||
int signatureBase64Length = Base64Url.GetEncodedLength(signature.Length);
|
||||
|
||||
Span<byte> signature = stackalloc byte[signatureProvider.GetSignatureLength()];
|
||||
signatureProvider.CalculateSignature(
|
||||
completeJwt.AsSpan().Slice(0, headerBase64Length + 1 + payloadBase64Length),
|
||||
signature);
|
||||
|
||||
written = Base64Url.EncodeToUtf8(signature, completeJwt.AsSpan()
|
||||
.Slice(headerBase64Length + 1 + payloadBase64Length + 1));
|
||||
var completeJwt = new byte[jwtData.Length + 1 + signatureBase64Length];
|
||||
Array.Copy(jwtData, completeJwt, jwtData.Length);
|
||||
completeJwt[jwtData.Length] = (byte)'.';
|
||||
|
||||
written = Base64Url.EncodeToUtf8(signature, completeJwt.AsSpan().Slice(jwtData.Length + 1, signatureBase64Length));
|
||||
|
||||
if (written != signatureBase64Length)
|
||||
throw new Exception("expected signature length did not match bytes written");
|
||||
|
|
@ -90,11 +90,12 @@ public static class JwtCreator
|
|||
return completeJwt;
|
||||
}
|
||||
|
||||
private static void WriteJwtHeader(Utf8JsonWriter writer, IJwtSignatureProvider signatureProvider)
|
||||
private static void WriteJwtHeader(Utf8JsonWriter writer, JwtSigAlgName algName, string keyId)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("typ"u8, "JWT"u8);
|
||||
signatureProvider.WriteJwtHeaderFields(writer);
|
||||
writer.WriteString("alg"u8, algName.ToString());
|
||||
writer.WriteString("kid"u8, keyId);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
17
IdentityShroud.Core/Security/Jwt/JwtSignerFactory.cs
Normal file
17
IdentityShroud.Core/Security/Jwt/JwtSignerFactory.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace IdentityShroud.Core;
|
||||
|
||||
public class JwtSignerFactory(IEnumerable<IJwtSigner> signers) : IJwtSignerFactory
|
||||
{
|
||||
private readonly IReadOnlyDictionary<JwtSigAlgName, IJwtSigner> _signers = signers
|
||||
.SelectMany(s => s.Algorithms.Select(alg => (alg, signer: s)))
|
||||
.ToDictionary(x => x.alg, x => x.signer);
|
||||
|
||||
public IJwtSigner Create(JwtSigAlgName algorithm)
|
||||
{
|
||||
if (_signers.TryGetValue(algorithm, out var signer))
|
||||
return signer;
|
||||
|
||||
throw new NotSupportedException($"JWT signing algorithm '{algorithm}' is not registered.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Services;
|
||||
|
||||
namespace IdentityShroud.Core;
|
||||
|
||||
public class SignatureProviderFactory(DekEncryptionService dekCryptor, IServiceProvider services)
|
||||
{
|
||||
|
||||
public static void SelectAlgorithmAndKey(Realm realm, Client client, out JwtSigAlgName alg, out byte[] key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IJwtSignatureProvider Create(JwtSigAlgName algorithm, byte[] keyData)
|
||||
{
|
||||
|
||||
|
||||
//realm.DefaultSignatureAlgorithm
|
||||
//realm.TokenSigningKeys
|
||||
|
||||
//IJwtSignatureProvider? sigProvider = services.GetKeyedService<IJwtSignatureProvider>();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class RsaJwtSignatureProvider : IJwtSignatureProvider
|
||||
{
|
||||
private JwtSigAlgName _sigAlgName;
|
||||
private RealmSigningKeyId _keyId;
|
||||
private readonly RSA _rsa;
|
||||
|
||||
public RsaJwtSignatureProvider(DekEncryptionService dekCryptor,
|
||||
RealmSigningKey privateKey,
|
||||
JwtSigAlgName sigAlgName)
|
||||
{
|
||||
_sigAlgName = sigAlgName;
|
||||
_keyId = privateKey.Id;
|
||||
|
||||
byte[] key = dekCryptor.Decrypt(privateKey.Key);
|
||||
_rsa = RSA.Create();
|
||||
_rsa.ImportPkcs8PrivateKey(key, out int _);
|
||||
}
|
||||
|
||||
/*
|
||||
+-------------------+---------------------------------+
|
||||
| "alg" Param Value | Digital Signature Algorithm |
|
||||
+-------------------+---------------------------------+
|
||||
| RS256 | RSASSA-PKCS1-v1_5 using SHA-256 |
|
||||
| RS384 | RSASSA-PKCS1-v1_5 using SHA-384 |
|
||||
| RS512 | RSASSA-PKCS1-v1_5 using SHA-512 |
|
||||
+-------------------+---------------------------------+
|
||||
*/
|
||||
|
||||
|
||||
public void WriteJwtHeaderFields(Utf8JsonWriter writer)
|
||||
{
|
||||
writer.WriteString("alg"u8, _sigAlgName.ToString());
|
||||
writer.WriteString("kid"u8, _keyId.ToString());
|
||||
}
|
||||
|
||||
public int GetSignatureLength()
|
||||
{
|
||||
return _rsa.KeySize / 8;
|
||||
}
|
||||
|
||||
public void CalculateSignature(ReadOnlySpan<byte> jwt, Span<byte> sig)
|
||||
{
|
||||
_rsa.SignData(jwt, sig, GetHashAlgorithmName(), RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_rsa.Dispose();
|
||||
}
|
||||
|
||||
private HashAlgorithmName GetHashAlgorithmName()
|
||||
=> _sigAlgName.Name switch
|
||||
{
|
||||
"RS256" => HashAlgorithmName.SHA256,
|
||||
"RS384" => HashAlgorithmName.SHA384,
|
||||
"RS512" => HashAlgorithmName.SHA512,
|
||||
_ => throw new ArgumentException("Invalid algorithm for RsaJwtSignatureProvider")
|
||||
};
|
||||
|
||||
}
|
||||
36
IdentityShroud.Core/Security/Jwt/RsaJwtSigner.cs
Normal file
36
IdentityShroud.Core/Security/Jwt/RsaJwtSigner.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System.Security.Cryptography;
|
||||
using IdentityShroud.Core.Model;
|
||||
|
||||
namespace IdentityShroud.Core;
|
||||
|
||||
public class RsaJwtSigner : IJwtSigner
|
||||
{
|
||||
public IReadOnlyList<JwtSigAlgName> Algorithms => [JwtSigAlgName.RS256, JwtSigAlgName.RS384, JwtSigAlgName.RS512];
|
||||
|
||||
// +-------------------+---------------------------------+
|
||||
// | "alg" Param Value | Digital Signature Algorithm |
|
||||
// +-------------------+---------------------------------+
|
||||
// | RS256 | RSASSA-PKCS1-v1_5 using SHA-256 |
|
||||
// | RS384 | RSASSA-PKCS1-v1_5 using SHA-384 |
|
||||
// | RS512 | RSASSA-PKCS1-v1_5 using SHA-512 |
|
||||
// +-------------------+---------------------------------+
|
||||
|
||||
public byte[] CalculateSignature(JwtSigAlgName algName, DecryptedSigningKey key, ReadOnlySpan<byte> jwt)
|
||||
{
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportPkcs8PrivateKey(key.KeyData, out int _);
|
||||
var sig = new byte[rsa.KeySize / 8];
|
||||
rsa.SignData(jwt, sig, GetHashAlgorithmName(algName), RSASignaturePadding.Pkcs1);
|
||||
return sig;
|
||||
}
|
||||
|
||||
private static HashAlgorithmName GetHashAlgorithmName(JwtSigAlgName algName)
|
||||
=> algName.Name switch
|
||||
{
|
||||
"RS256" => HashAlgorithmName.SHA256,
|
||||
"RS384" => HashAlgorithmName.SHA384,
|
||||
"RS512" => HashAlgorithmName.SHA512,
|
||||
_ => throw new ArgumentException("Invalid algorithm for RsaJwtSignatureProvider")
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Security.Cryptography;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Security;
|
||||
|
|
@ -9,9 +10,17 @@ public class DataEncryptionService(
|
|||
{
|
||||
public EncryptedValue Encrypt(RealmDek dek, ReadOnlySpan<byte> plain)
|
||||
{
|
||||
var key = dekCryptor.Decrypt(dek.KeyData);
|
||||
byte[] cipher = Encryption.Encrypt(plain, key);
|
||||
return new (dek.Id, cipher);
|
||||
Span<byte> key = stackalloc byte[dekCryptor.GetDecryptedSize(dek.KeyData)];
|
||||
try
|
||||
{
|
||||
dekCryptor.Decrypt(dek.KeyData, key);
|
||||
byte[] cipher = Encryption.Encrypt(plain, key);
|
||||
return new (dek.Id, cipher);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(key);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Decrypt(IReadOnlyList<RealmDek> deks, EncryptedValue input)
|
||||
|
|
@ -20,8 +29,19 @@ public class DataEncryptionService(
|
|||
// - Old key removed before migration completed (should not be possible)
|
||||
// - Wrong keyset because of programming error.
|
||||
var dek = deks.SingleOrDefault(d => d.Id == input.DekId)
|
||||
?? throw new InvalidOperationException("Required key not found");
|
||||
var key = dekCryptor.Decrypt(dek.KeyData);
|
||||
return Encryption.Decrypt(input.Value, key);
|
||||
?? throw new InvalidOperationException("Required key not found");
|
||||
|
||||
Span<byte> key = stackalloc byte[dekCryptor.GetDecryptedSize(dek.KeyData)];
|
||||
try
|
||||
{
|
||||
dekCryptor.Decrypt(dek.KeyData, key);
|
||||
byte[] output = new byte[Encryption.GetDecryptedLength(input.Value)];
|
||||
Encryption.Decrypt(input.Value, key, output);
|
||||
return output;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CryptographicOperations.ZeroMemory(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,8 +18,6 @@ public class DekEncryptionService : IDekEncryptionService
|
|||
public DekEncryptionService(ISecretProvider secretProvider)
|
||||
{
|
||||
_encryptionKeys = secretProvider.GetKeys("master");
|
||||
// if (_encryptionKey.Length != 32) // 256‑bit key
|
||||
// throw new Exception("Key must be 256 bits (32 bytes) for AES‑256‑GCM.");
|
||||
}
|
||||
|
||||
public EncryptedDek Encrypt(ReadOnlySpan<byte> plaintext)
|
||||
|
|
@ -29,10 +27,14 @@ public class DekEncryptionService : IDekEncryptionService
|
|||
return new (encryptionKey.Id, cipher);
|
||||
}
|
||||
|
||||
public byte[] Decrypt(EncryptedDek input)
|
||||
public void Decrypt(EncryptedDek input, Span<byte> output)
|
||||
{
|
||||
var encryptionKey = GetKey(input.KekId);
|
||||
Encryption.Decrypt(input.Value, encryptionKey.Key, output);
|
||||
}
|
||||
|
||||
return Encryption.Decrypt(input.Value, encryptionKey.Key);
|
||||
public int GetDecryptedSize(EncryptedDek input)
|
||||
{
|
||||
return Encryption.GetDecryptedLength(input.Value);
|
||||
}
|
||||
}
|
||||
17
IdentityShroud.GraphQL/IdentityShroud.GraphQL.csproj
Normal file
17
IdentityShroud.GraphQL/IdentityShroud.GraphQL.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IdentityShroud.Core\IdentityShroud.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
26
IdentityShroud.GraphQL/Query.cs
Normal file
26
IdentityShroud.GraphQL/Query.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
susing IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.Model;
|
||||
|
||||
namespace IdentityShroud.GraphQL;
|
||||
|
||||
public class Query
|
||||
{
|
||||
public string GetHello() => "Hello, world!";
|
||||
|
||||
public async Task<Realm?> GetRealms(
|
||||
Guid id,
|
||||
[Service] IRealmService realmService)
|
||||
{
|
||||
return await realmService.FindById(id);
|
||||
}
|
||||
}
|
||||
|
||||
public class Mutation
|
||||
{
|
||||
public async Task<Realm> RealmCreate(string name)
|
||||
{
|
||||
Realm r = new();
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
31
IdentityShroud.GraphQL/RegistrationExtensions.cs
Normal file
31
IdentityShroud.GraphQL/RegistrationExtensions.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace IdentityShroud.GraphQL;
|
||||
|
||||
public static class RegistrationExtensions
|
||||
{
|
||||
extension(IServiceCollection services)
|
||||
{
|
||||
public IServiceCollection AddIdentityShroudGraphQL()
|
||||
{
|
||||
services
|
||||
.AddGraphQLServer()
|
||||
.AddMutationConventions(applyToAllMutations: true)
|
||||
.AddMutationType<Mutation>()
|
||||
.AddQueryType<Query>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
extension(IEndpointRouteBuilder app)
|
||||
{
|
||||
public IEndpointRouteBuilder MapIdentityShroudGraphQL()
|
||||
{
|
||||
app.MapGraphQL();
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
318
IdentityShroud.Migrations/Migrations/20260412083710_Initial.Designer.cs
generated
Normal file
318
IdentityShroud.Migrations/Migrations/20260412083710_Initial.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IdentityShroud.Core.EFCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IdentityShroud.Migrations.Migrations
|
||||
{
|
||||
[DbContext(typeof(Db))]
|
||||
[Migration("20260412083710_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowClientCredentialsFlow")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("allow_client_credentials_flow");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<bool>("Confidential")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("confidential");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("character varying(80)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<Guid>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.Property<string>("SignatureAlgorithm")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("signature_algorithm");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_client");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_client_client_id");
|
||||
|
||||
b.HasIndex("RealmId")
|
||||
.HasDatabaseName("ix_client_realm_id");
|
||||
|
||||
b.ToTable("client", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<Guid>("ClientId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<int?>("ClientId1")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("client_id1");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<DateTime?>("Expires")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expires");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("revoked_at");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "Secret", "IdentityShroud.Core.Model.ClientSecret.Secret#EncryptedValue", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<Guid>("DekId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("secret_dek_id");
|
||||
|
||||
b1.Property<byte[]>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("secret_value");
|
||||
});
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_client_secret");
|
||||
|
||||
b.HasIndex("ClientId1")
|
||||
.HasDatabaseName("ix_client_secret_client_id1");
|
||||
|
||||
b.ToTable("client_secret", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("DefaultSignatureAlgorithm")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("default_signature_algorithm");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasColumnName("slug");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_realm");
|
||||
|
||||
b.ToTable("realm", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("active");
|
||||
|
||||
b.Property<string>("Algorithm")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("algorithm");
|
||||
|
||||
b.Property<Guid>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "KeyData", "IdentityShroud.Core.Model.RealmDek.KeyData#EncryptedDek", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<Guid>("KekId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("key_data_kek_id");
|
||||
|
||||
b1.Property<byte[]>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("key_data_value");
|
||||
});
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_realm_dek");
|
||||
|
||||
b.HasIndex("RealmId")
|
||||
.HasDatabaseName("ix_realm_dek_realm_id");
|
||||
|
||||
b.ToTable("realm_dek", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<string>("KeyType")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("key_type");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<string>("PublicKeyParameters")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("public_key_parameters");
|
||||
|
||||
b.Property<Guid?>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("revoked_at");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "Key", "IdentityShroud.Core.Model.RealmSigningKey.Key#EncryptedDek", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<Guid>("KekId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("key_kek_id");
|
||||
|
||||
b1.Property<byte[]>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("key_value");
|
||||
});
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_realm_key");
|
||||
|
||||
b.HasIndex("RealmId")
|
||||
.HasDatabaseName("ix_realm_key_realm_id");
|
||||
|
||||
b.ToTable("realm_key", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Realm", null)
|
||||
.WithMany("Clients")
|
||||
.HasForeignKey("RealmId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_client_realm_realm_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Client", null)
|
||||
.WithMany("Secrets")
|
||||
.HasForeignKey("ClientId1")
|
||||
.HasConstraintName("fk_client_secret_client_client_id1");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Realm", null)
|
||||
.WithMany("DataEncryptionKeys")
|
||||
.HasForeignKey("RealmId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_realm_dek_realm_realm_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Realm", null)
|
||||
.WithMany("TokenSigningKeys")
|
||||
.HasForeignKey("RealmId")
|
||||
.HasConstraintName("fk_realm_key_realm_realm_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
|
||||
{
|
||||
b.Navigation("Secrets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
|
||||
{
|
||||
b.Navigation("Clients");
|
||||
|
||||
b.Navigation("DataEncryptionKeys");
|
||||
|
||||
b.Navigation("TokenSigningKeys");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
171
IdentityShroud.Migrations/Migrations/20260412083710_Initial.cs
Normal file
171
IdentityShroud.Migrations/Migrations/20260412083710_Initial.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IdentityShroud.Migrations.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "realm",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
slug = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false),
|
||||
name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
default_signature_algorithm = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_realm", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "client",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
client_id = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false),
|
||||
name = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: true),
|
||||
description = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
signature_algorithm = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),
|
||||
confidential = table.Column<bool>(type: "boolean", nullable: false),
|
||||
allow_client_credentials_flow = table.Column<bool>(type: "boolean", nullable: false),
|
||||
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_client", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_client_realm_realm_id",
|
||||
column: x => x.realm_id,
|
||||
principalTable: "realm",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "realm_dek",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
active = table.Column<bool>(type: "boolean", nullable: false),
|
||||
algorithm = table.Column<string>(type: "text", nullable: false),
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
key_data_kek_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
key_data_value = table.Column<byte[]>(type: "bytea", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_realm_dek", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_realm_dek_realm_realm_id",
|
||||
column: x => x.realm_id,
|
||||
principalTable: "realm",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "realm_key",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
key_type = table.Column<string>(type: "text", nullable: false),
|
||||
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
revoked_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
priority = table.Column<int>(type: "integer", nullable: false),
|
||||
public_key_parameters = table.Column<string>(type: "jsonb", nullable: true),
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
key_kek_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
key_value = table.Column<byte[]>(type: "bytea", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_realm_key", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_realm_key_realm_realm_id",
|
||||
column: x => x.realm_id,
|
||||
principalTable: "realm",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "client_secret",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
client_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
expires = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
revoked_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
client_id1 = table.Column<int>(type: "integer", nullable: true),
|
||||
secret_dek_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
secret_value = table.Column<byte[]>(type: "bytea", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_client_secret", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_client_secret_client_client_id1",
|
||||
column: x => x.client_id1,
|
||||
principalTable: "client",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_client_client_id",
|
||||
table: "client",
|
||||
column: "client_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_client_realm_id",
|
||||
table: "client",
|
||||
column: "realm_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_client_secret_client_id1",
|
||||
table: "client_secret",
|
||||
column: "client_id1");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_realm_dek_realm_id",
|
||||
table: "realm_dek",
|
||||
column: "realm_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_realm_key_realm_id",
|
||||
table: "realm_key",
|
||||
column: "realm_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "client_secret");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "realm_dek");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "realm_key");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "client");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "realm");
|
||||
}
|
||||
}
|
||||
}
|
||||
315
IdentityShroud.Migrations/Migrations/DbModelSnapshot.cs
Normal file
315
IdentityShroud.Migrations/Migrations/DbModelSnapshot.cs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IdentityShroud.Core.EFCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IdentityShroud.Migrations.Migrations
|
||||
{
|
||||
[DbContext(typeof(Db))]
|
||||
partial class DbModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowClientCredentialsFlow")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("allow_client_credentials_flow");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<bool>("Confidential")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("confidential");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("character varying(80)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<Guid>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.Property<string>("SignatureAlgorithm")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("signature_algorithm");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_client");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_client_client_id");
|
||||
|
||||
b.HasIndex("RealmId")
|
||||
.HasDatabaseName("ix_client_realm_id");
|
||||
|
||||
b.ToTable("client", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<Guid>("ClientId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("client_id");
|
||||
|
||||
b.Property<int?>("ClientId1")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("client_id1");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<DateTime?>("Expires")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expires");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("revoked_at");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "Secret", "IdentityShroud.Core.Model.ClientSecret.Secret#EncryptedValue", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<Guid>("DekId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("secret_dek_id");
|
||||
|
||||
b1.Property<byte[]>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("secret_value");
|
||||
});
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_client_secret");
|
||||
|
||||
b.HasIndex("ClientId1")
|
||||
.HasDatabaseName("ix_client_secret_client_id1");
|
||||
|
||||
b.ToTable("client_secret", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("DefaultSignatureAlgorithm")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("default_signature_algorithm");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasColumnName("slug");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_realm");
|
||||
|
||||
b.ToTable("realm", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("active");
|
||||
|
||||
b.Property<string>("Algorithm")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("algorithm");
|
||||
|
||||
b.Property<Guid>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "KeyData", "IdentityShroud.Core.Model.RealmDek.KeyData#EncryptedDek", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<Guid>("KekId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("key_data_kek_id");
|
||||
|
||||
b1.Property<byte[]>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("key_data_value");
|
||||
});
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_realm_dek");
|
||||
|
||||
b.HasIndex("RealmId")
|
||||
.HasDatabaseName("ix_realm_dek_realm_id");
|
||||
|
||||
b.ToTable("realm_dek", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<string>("KeyType")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("key_type");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<string>("PublicKeyParameters")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("public_key_parameters");
|
||||
|
||||
b.Property<Guid?>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("revoked_at");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "Key", "IdentityShroud.Core.Model.RealmSigningKey.Key#EncryptedDek", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<Guid>("KekId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("key_kek_id");
|
||||
|
||||
b1.Property<byte[]>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("key_value");
|
||||
});
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_realm_key");
|
||||
|
||||
b.HasIndex("RealmId")
|
||||
.HasDatabaseName("ix_realm_key_realm_id");
|
||||
|
||||
b.ToTable("realm_key", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Realm", null)
|
||||
.WithMany("Clients")
|
||||
.HasForeignKey("RealmId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_client_realm_realm_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.ClientSecret", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Client", null)
|
||||
.WithMany("Secrets")
|
||||
.HasForeignKey("ClientId1")
|
||||
.HasConstraintName("fk_client_secret_client_client_id1");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmDek", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Realm", null)
|
||||
.WithMany("DataEncryptionKeys")
|
||||
.HasForeignKey("RealmId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_realm_dek_realm_realm_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.RealmSigningKey", b =>
|
||||
{
|
||||
b.HasOne("IdentityShroud.Core.Model.Realm", null)
|
||||
.WithMany("TokenSigningKeys")
|
||||
.HasForeignKey("RealmId")
|
||||
.HasConstraintName("fk_realm_key_realm_realm_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Client", b =>
|
||||
{
|
||||
b.Navigation("Secrets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityShroud.Core.Model.Realm", b =>
|
||||
{
|
||||
b.Navigation("Clients");
|
||||
|
||||
b.Navigation("DataEncryptionKeys");
|
||||
|
||||
b.Navigation("TokenSigningKeys");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
9
IdentityShroud.PluginSupport/IPlugin.cs
Normal file
9
IdentityShroud.PluginSupport/IPlugin.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace IdentityShroud.PluginSupport;
|
||||
|
||||
/// <summary>
|
||||
/// Any class that should be discovered when loading a dll should implement IPlugin
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
15
IdentityShroud.SecretProviders/ISecretProvider.cs
Normal file
15
IdentityShroud.SecretProviders/ISecretProvider.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace IdentityShroud.SecretProviders;
|
||||
|
||||
/// <summary>
|
||||
/// Required interface of a SecretProvider.
|
||||
/// </summary>
|
||||
public interface ISecretProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Used as a key in the registry. This same value should be used for the type field
|
||||
/// when configuring a secret.
|
||||
/// </summary>
|
||||
string Key { get; }
|
||||
|
||||
Task<PlainSecret> GetSecret(string configurationValue, CancellationToken ct = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IdentityShroud.PluginSupport\IdentityShroud.PluginSupport.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
IdentityShroud.SecretProviders/PlainSecret.cs
Normal file
18
IdentityShroud.SecretProviders/PlainSecret.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System.Security.Cryptography;
|
||||
|
||||
namespace IdentityShroud.SecretProviders;
|
||||
|
||||
public sealed class PlainSecret(byte[] secret) : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
public ReadOnlySpan<byte> Secret => secret;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
CryptographicOperations.ZeroMemory(secret);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,16 @@ public class NullDekEncryptionService : IDekEncryptionService
|
|||
return new(KeyId, plain.ToArray());
|
||||
}
|
||||
|
||||
public void Decrypt(EncryptedDek input, Span<byte> output)
|
||||
{
|
||||
input.Value.CopyTo(output);
|
||||
}
|
||||
|
||||
public int GetDecryptedSize(EncryptedDek input)
|
||||
{
|
||||
return input.Value.Length;
|
||||
}
|
||||
|
||||
public byte[] Decrypt(EncryptedDek input)
|
||||
{
|
||||
return input.Value;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "08_Tests", "08_Tests", "{98
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01", "01", "{07B08872-1141-4BE6-87E6-B85E52FE4341}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityShroud.GraphQL", "IdentityShroud.GraphQL\IdentityShroud.GraphQL.csproj", "{8E9BAD89-B964-4AC2-9773-8CB7E93F76C8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "04 Plugin Support", "04 Plugin Support", "{ABF0B435-2D50-41C8-847B-0905136537AB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityShroud.SecretProviders", "IdentityShroud.SecretProviders\IdentityShroud.SecretProviders.csproj", "{A27A70F7-415B-4F06-8A2C-1399675D41AE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityShroud.PluginSupport", "IdentityShroud.PluginSupport\IdentityShroud.PluginSupport.csproj", "{D4984187-8283-4494-88CF-35EDAD13E4DE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -54,6 +62,18 @@ Global
|
|||
{35D33207-27A8-43E9-A8CA-A158A1E4448C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{35D33207-27A8-43E9-A8CA-A158A1E4448C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{35D33207-27A8-43E9-A8CA-A158A1E4448C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8E9BAD89-B964-4AC2-9773-8CB7E93F76C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8E9BAD89-B964-4AC2-9773-8CB7E93F76C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8E9BAD89-B964-4AC2-9773-8CB7E93F76C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8E9BAD89-B964-4AC2-9773-8CB7E93F76C8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A27A70F7-415B-4F06-8A2C-1399675D41AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A27A70F7-415B-4F06-8A2C-1399675D41AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A27A70F7-415B-4F06-8A2C-1399675D41AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A27A70F7-415B-4F06-8A2C-1399675D41AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D4984187-8283-4494-88CF-35EDAD13E4DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D4984187-8283-4494-88CF-35EDAD13E4DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4984187-8283-4494-88CF-35EDAD13E4DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4984187-8283-4494-88CF-35EDAD13E4DE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{4758FE2E-A437-44F0-B58E-09E52D67D288} = {980900AA-E052-498B-A41A-4F33A8678828}
|
||||
|
|
@ -62,5 +82,8 @@ Global
|
|||
{A8554BCC-C9B6-4D96-90AD-FE80E95441F4} = {980900AA-E052-498B-A41A-4F33A8678828}
|
||||
{D2B446A0-AB62-4555-9D79-33FF43D7CEF4} = {07B08872-1141-4BE6-87E6-B85E52FE4341}
|
||||
{8490BF59-B68A-4BE0-9F96-6CB262AF4850} = {07B08872-1141-4BE6-87E6-B85E52FE4341}
|
||||
{8E9BAD89-B964-4AC2-9773-8CB7E93F76C8} = {07B08872-1141-4BE6-87E6-B85E52FE4341}
|
||||
{A27A70F7-415B-4F06-8A2C-1399675D41AE} = {ABF0B435-2D50-41C8-847B-0905136537AB}
|
||||
{D4984187-8283-4494-88CF-35EDAD13E4DE} = {ABF0B435-2D50-41C8-847B-0905136537AB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGeneratedRouteBuilderExtensions_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fb32218626d29174fc2ad283f379841286bfbde8_003FGeneratedRouteBuilderExtensions_002Eg_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGeneratedRouteBuilderExtensions_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fcb8492c9e846c0578d51c91f792dafe3872f7ff6_003FGeneratedRouteBuilderExtensions_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHealthCheckEndpointRouteBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6d0f079e13da4e98881aa3e6e169c6d34f08_003F0e_003Fc2b30661_003FHealthCheckEndpointRouteBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHotChocolateAspNetCoreServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fe57a7415cb4c78a7d65c32df7bc3ff0dbf395d62d38f5b0e74f5e5bef7f79a1_003FHotChocolateAspNetCoreServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAssertionException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F8bd9680bae73f114751c097b1235b5cd382cc262b5cc15a1cba29ac19c8c2d_003FIAssertionException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAsyncDisposable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7d59f4f94af72f8d3797655412cdc64435acc6454985685e415ee5fe817f_003FIAsyncDisposable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIEndpointMetadataProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F375a865e3a25e4d98cfdf8b893e61457bee3321eeb99c378ffcf91ba35e5319a_003FIEndpointMetadataProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
|
@ -54,8 +55,13 @@
|
|||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUnauthorizedResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F848c629dd035d9535248fad9944522c1b9cfaa290d4c1af25857955bc955ac8_003FUnauthorizedResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUTF8Encoding_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F47f5e4c98751367a86329ab1e0a76d86b39fd076a5b019284694f39d40fd9011_003FUTF8Encoding_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUtf8JsonWriter_002EWriteValues_002EGuid_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F79cee790efff8e1673abf37fe722bbdc0885a1a3e539a1e12fac638fd35426f_003FUtf8JsonWriter_002EWriteValues_002EGuid_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebApplication_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fae62ba349516ed6ffc77677754e3f3813337894f9d942dfa80ae3055c49_003FWebApplication_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWebEncoders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fce6b69dd397f614758bc5821136ec8af3fa22563dd657769e231f51be1fbbc_003FWebEncoders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/dotCover/Editor/HighlightingSourceSnapshotLocation/@EntryValue">/home/eelke/.cache/JetBrains/Rider2025.3/resharper-host/temp/Rider/vAny/CoverageData/_IdentityShroud.-1277985570/Snapshot/snapshot.utdcvr</s:String>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue