Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DMS-456] Add Claim set scope to application client #380

Merged
merged 5 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public async Task<ClientCreateResult> CreateClientAsync(
string clientId,
string clientSecret,
string role,
string displayName
string displayName,
string scope
)
{
try
Expand All @@ -42,7 +43,7 @@ string displayName
Secret = clientSecret,
Name = displayName,
ServiceAccountsEnabled = true,
DefaultClientScopes = [keycloakContext.Scope],
DefaultClientScopes = [scope],
ProtocolMappers = ConfigServiceProtocolMapper(),
};

Expand All @@ -59,37 +60,7 @@ string displayName
clientRole = await _keycloakClient.GetRoleByNameAsync(_realm, role);
}

var clientScopes = await _keycloakClient.GetClientScopesAsync(_realm);
ClientScope? clientScope = clientScopes.FirstOrDefault(x => x.Name.Equals(keycloakContext.Scope));

if (clientScope is null)
{
await _keycloakClient.CreateClientScopeAsync(
_realm,
new ClientScope()
{
Name = keycloakContext.Scope,
Protocol = "openid-connect",
ProtocolMappers = new List<ProtocolMapper>(
[
new ProtocolMapper()
{
Name = "audience resolve",
Protocol = "openid-connect",
_ProtocolMapper = "oidc-audience-resolve-mapper",
ConsentRequired = false,
Config = new Dictionary<string, string>
{
{ "introspection.token.claim", "true" },
{ "access.token.claim", "true" },
},
},
]
),
Attributes = new Attributes() { IncludeInTokenScope = "true" },
}
);
}
await CheckAndCreateClientScopeAsync(scope);

string? createdClientUuid = await _keycloakClient.CreateClientAndRetrieveClientIdAsync(
_realm,
Expand Down Expand Up @@ -212,6 +183,47 @@ public async Task<ClientClientsResult> GetAllClientsAsync()
}
}

private async Task CheckAndCreateClientScopeAsync(string scope)
{
bool scopeExists = await ClientScopeExistsAsync(scope);

if (!scopeExists)
{
await _keycloakClient.CreateClientScopeAsync(
_realm,
new ClientScope()
{
Name = scope,
Protocol = "openid-connect",
ProtocolMappers = new List<ProtocolMapper>(
[
new ProtocolMapper()
{
Name = "audience resolve",
Protocol = "openid-connect",
_ProtocolMapper = "oidc-audience-resolve-mapper",
ConsentRequired = false,
Config = new Dictionary<string, string>
{
{ "introspection.token.claim", "true" },
{ "access.token.claim", "true" },
},
},
]
),
Attributes = new Attributes() { IncludeInTokenScope = "true" },
}
);
}
}

private async Task<bool> ClientScopeExistsAsync(string scope)
{
var clientScopes = await _keycloakClient.GetClientScopesAsync(_realm);
ClientScope? clientScope = clientScopes.FirstOrDefault(x => x.Name.Equals(scope));
return clientScope != null;
}

private static IdentityProviderError ExceptionToKeycloakError(FlurlHttpException ex)
{
return ex.StatusCode switch
Expand All @@ -223,4 +235,66 @@ private static IdentityProviderError ExceptionToKeycloakError(FlurlHttpException
_ => new IdentityProviderError("Unknown"),
};
}

public async Task<ClientUpdateResult> UpdateClientAsync(
string clientUuid,
string displayName,
string scope
)
{
try
{
var client = await _keycloakClient.GetClientAsync(_realm, clientUuid);
if (client != null)
{
await CheckAndCreateClientScopeAsync(scope);
var scopeExists = await ClientScopeExistsAsync(scope);
if (scopeExists)
{
// Delete the existing client
await _keycloakClient.DeleteClientAsync(_realm, clientUuid);
Client newClient = new()
{
ClientId = client.ClientId,
Enabled = true,
Secret = client.Secret,
Name = displayName,
ServiceAccountsEnabled = true,
DefaultClientScopes = [scope],
ProtocolMappers = client.ProtocolMappers,
};
// Re-create the client
string? newClientId = await _keycloakClient.CreateClientAndRetrieveClientIdAsync(
_realm,
newClient
);
if (!string.IsNullOrEmpty(newClientId))
{
return new ClientUpdateResult.Success(Guid.Parse(newClientId));
}
}
else
{
var scopeNotFound = $"Scope {scope} not found";
logger.LogError(message: scopeNotFound);
return new ClientUpdateResult.FailureIdentityProvider(
new IdentityProviderError(scopeNotFound)
);
}
}

logger.LogError("Update client failure");
return new ClientUpdateResult.FailureUnknown($"Error while updating the client: {displayName}");
}
catch (FlurlHttpException ex)
{
logger.LogError(ex, "Update client failure");
return new ClientUpdateResult.FailureIdentityProvider(ExceptionToKeycloakError(ex));
}
catch (Exception ex)
{
logger.LogError(ex, "Update client failure");
return new ClientUpdateResult.FailureUnknown(ex.Message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ public async Task Should_get_and_failure_reference_not_found_and_invalid_vendor_
EducationOrganizationIds = [],
};

var applicationUpdateResult = await _applicationRepository.UpdateApplication(applicationUpdate);
var applicationUpdateResult = await _applicationRepository.UpdateApplication(
applicationUpdate,
new ApiClientCommand { ClientId = Guid.NewGuid().ToString(), ClientUuid = Guid.NewGuid() }
);
applicationUpdateResult.Should().BeOfType<ApplicationUpdateResult.FailureVendorNotFound>();
}
}
Expand Down Expand Up @@ -235,7 +238,8 @@ public async Task SetUp()
ClaimSetName = command.ClaimSetName,
EducationOrganizationIds = command.EducationOrganizationIds,
VendorId = command.VendorId,
}
},
new ApiClientCommand { ClientId = Guid.NewGuid().ToString(), ClientUuid = Guid.NewGuid() }
);
vendorUpdateResult.Should().BeOfType<ApplicationUpdateResult.Success>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class InsertTest : ClaimSetTests
[SetUp]
public async Task Setup()
{
ClaimSetInsertCommand claimSet = new() { Name = "Test ClaimSet" };
ClaimSetInsertCommand claimSet = new() { Name = "Test-ClaimSet" };

var result = await _repository.InsertClaimSet(claimSet);
result.Should().BeOfType<ClaimSetInsertResult.Success>();
Expand All @@ -49,7 +49,7 @@ public async Task Should_get_test_claimSet_from_get_all()
claimSetFromDb.Should().BeOfType<ClaimSetResponseReduced>();

var reducedResponse = (ClaimSetResponseReduced)claimSetFromDb;
reducedResponse.Name.Should().Be("Test ClaimSet");
reducedResponse.Name.Should().Be("Test-ClaimSet");
}

[Test]
Expand All @@ -62,13 +62,13 @@ public async Task Should_get_test_claimSet_from_get_by_id()
claimSetFromDb.Should().BeOfType<ClaimSetResponseReduced>();

var reducedResponse = (ClaimSetResponseReduced)claimSetFromDb;
reducedResponse.Name.Should().Be("Test ClaimSet");
reducedResponse.Name.Should().Be("Test-ClaimSet");
}

[Test]
public async Task Should_get_duplicate_failure()
{
ClaimSetInsertCommand claimSetDup = new() { Name = "Test ClaimSet" };
ClaimSetInsertCommand claimSetDup = new() { Name = "Test-ClaimSet" };

var resultDup = await _repository.InsertClaimSet(claimSetDup);
resultDup.Should().BeOfType<ClaimSetInsertResult.FailureDuplicateClaimSetName>();
Expand All @@ -84,15 +84,15 @@ public class UpdateTests : ClaimSetTests
[SetUp]
public async Task Setup()
{
_insertClaimSet = new ClaimSetInsertCommand() { Name = "Test Insert ClaimSet" };
_insertClaimSet = new ClaimSetInsertCommand() { Name = "Test-Insert-ClaimSet" };

_updateClaimSet = new ClaimSetUpdateCommand() { Name = "Test Update ClaimSet" };
_updateClaimSet = new ClaimSetUpdateCommand() { Name = "Test-Update-ClaimSet" };

var insertResult = await _repository.InsertClaimSet(_insertClaimSet);
insertResult.Should().BeOfType<ClaimSetInsertResult.Success>();

_updateClaimSet.Id = (insertResult as ClaimSetInsertResult.Success)!.Id;
_updateClaimSet.Name = "Test Update ClaimSet";
_updateClaimSet.Name = "Test-Update-ClaimSet";

var updateResult = await _repository.UpdateClaimSet(_updateClaimSet);
updateResult.Should().BeOfType<ClaimSetUpdateResult.Success>();
Expand All @@ -112,7 +112,7 @@ public async Task Should_get_update_claimSet_from_get_all()
claimSetFromDb.Should().BeOfType<ClaimSetResponseReduced>();

var reducedResponse = (ClaimSetResponseReduced)claimSetFromDb;
reducedResponse.Name.Should().Be("Test Update ClaimSet");
reducedResponse.Name.Should().Be("Test-Update-ClaimSet");
}

[Test]
Expand All @@ -125,7 +125,7 @@ public async Task Should_get_test_claimSet_from_get_by_id()
claimSetFromDb.Should().BeOfType<ClaimSetResponseReduced>();

var reducedResponse = (ClaimSetResponseReduced)claimSetFromDb;
reducedResponse.Name.Should().Be("Test Update ClaimSet");
reducedResponse.Name.Should().Be("Test-Update-ClaimSet");
}
}

Expand All @@ -139,12 +139,12 @@ public class DeleteTests : ClaimSetTests
public async Task Setup()
{
var insertResult1 = await _repository.InsertClaimSet(
new ClaimSetInsertCommand() { Name = "Test One" }
new ClaimSetInsertCommand() { Name = "Test-One" }
);
_id1 = ((ClaimSetInsertResult.Success)insertResult1).Id;

var insertResult2 = await _repository.InsertClaimSet(
new ClaimSetInsertCommand() { Name = "Test Two" }
new ClaimSetInsertCommand() { Name = "Test-Two" }
);
_id2 = ((ClaimSetInsertResult.Success)insertResult2).Id;

Expand Down Expand Up @@ -180,7 +180,7 @@ public class ExportTest : ClaimSetTests
[SetUp]
public async Task Setup()
{
ClaimSetInsertCommand claimSet = new() { Name = "Test Export ClaimSet" };
ClaimSetInsertCommand claimSet = new() { Name = "Test-Export-ClaimSet" };

var result = await _repository.InsertClaimSet(claimSet);
result.Should().BeOfType<ClaimSetInsertResult.Success>();
Expand All @@ -195,7 +195,7 @@ public async Task Should_export_claimSet()
result.Should().BeOfType<ClaimSetExportResult.Success>();

var valueFromDb = ((ClaimSetExportResult.Success)result).ClaimSetExportResponse;
valueFromDb.Name.Should().Be("Test Export ClaimSet");
valueFromDb.Name.Should().Be("Test-Export-ClaimSet");
}
}

Expand Down Expand Up @@ -233,7 +233,7 @@ public async Task Setup()

ClaimSetImportCommand claimSet = new()
{
Name = "Test Import ClaimSet",
Name = "Test-Import-ClaimSet",
ResourceClaims = resourceClaims,
};

Expand Down Expand Up @@ -262,7 +262,7 @@ public async Task Should_get_test_claimSet_from_get_by_id()
claimSetFromDb.Should().BeOfType<ClaimSetResponse>();

var response = (ClaimSetResponse)claimSetFromDb;
response.Name.Should().Be("Test Import ClaimSet");
response.Name.Should().Be("Test-Import-ClaimSet");
response.ResourceClaims.Should().NotBeNull();
}

Expand Down Expand Up @@ -295,7 +295,7 @@ public async Task Should_get_duplicate_failure()

ClaimSetImportCommand claimSetDup = new()
{
Name = "Test Import ClaimSet",
Name = "Test-Import-ClaimSet",
ResourceClaims = resourceClaims,
};

Expand All @@ -313,14 +313,14 @@ public class CopyTest : ClaimSetTests
[SetUp]
public async Task Setup()
{
ClaimSetInsertCommand claimSet = new() { Name = "Original ClaimSet" };
ClaimSetInsertCommand claimSet = new() { Name = "Original-ClaimSet" };

var result = await _repository.InsertClaimSet(claimSet);
result.Should().BeOfType<ClaimSetInsertResult.Success>();
_id = (result as ClaimSetInsertResult.Success)!.Id;
_id.Should().BeGreaterThan(0);

ClaimSetCopyCommand command = new() { OriginalId = _id, Name = "Copy Test ClaimSet" };
ClaimSetCopyCommand command = new() { OriginalId = _id, Name = "Copy-Test-ClaimSet" };

var copy = await _repository.Copy(command);
copy.Should().BeOfType<ClaimSetCopyResult.Success>();
Expand All @@ -347,7 +347,7 @@ public async Task Should_get_claimSet_from_get_by_id()
claimSetFromDb1.Should().BeOfType<ClaimSetResponseReduced>();

var reducedResponse1 = (ClaimSetResponseReduced)claimSetFromDb1;
reducedResponse1.Name.Should().Be("Original ClaimSet");
reducedResponse1.Name.Should().Be("Original-ClaimSet");

var getByIdResult2 = await _repository.GetClaimSet(_idCopy, false);
getByIdResult2.Should().BeOfType<ClaimSetGetResult.Success>();
Expand All @@ -356,7 +356,7 @@ public async Task Should_get_claimSet_from_get_by_id()
claimSetFromDb2.Should().BeOfType<ClaimSetResponseReduced>();

var reducedResponse2 = (ClaimSetResponseReduced)claimSetFromDb2;
reducedResponse2.Name.Should().Be("Copy Test ClaimSet");
reducedResponse2.Name.Should().Be("Copy-Test-ClaimSet");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ILogger<ApplicationRepository> logger
{
public async Task<ApplicationInsertResult> InsertApplication(
ApplicationInsertCommand command,
ApiClientInsertCommand clientCommand
ApiClientCommand clientCommand
)
{
await using var connection = new NpgsqlConnection(databaseOptions.Value.DatabaseConnection);
Expand Down Expand Up @@ -174,7 +174,10 @@ FROM dmscs.Application a
}
}

public async Task<ApplicationUpdateResult> UpdateApplication(ApplicationUpdateCommand command)
public async Task<ApplicationUpdateResult> UpdateApplication(
ApplicationUpdateCommand command,
ApiClientCommand clientCommand
)
{
await using var connection = new NpgsqlConnection(databaseOptions.Value.DatabaseConnection);
await connection.OpenAsync();
Expand Down Expand Up @@ -209,6 +212,14 @@ INSERT INTO dmscs.ApplicationEducationOrganization (ApplicationId, EducationOrga
});

await connection.ExecuteAsync(sql, educationOrganizations);

string updateApiClientsql = """
UPDATE dmscs.ApiClient
SET ClientUuid=@ClientUuid WHERE ClientId = @ClientId;
""";

await connection.ExecuteAsync(updateApiClientsql, clientCommand);

await transaction.CommitAsync();

return new ApplicationUpdateResult.Success();
Expand Down
Loading
Loading