Still working on getting client credential flow complete, most of the request works but still working on generating the JWT.
This commit is contained in:
parent
1a8c63808a
commit
8782ef39c6
80 changed files with 1331 additions and 414 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using IdentityShroud.Core.EFCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
using Testcontainers.PostgreSql;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
|
||||
<PackageReference Include="jose-jwt" Version="5.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ public class JwtSignatureGeneratorTests
|
|||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
""";
|
||||
JsonWebKeySet keySet = JsonSerializer.Deserialize<JsonWebKeySet>(keycloakKeySet)!;
|
||||
using RSA publicKey = LoadFromJwk(keySet.Keys[0]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
using IdentityShroud.Api;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.EFCore;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Security.Keys;
|
||||
using IdentityShroud.Core.Services;
|
||||
using IdentityShroud.Core.Tests.Fixtures;
|
||||
using IdentityShroud.TestUtils.Substitutes;
|
||||
|
|
@ -7,6 +11,29 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
namespace IdentityShroud.Core.Tests.Services;
|
||||
|
||||
public static class RealmDekBuilder
|
||||
{
|
||||
public static RealmDek DefaultActive() =>
|
||||
new()
|
||||
{
|
||||
Id = DekId.NewId(),
|
||||
Active = true,
|
||||
Algorithm = KeyType.AES,
|
||||
KeyData = new EncryptedDek(KekId.NewId(),
|
||||
[
|
||||
0
|
||||
])
|
||||
};
|
||||
}
|
||||
|
||||
public static class ClientCreateRequestBuilder
|
||||
{
|
||||
public static ClientCreateRequest Default() => new(
|
||||
"test-client",
|
||||
"Test Client",
|
||||
"A test client");
|
||||
}
|
||||
|
||||
public class ClientServiceTests : IClassFixture<DbFixture>
|
||||
{
|
||||
private readonly DbFixture _dbFixture;
|
||||
|
|
@ -34,15 +61,28 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
{
|
||||
if (!db.Realms.Any(r => r.Id == _realmId))
|
||||
{
|
||||
db.Realms.Add(new() { Id = _realmId, Slug = "test-realm", Name = "Test Realm" });
|
||||
db.Realms.Add(new()
|
||||
{
|
||||
Id = _realmId,
|
||||
Slug = "test-realm",
|
||||
Name = "Test Realm",
|
||||
DataEncryptionKeys = [ RealmDekBuilder.DefaultActive(), ],
|
||||
});
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private ClientService CreateSut(Db db) => new(db,
|
||||
_dataEncryptionService,
|
||||
new ClientCreateRequestValidator(),
|
||||
_clock);
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task Create(bool allowClientCredentialsFlow)
|
||||
public async Task Create(bool withSecret)
|
||||
{
|
||||
// Setup
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
|
@ -52,15 +92,13 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
await using (var db = _dbFixture.CreateDbContext())
|
||||
{
|
||||
// Act
|
||||
ClientService sut = new(db, _dataEncryptionService, _clock);
|
||||
ClientService sut = CreateSut(db);
|
||||
var response = await sut.Create(
|
||||
_realmId,
|
||||
new ClientCreateRequest
|
||||
ClientCreateRequestBuilder.Default() with
|
||||
{
|
||||
ClientId = "test-client",
|
||||
Name = "Test Client",
|
||||
Description = "A test client",
|
||||
AllowClientCredentialsFlow = allowClientCredentialsFlow,
|
||||
Confidential = withSecret,
|
||||
GenerateSecret = withSecret,
|
||||
},
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -70,7 +108,7 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
Assert.Equal("test-client", val.ClientId);
|
||||
Assert.Equal("Test Client", val.Name);
|
||||
Assert.Equal("A test client", val.Description);
|
||||
Assert.Equal(allowClientCredentialsFlow, val.AllowClientCredentialsFlow);
|
||||
Assert.Equal(withSecret, val.Confidential);
|
||||
Assert.Equal(now, val.CreatedAt);
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +118,7 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
.Include(e => e.Secrets)
|
||||
.SingleAsync(e => e.Id == val.Id, TestContext.Current.CancellationToken);
|
||||
|
||||
if (allowClientCredentialsFlow)
|
||||
if (withSecret)
|
||||
Assert.Single(dbRecord.Secrets);
|
||||
else
|
||||
Assert.Empty(dbRecord.Secrets);
|
||||
|
|
@ -108,7 +146,7 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
|
||||
await using var actContext = _dbFixture.CreateDbContext();
|
||||
// Act
|
||||
ClientService sut = new(actContext, _dataEncryptionService, _clock);
|
||||
ClientService sut = CreateSut(actContext);
|
||||
Client? result = await sut.GetByClientId(_realmId, clientId, TestContext.Current.CancellationToken);
|
||||
|
||||
// Verify
|
||||
|
|
@ -143,7 +181,7 @@ public class ClientServiceTests : IClassFixture<DbFixture>
|
|||
|
||||
await using var actContext = _dbFixture.CreateDbContext();
|
||||
// Act
|
||||
ClientService sut = new(actContext, _dataEncryptionService, _clock);
|
||||
ClientService sut = CreateSut(actContext);
|
||||
Client? result = await sut.FindById(_realmId, searchId, TestContext.Current.CancellationToken);
|
||||
|
||||
// Verify
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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;
|
||||
using IdentityShroud.TestUtils.Substitutes;
|
||||
|
||||
|
|
@ -9,23 +10,20 @@ namespace IdentityShroud.Core.Tests.Services;
|
|||
|
||||
public class DataEncryptionServiceTests
|
||||
{
|
||||
private readonly IRealmContext _realmContext = Substitute.For<IRealmContext>();
|
||||
// private readonly IRealmContext _realmContext = Substitute.For<IRealmContext>();
|
||||
private readonly IDekEncryptionService _dekCryptor = new NullDekEncryptionService();// Substitute.For<IDekEncryptionService>();
|
||||
|
||||
private readonly DekId _activeDekId = DekId.NewId();
|
||||
private readonly DekId _secondDekId = DekId.NewId();
|
||||
private DataEncryptionService CreateSut()
|
||||
=> new(_realmContext, _dekCryptor);
|
||||
=> new(_dekCryptor);
|
||||
|
||||
[Fact]
|
||||
public void Encrypt_UsesActiveKey()
|
||||
{
|
||||
_realmContext.GetDeks(Arg.Any<CancellationToken>()).Returns([
|
||||
CreateRealmDek(_secondDekId, false),
|
||||
CreateRealmDek(_activeDekId, true),
|
||||
]);
|
||||
|
||||
var cipher = CreateSut().Encrypt("Hello"u8);
|
||||
var dek = CreateRealmDek(_activeDekId, true);
|
||||
|
||||
var cipher = CreateSut().Encrypt(dek, "Hello"u8);
|
||||
|
||||
Assert.Equal(_activeDekId, cipher.DekId);
|
||||
}
|
||||
|
|
@ -34,20 +32,18 @@ public class DataEncryptionServiceTests
|
|||
public void Decrypt_UsesCorrectKey()
|
||||
{
|
||||
var first = CreateRealmDek(_activeDekId, true);
|
||||
_realmContext.GetDeks(Arg.Any<CancellationToken>()).Returns([ first ]);
|
||||
|
||||
var sut = CreateSut();
|
||||
var cipher = sut.Encrypt("Hello"u8);
|
||||
var cipher = sut.Encrypt(first, "Hello"u8);
|
||||
|
||||
// Deactivate original key
|
||||
first.Active = false;
|
||||
// Make new active
|
||||
var second = CreateRealmDek(_secondDekId, true);
|
||||
// Return both
|
||||
_realmContext.GetDeks(Arg.Any<CancellationToken>()).Returns([ first, second ]);
|
||||
RealmDek[] list = [ first, second ];
|
||||
|
||||
|
||||
var decoded = sut.Decrypt(cipher);
|
||||
var decoded = sut.Decrypt(list, cipher);
|
||||
|
||||
Assert.Equal("Hello"u8, decoded);
|
||||
}
|
||||
|
|
@ -57,7 +53,7 @@ public class DataEncryptionServiceTests
|
|||
{
|
||||
Id = id,
|
||||
Active = active,
|
||||
Algorithm = "AES",
|
||||
Algorithm = KeyType.AES,
|
||||
KeyData = new(KekId.NewId(), RandomNumberGenerator.GetBytes(32)),
|
||||
RealmId = default,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Services;
|
||||
|
||||
namespace IdentityShroud.Core.Tests.Services;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
using FluentResults;
|
||||
using IdentityShroud.Core.Contracts;
|
||||
using IdentityShroud.Core.EFCore;
|
||||
using IdentityShroud.Core.Model;
|
||||
using IdentityShroud.Core.Security;
|
||||
using IdentityShroud.Core.Security.Keys;
|
||||
using IdentityShroud.Core.Services;
|
||||
using IdentityShroud.Core.Tests.Fixtures;
|
||||
using IdentityShroud.TestUtils.Substitutes;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Shouldly;
|
||||
|
||||
namespace IdentityShroud.Core.Tests.Services;
|
||||
|
||||
|
|
@ -12,6 +15,7 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
{
|
||||
private readonly DbFixture _dbFixture;
|
||||
private readonly IKeyService _keyService = Substitute.For<IKeyService>();
|
||||
private readonly IDekEncryptionService _dekCryptor = new NullDekEncryptionService();
|
||||
|
||||
public RealmServiceTests(DbFixture dbFixture)
|
||||
{
|
||||
|
|
@ -25,6 +29,9 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
{
|
||||
db.Database.ExecuteSqlRaw("TRUNCATE realm CASCADE;");
|
||||
}
|
||||
|
||||
private RealmService CreateSut(Db db) => new(db, _keyService, _dekCryptor, new ClockService());
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
|
|
@ -36,20 +43,14 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
if (idString is not null)
|
||||
realmId = new(idString);
|
||||
|
||||
RealmCreateResponse? val;
|
||||
Realm? val;
|
||||
await using (var db = _dbFixture.CreateDbContext())
|
||||
{
|
||||
_keyService.CreateKey(Arg.Any<KeyPolicy>())
|
||||
.Returns(new RealmKey()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
KeyType = "TST",
|
||||
Key = new(KekId.NewId(), [21]),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
.Returns(new CreateKeyResponse(KeyType.AES, new KeyData([21])));
|
||||
// Act
|
||||
RealmService sut = new(db, _keyService);
|
||||
var response = await sut.Create(
|
||||
RealmService sut = CreateSut(db);
|
||||
Result<Realm> response = await sut.Create(
|
||||
new(realmId, "slug", "New realm"),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -60,8 +61,12 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
else
|
||||
Assert.NotEqual(Guid.Empty, val.Id);
|
||||
|
||||
Assert.Equal("slug", val.Slug);
|
||||
Assert.Equal("New realm", val.Name);
|
||||
Assert.Multiple(
|
||||
() => val.Slug.ShouldBe("slug"),
|
||||
() => val.Name.ShouldBe("New realm"),
|
||||
() => val.DataEncryptionKeys.ShouldContain(d => d.Active),
|
||||
() => val.TokenSigningKeys.ShouldContain(d => !d.RevokedAt.HasValue)
|
||||
);
|
||||
|
||||
_keyService.Received().CreateKey(Arg.Any<KeyPolicy>());
|
||||
}
|
||||
|
|
@ -69,9 +74,9 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
await using (var db = _dbFixture.CreateDbContext())
|
||||
{
|
||||
var dbRecord = await db.Realms
|
||||
.Include(e => e.Keys)
|
||||
.Include(e => e.TokenSigningKeys)
|
||||
.SingleAsync(e => e.Id == val.Id, TestContext.Current.CancellationToken);
|
||||
Assert.Equal("TST", dbRecord.Keys[0].KeyType);
|
||||
Assert.Equal(KeyType.AES, dbRecord.TokenSigningKeys[0].KeyType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +103,7 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
|
||||
await using var actContext = _dbFixture.CreateDbContext();
|
||||
// Act
|
||||
RealmService sut = new(actContext, _keyService);
|
||||
RealmService sut = CreateSut(actContext);
|
||||
var result = await sut.FindBySlug(slug, TestContext.Current.CancellationToken);
|
||||
|
||||
// Verify
|
||||
|
|
@ -131,7 +136,7 @@ public class RealmServiceTests : IClassFixture<DbFixture>
|
|||
|
||||
await using var actContext = _dbFixture.CreateDbContext();
|
||||
// Act
|
||||
RealmService sut = new(actContext, _keyService);
|
||||
RealmService sut = CreateSut(actContext);
|
||||
Realm? result = await sut.FindById(id, TestContext.Current.CancellationToken);
|
||||
|
||||
// Verify
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Buffers.Text;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using IdentityShroud.Core.DTO;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
namespace IdentityShroud.Core.Tests;
|
||||
|
||||
|
|
@ -74,10 +73,10 @@ public static class JwtReader
|
|||
return new JsonWebToken()
|
||||
{
|
||||
Header = JsonSerializer.Deserialize<JsonWebTokenHeader>(
|
||||
Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(jwt, 0, firstDot)))!,
|
||||
Base64Url.DecodeFromChars(jwt.AsSpan().Slice(0, firstDot)))!,
|
||||
Payload = JsonSerializer.Deserialize<JsonWebTokenPayload>(
|
||||
Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(jwt, firstDot + 1, secondDot - (firstDot + 1))))!,
|
||||
Signature = WebEncoders.Base64UrlDecode(jwt, secondDot + 1, jwt.Length - (secondDot + 1))
|
||||
Base64Url.DecodeFromChars(jwt.AsSpan().Slice(firstDot + 1, secondDot - (firstDot + 1))))!,
|
||||
Signature = Base64Url.DecodeFromChars(jwt.AsSpan().Slice(secondDot + 1, jwt.Length - (secondDot + 1))),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue