diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1eb0123..e71a299 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,17 +3,17 @@
true
-
+
-
+
-
-
+
+
@@ -28,7 +28,7 @@
-
+
@@ -38,7 +38,7 @@
-
+
@@ -48,8 +48,8 @@
-
-
+
+
@@ -58,4 +58,4 @@
-
+
\ No newline at end of file
diff --git a/SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs b/SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs
index 27e9781..8e123e2 100644
--- a/SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs
+++ b/SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs
@@ -40,36 +40,47 @@ private IntegrationTestCertificate(string certificateDirectory, X509Certificate2
public static IntegrationTestCertificate Create()
{
string certificateDirectory = Path.Combine(Path.GetTempPath(), $"securityservice-cert-{Guid.NewGuid():N}");
- Directory.CreateDirectory(certificateDirectory);
+ IntegrationTestCertificate? result = null;
- using RSA rsa = RSA.Create(2048);
- CertificateRequest request = new CertificateRequest(RootSubjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
- request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true));
+ try
+ {
+ Directory.CreateDirectory(certificateDirectory);
+
+ using RSA rsa = RSA.Create(2048);
+ CertificateRequest request = new CertificateRequest(RootSubjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
+ request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true));
- OidCollection enhancedKeyUsages = new OidCollection();
- enhancedKeyUsages.Add(new Oid("1.3.6.1.5.5.7.3.1"));
- request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsages, false));
+ OidCollection enhancedKeyUsages = new OidCollection();
+ enhancedKeyUsages.Add(new Oid("1.3.6.1.5.5.7.3.1"));
+ request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsages, false));
- SubjectAlternativeNameBuilder subjectAlternativeNames = new SubjectAlternativeNameBuilder();
- subjectAlternativeNames.AddDnsName(LocalhostName);
- subjectAlternativeNames.AddIpAddress(IPAddress.Parse(LoopbackAddress));
- request.CertificateExtensions.Add(subjectAlternativeNames.Build());
+ SubjectAlternativeNameBuilder subjectAlternativeNames = new SubjectAlternativeNameBuilder();
+ subjectAlternativeNames.AddDnsName(LocalhostName);
+ subjectAlternativeNames.AddIpAddress(IPAddress.Parse(LoopbackAddress));
+ request.CertificateExtensions.Add(subjectAlternativeNames.Build());
- request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
+ request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
- using X509Certificate2 certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
- byte[] pfxBytes = certificate.Export(X509ContentType.Pfx, PasswordValue);
- byte[] certificateBytes = certificate.Export(X509ContentType.Cert);
+ using X509Certificate2 certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
+ byte[] pfxBytes = certificate.Export(X509ContentType.Pfx, PasswordValue);
+ byte[] certificateBytes = certificate.Export(X509ContentType.Cert);
- string serverCertificatePath = Path.Combine(certificateDirectory, ServerCertificateFileName);
- string rootCertificatePath = Path.Combine(certificateDirectory, RootCertificateFileName);
- File.WriteAllBytes(serverCertificatePath, pfxBytes);
- File.WriteAllBytes(rootCertificatePath, certificateBytes);
+ string serverCertificatePath = Path.Combine(certificateDirectory, ServerCertificateFileName);
+ string rootCertificatePath = Path.Combine(certificateDirectory, RootCertificateFileName);
+ File.WriteAllBytes(serverCertificatePath, pfxBytes);
+ File.WriteAllBytes(rootCertificatePath, certificateBytes);
- var result = new IntegrationTestCertificate(certificateDirectory, X509CertificateLoader.LoadPkcs12(pfxBytes, PasswordValue, X509KeyStorageFlags.EphemeralKeySet));
- result.InstallTrustedRoot();
- return result;
+ result = new IntegrationTestCertificate(certificateDirectory, X509CertificateLoader.LoadPkcs12(pfxBytes, PasswordValue, X509KeyStorageFlags.EphemeralKeySet));
+ result.InstallTrustedRoot();
+ return result;
+ }
+ catch
+ {
+ result?.Dispose();
+ TryDeleteDirectory(certificateDirectory);
+ throw;
+ }
}
public void InstallTrustedRoot()
@@ -94,18 +105,23 @@ public void Dispose()
return;
}
- if (_trustedRootInstalled)
+ try
{
- RemoveTrustedRoot();
+ if (_trustedRootInstalled)
+ {
+ RemoveTrustedRoot();
+ }
}
-
- if (Directory.Exists(_certificateDirectory))
+ catch
{
- Directory.Delete(_certificateDirectory, recursive: true);
+ // Cleanup must not mask the original test failure.
+ }
+ finally
+ {
+ TryDeleteDirectory(_certificateDirectory);
+ _certificate.Dispose();
+ _disposed = true;
}
-
- _certificate.Dispose();
- _disposed = true;
}
private void RemoveTrustedRoot()
@@ -121,6 +137,21 @@ private void RemoveTrustedRoot()
_trustedRootInstalled = false;
}
+ private static void TryDeleteDirectory(string directory)
+ {
+ try
+ {
+ if (Directory.Exists(directory))
+ {
+ Directory.Delete(directory, recursive: true);
+ }
+ }
+ catch
+ {
+ // Cleanup must not mask the original test failure.
+ }
+ }
+
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
diff --git a/SecurityService.IntegrationTesting.Helpers/ReqnrollExtensions.cs b/SecurityService.IntegrationTesting.Helpers/ReqnrollExtensions.cs
index 8e26eda..fb0423f 100644
--- a/SecurityService.IntegrationTesting.Helpers/ReqnrollExtensions.cs
+++ b/SecurityService.IntegrationTesting.Helpers/ReqnrollExtensions.cs
@@ -316,6 +316,10 @@ public static List ToUserResponses(this DataTableRows tableRows){
private static List SplitString(this String stringToSplit){
List result = new List();
+ if (String.IsNullOrWhiteSpace(stringToSplit)){
+ return result;
+ }
+
List splitString = stringToSplit.Split(",").ToList();
foreach (String split in splitString){
result.Add(split.Trim());
diff --git a/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs b/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs
index 49b8d86..19f222e 100644
--- a/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs
+++ b/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs
@@ -7,6 +7,7 @@ namespace SecurityService.IntegrationTesting.Helpers;
using Shared.IntegrationTesting;
using Shouldly;
using System.Collections.Generic;
+using System.Security.Claims;
using System.Threading;
using Reqnroll;
@@ -14,6 +15,143 @@ public class SecurityServiceSteps{
private readonly ISecurityServiceClient SecurityServiceClient;
private readonly string AccessToken;
+ private static void AssertExpectedResults(
+ IEnumerable actual,
+ IEnumerable expected,
+ Func keySelector,
+ bool requireExactCount = true)
+ {
+ List actualList = actual.ToList();
+ List expectedList = expected.ToList();
+
+ if (requireExactCount)
+ {
+ actualList.Count.ShouldBe(expectedList.Count);
+ }
+
+ actualList.Select(keySelector).Distinct().Count().ShouldBe(actualList.Count);
+ expectedList.Select(keySelector).Distinct().Count().ShouldBe(expectedList.Count);
+ }
+
+ private static void AssertStringCollectionsMatch(
+ IEnumerable actual,
+ IEnumerable expected)
+ {
+ actual.OrderBy(value => value).ShouldBe(expected.OrderBy(value => value));
+ }
+
+ private static void AssertUserClaimsContain(
+ IReadOnlyDictionary actual,
+ IReadOnlyDictionary? expected)
+ {
+ if (expected is null)
+ {
+ return;
+ }
+
+ foreach (KeyValuePair expectedClaim in expected)
+ {
+ String claimType = GetClaimType(expectedClaim.Key);
+ actual.ContainsKey(claimType).ShouldBeTrue(expectedClaim.Key);
+ actual[claimType].ShouldBe(expectedClaim.Value);
+ }
+ }
+
+ private static String GetClaimType(String claimName) => claimName.ToLowerInvariant() switch
+ {
+ "emailaddress" => ClaimTypes.Email,
+ "givenname" => ClaimTypes.GivenName,
+ "surname" => ClaimTypes.Surname,
+ "role" => ClaimTypes.Role,
+ _ => claimName
+ };
+
+ private async Task GetAndVerifyUser(CreateUserRequest request, CancellationToken cancellationToken)
+ {
+ Result>? response = await this.SecurityServiceClient
+ .GetUsers(this.AccessToken, request.EmailAddress, cancellationToken)
+ .ConfigureAwait(false);
+
+ response.IsSuccess.ShouldBeTrue(response.Message);
+ UserResponse user = response.Data.Single();
+ String expectedUserName = String.IsNullOrWhiteSpace(request.UserName)
+ ? request.EmailAddress ?? String.Empty
+ : request.UserName;
+
+ user.UserName.ShouldBe(expectedUserName);
+ user.EmailAddress.ShouldBe(request.EmailAddress);
+ user.GivenName.ShouldBe(request.GivenName);
+ user.MiddleName.ShouldBe(request.MiddleName);
+ user.FamilyName.ShouldBe(request.FamilyName);
+ user.PhoneNumber.ShouldBe(request.PhoneNumber);
+ AssertStringCollectionsMatch(user.Roles, request.Roles);
+ AssertUserClaimsContain(user.Claims, request.Claims);
+
+ return user;
+ }
+
+ private async Task VerifyApiScope(CreateApiScopeRequest request)
+ {
+ Result? response = await this.SecurityServiceClient
+ .GetApiScope(this.AccessToken, request.Name, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ response.IsSuccess.ShouldBeTrue(response.Message);
+ response.Data.ShouldNotBeNull();
+ response.Data.Name.ShouldBe(request.Name);
+ response.Data.DisplayName.ShouldBe(request.DisplayName);
+ response.Data.Description.ShouldBe(request.Description);
+ }
+
+ private async Task VerifyApiResource(CreateApiResourceRequest request)
+ {
+ Result? response = await this.SecurityServiceClient
+ .GetApiResource(this.AccessToken, request.Name, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ response.IsSuccess.ShouldBeTrue(response.Message);
+ response.Data.ShouldNotBeNull();
+ response.Data.Name.ShouldBe(request.Name);
+ response.Data.DisplayName.ShouldBe(request.DisplayName);
+ response.Data.Description.ShouldBe(request.Description);
+ AssertStringCollectionsMatch(response.Data.Scopes, request.Scopes);
+ AssertStringCollectionsMatch(response.Data.UserClaims, request.UserClaims);
+ }
+
+ private async Task VerifyClient(CreateClientRequest request)
+ {
+ Result? response = await this.SecurityServiceClient
+ .GetClient(this.AccessToken, request.ClientId, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ response.IsSuccess.ShouldBeTrue(response.Message);
+ response.Data.ShouldNotBeNull();
+ response.Data.ClientId.ShouldBe(request.ClientId);
+ response.Data.ClientName.ShouldBe(request.ClientName);
+ response.Data.Description.ShouldBe(request.ClientDescription);
+ response.Data.ClientUri.ShouldBe(request.ClientUri);
+ response.Data.RequireConsent.ShouldBe(request.RequireConsent);
+ response.Data.AllowOfflineAccess.ShouldBe(request.AllowOfflineAccess);
+ AssertStringCollectionsMatch(response.Data.AllowedScopes, request.AllowedScopes);
+ AssertStringCollectionsMatch(response.Data.AllowedGrantTypes, request.AllowedGrantTypes);
+ AssertStringCollectionsMatch(response.Data.ClientRedirectUris, request.ClientRedirectUris);
+ AssertStringCollectionsMatch(response.Data.ClientPostLogoutRedirectUris, request.ClientPostLogoutRedirectUris);
+ }
+
+ private async Task VerifyIdentityResource(CreateIdentityResourceRequest request)
+ {
+ Result? response = await this.SecurityServiceClient
+ .GetIdentityResource(this.AccessToken, request.Name, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ response.IsSuccess.ShouldBeTrue(response.Message);
+ response.Data.ShouldNotBeNull();
+ response.Data.Name.ShouldBe(request.Name);
+ response.Data.DisplayName.ShouldBe(request.DisplayName);
+ response.Data.Description.ShouldBe(request.Description);
+ AssertStringCollectionsMatch(response.Data.Claims, request.Claims ?? Enumerable.Empty());
+ }
+
public SecurityServiceSteps(ISecurityServiceClient securityServiceClient, String accessToken){
this.SecurityServiceClient = securityServiceClient;
this.AccessToken = accessToken;
@@ -28,8 +166,7 @@ private async Task CreateApiScope(String accessToken, CreateApiScopeRequest crea
public async Task GivenICreateTheFollowingApiScopes(List createApiScopeRequests){
foreach (CreateApiScopeRequest createApiScopeRequest in createApiScopeRequests){
await this.CreateApiScope(this.AccessToken, createApiScopeRequest, CancellationToken.None).ConfigureAwait(false);
-
- // TODO: can do a get in here to verify really created...
+ await this.VerifyApiScope(createApiScopeRequest).ConfigureAwait(false);
}
}
@@ -56,7 +193,8 @@ public async Task GivenTheFollowingApiResourcesExist(DataTable table){
splitUserClaims.ForEach(a => { createApiResourceRequest.UserClaims.Add(a.Trim()); });
Result? result = await this.SecurityServiceClient.CreateApiResource(this.AccessToken, createApiResourceRequest, CancellationToken.None).ConfigureAwait(false);
- result.IsSuccess.ShouldBeTrue();
+ result.IsSuccess.ShouldBeTrue(result.Message);
+ await this.VerifyApiResource(createApiResourceRequest).ConfigureAwait(false);
}
}
@@ -64,9 +202,9 @@ public async Task GivenTheFollowingApiResourcesExist(DataTable table){
List<(String clientId, String secret, List allowedGrantTypes)> clients = new List<(String clientId, String secret, List allowedGrantTypes)>();
foreach (CreateClientRequest createClientRequest in createClientRequests){
Result? result = await this.SecurityServiceClient.CreateClient(this.AccessToken, createClientRequest, CancellationToken.None).ConfigureAwait(false);
- result.IsSuccess.ShouldBeTrue();
-
- // TODO: What do i do here....
+ result.IsSuccess.ShouldBeTrue(result.Message);
+ await this.VerifyClient(createClientRequest).ConfigureAwait(false);
+
clients.Add((createClientRequest.ClientId, createClientRequest.Secret, createClientRequest.AllowedGrantTypes));
}
@@ -76,7 +214,8 @@ public async Task GivenTheFollowingApiResourcesExist(DataTable table){
public async Task GivenTheFollowingApiResourcesExist(List requests){
foreach (CreateApiResourceRequest createApiResourceRequest in requests){
Result? result = await this.SecurityServiceClient.CreateApiResource(this.AccessToken, createApiResourceRequest, CancellationToken.None).ConfigureAwait(false);
- result.IsSuccess.ShouldBeTrue();
+ result.IsSuccess.ShouldBeTrue(result.Message);
+ await this.VerifyApiResource(createApiResourceRequest).ConfigureAwait(false);
}
}
@@ -101,18 +240,14 @@ public async Task WhenIGetTheApiResourcesApiResourceDetailsAreReturnedAsFollows(
Result>? apiResourceDetailsListResult = await this.SecurityServiceClient.GetApiResources(this.AccessToken, cancellationToken).ConfigureAwait(false);
apiResourceDetailsListResult.IsSuccess.ShouldBeTrue();
List apiResourceDetailsList = apiResourceDetailsListResult.Data;
+ AssertExpectedResults(apiResourceDetailsList, expectedDetails, resource => resource.Name);
foreach (ApiResourceResponse apiResourceDetails in expectedDetails){
ApiResourceResponse? foundRecord = apiResourceDetailsList.SingleOrDefault(a => a.Name == apiResourceDetails.Name);
foundRecord.ShouldNotBeNull();
foundRecord.Description.ShouldBe(apiResourceDetails.Description);
foundRecord.DisplayName.ShouldBe(apiResourceDetails.DisplayName);
- foreach (String? scope in apiResourceDetails.Scopes){
- foundRecord.Scopes.ShouldContain(scope);
- }
-
- foreach (String? userClaim in apiResourceDetails.UserClaims){
- foundRecord.UserClaims.ShouldContain(userClaim);
- }
+ AssertStringCollectionsMatch(foundRecord.Scopes, apiResourceDetails.Scopes);
+ AssertStringCollectionsMatch(foundRecord.UserClaims, apiResourceDetails.UserClaims);
}
}
@@ -126,13 +261,8 @@ public async Task WhenIGetTheApiResourceWithNameTheApiResourceDetailsAreReturned
apiResourceDetails.ShouldNotBeNull();
apiResourceDetails.Description.ShouldBe(expectedRecord.Description);
apiResourceDetails.DisplayName.ShouldBe(expectedRecord.DisplayName);
- foreach (String? scope in expectedRecord.Scopes){
- apiResourceDetails.Scopes.ShouldContain(scope);
- }
-
- foreach (String? userClaim in expectedRecord.UserClaims){
- apiResourceDetails.UserClaims.ShouldContain(userClaim);
- }
+ AssertStringCollectionsMatch(apiResourceDetails.Scopes, expectedRecord.Scopes);
+ AssertStringCollectionsMatch(apiResourceDetails.UserClaims, expectedRecord.UserClaims);
}
public async Task WhenIGetTheApiScopesApiScopeDetailsAreReturnedAsFollows(List expectedDetails, CancellationToken cancellationToken){
@@ -140,6 +270,7 @@ public async Task WhenIGetTheApiScopesApiScopeDetailsAreReturnedAsFollows(List? apiScopeDetailsList = apiScopeDetailsListResult.Data;
+ AssertExpectedResults(apiScopeDetailsList!, expectedDetails, scope => scope.Name);
foreach (ApiScopeResponse apiScopeDetails in expectedDetails){
ApiScopeResponse? foundRecord = apiScopeDetailsList.SingleOrDefault(a => a.Name == apiScopeDetails.Name);
foundRecord.ShouldNotBeNull();
@@ -161,7 +292,7 @@ public async Task WhenIGetTheApiScopeWithNameTheApiScopeDetailsAreReturnedAsFoll
}
public async Task WhenIGetTheClientWithClientIdTheClientDetailsAreReturnedAsFollows(List expectedDetails, String clientId, CancellationToken cancellationToken){
- Result? clientDetailsResult = await this.SecurityServiceClient.GetClient(this.AccessToken, clientId, CancellationToken.None).ConfigureAwait(false);
+ Result? clientDetailsResult = await this.SecurityServiceClient.GetClient(this.AccessToken, clientId, cancellationToken).ConfigureAwait(false);
clientDetailsResult.IsSuccess.ShouldBeTrue();
ClientResponse clientDetails = clientDetailsResult.Data;
ClientResponse expectedRecord = expectedDetails.Single();
@@ -170,19 +301,18 @@ public async Task WhenIGetTheClientWithClientIdTheClientDetailsAreReturnedAsFoll
clientDetails.ClientId.ShouldBe(expectedRecord.ClientId);
clientDetails.ClientName.ShouldBe(expectedRecord.ClientName);
clientDetails.Description.ShouldBe(expectedRecord.Description);
- foreach (String expectedRecordAllowedGrantType in expectedRecord.AllowedGrantTypes){
- clientDetails.AllowedGrantTypes.ShouldContain(expectedRecordAllowedGrantType);
- }
-
- foreach (String expectedRecordAllowedScope in expectedRecord.AllowedScopes){
- clientDetails.AllowedScopes.ShouldContain(expectedRecordAllowedScope);
- }
+ AssertStringCollectionsMatch(clientDetails.AllowedGrantTypes, expectedRecord.AllowedGrantTypes);
+ AssertStringCollectionsMatch(clientDetails.AllowedScopes, expectedRecord.AllowedScopes);
}
public async Task WhenIGetTheClientsClientsDetailsAreReturnedAsFollows(List expectedDetails, CancellationToken cancellationToken){
- Result>? clientDetailsListResult = await this.SecurityServiceClient.GetClients(this.AccessToken, CancellationToken.None).ConfigureAwait(false);
+ Result>? clientDetailsListResult = await this.SecurityServiceClient.GetClients(this.AccessToken, cancellationToken).ConfigureAwait(false);
clientDetailsListResult.IsSuccess.ShouldBeTrue();
List? clientDetailsList = clientDetailsListResult.Data;
+ List expectedClientDetails = clientDetailsList!
+ .Where(client => expectedDetails.Any(expected => expected.ClientId == client.ClientId))
+ .ToList();
+ AssertExpectedResults(expectedClientDetails, expectedDetails, client => client.ClientId);
foreach (ClientResponse expectedRecord in expectedDetails){
ClientResponse? foundRecord = clientDetailsList.SingleOrDefault(a => a.ClientId == expectedRecord.ClientId);
foundRecord.ShouldNotBeNull();
@@ -190,20 +320,16 @@ public async Task WhenIGetTheClientsClientsDetailsAreReturnedAsFollows(List requests, CancellationToken cancellationToken){
foreach (CreateIdentityResourceRequest createIdentityResourceRequest in requests){
Result? result = await this.SecurityServiceClient.CreateIdentityResource(this.AccessToken, createIdentityResourceRequest, cancellationToken).ConfigureAwait(false);
- result.IsSuccess.ShouldBeTrue();
+ result.IsSuccess.ShouldBeTrue(result.Message);
+ await this.VerifyIdentityResource(createIdentityResourceRequest).ConfigureAwait(false);
}
}
@@ -218,15 +344,14 @@ public async Task WhenIGetTheIdentityResourceWithNameTheIdentityResourceDetailsA
identityResourceDetails.Description.ShouldBe(expectedRecord.Description);
identityResourceDetails.DisplayName.ShouldBe(expectedRecord.DisplayName);
- foreach (String expectedRecordClaim in expectedRecord.Claims){
- identityResourceDetails.Claims.ShouldContain(expectedRecordClaim);
- }
+ AssertStringCollectionsMatch(identityResourceDetails.Claims, expectedRecord.Claims);
}
public async Task WhenIGetTheIdentityResourcesIdentityResourceDetailsAreReturnedAsFollows(List expectedDetails, CancellationToken cancellationToken){
- Result>? getIdentityResourcesResult = await this.SecurityServiceClient.GetIdentityResources(this.AccessToken, CancellationToken.None).ConfigureAwait(false);
+ Result>? getIdentityResourcesResult = await this.SecurityServiceClient.GetIdentityResources(this.AccessToken, cancellationToken).ConfigureAwait(false);
getIdentityResourcesResult.IsSuccess.ShouldBeTrue();
List? identityResourceDetailsList = getIdentityResourcesResult.Data;
+ AssertExpectedResults(identityResourceDetailsList!, expectedDetails, resource => resource.Name);
foreach (IdentityResourceResponse expectedRecord in expectedDetails){
IdentityResourceResponse? foundRecord = identityResourceDetailsList.SingleOrDefault(a => a.Name == expectedRecord.Name);
foundRecord.ShouldNotBeNull();
@@ -234,10 +359,7 @@ public async Task WhenIGetTheIdentityResourcesIdentityResourceDetailsAreReturned
foundRecord.Description.ShouldBe(expectedRecord.Description);
foundRecord.DisplayName.ShouldBe(expectedRecord.DisplayName);
- foreach (String expectedRecordClaim in expectedRecord.Claims)
- {
- foundRecord.Claims.ShouldContain(expectedRecordClaim);
- }
+ AssertStringCollectionsMatch(foundRecord.Claims, expectedRecord.Claims);
}
}
@@ -252,8 +374,13 @@ public async Task WhenIGetTheIdentityResourcesIdentityResourceDetailsAreReturned
Result>? roles = await this.SecurityServiceClient.GetRoles(this.AccessToken, cancellationToken);
roles.IsSuccess.ShouldBeTrue(roles.Message);
+ roles.Data.Count.ShouldBe(requests.Count);
+ roles.Data.Select(role => role.RoleName).Distinct().Count().ShouldBe(roles.Data.Count);
+ requests.Select(request => request.RoleName).Distinct().Count().ShouldBe(requests.Count);
+
foreach (CreateRoleRequest request in requests) {
RoleResponse r = roles.Data.Single(r => r.RoleName == request.RoleName);
+ r.RoleId.ShouldNotBeNullOrWhiteSpace();
roleList.Add((r.RoleName, r.RoleId));
}
@@ -272,9 +399,10 @@ public async Task WhenIGetTheRoleWithNameTheRoleDetailsAreReturnedAsFollows(List
public async Task WhenIGetTheRolesRolesDetailsAreReturnedAsFollows(List expectedDetails, CancellationToken cancellationToken)
{
- Result>? getRolesResult = await this.SecurityServiceClient.GetRoles(this.AccessToken, CancellationToken.None).ConfigureAwait(false);
+ Result>? getRolesResult = await this.SecurityServiceClient.GetRoles(this.AccessToken, cancellationToken).ConfigureAwait(false);
getRolesResult.IsSuccess.ShouldBeTrue();
List? rolesList = getRolesResult.Data;
+ AssertExpectedResults(rolesList!, expectedDetails, role => role.RoleName);
foreach (RoleResponse expectedRecord in expectedDetails)
{
RoleResponse? foundRecord = rolesList.SingleOrDefault(a => a.RoleName == expectedRecord.RoleName);
@@ -287,15 +415,14 @@ public async Task WhenIGetTheRolesRolesDetailsAreReturnedAsFollows(List results = new List<(String, String)>();
foreach (CreateUserRequest createUserRequest in requests){
Result? result = await this.SecurityServiceClient.CreateUser(this.AccessToken, createUserRequest, cancellationToken).ConfigureAwait(false);
- result.IsSuccess.ShouldBeTrue();
+ result.IsSuccess.ShouldBeTrue(result.Message);
- Result>? user = await this.SecurityServiceClient.GetUsers(this.AccessToken, createUserRequest.EmailAddress, cancellationToken);
- user.IsSuccess.ShouldBeTrue();
+ UserResponse user = await this.GetAndVerifyUser(createUserRequest, cancellationToken).ConfigureAwait(false);
String userKey = String.IsNullOrWhiteSpace(createUserRequest.UserName)
? createUserRequest.EmailAddress ?? String.Empty
: createUserRequest.UserName;
- results.Add((userKey, user.Data.Single().UserId));
+ results.Add((userKey, user.UserId));
}
return results;
}
@@ -314,9 +441,10 @@ public Task RemoveRoleMfa(String roleId, CancellationToken cancellationT
public async Task WhenIGetTheUsersUsersDetailsAreReturnedAsFollows(List expectedDetails, CancellationToken cancellationToken)
{
- Result>? getUsersResult = await this.SecurityServiceClient.GetUsers(this.AccessToken, String.Empty, CancellationToken.None).ConfigureAwait(false);
+ Result>? getUsersResult = await this.SecurityServiceClient.GetUsers(this.AccessToken, String.Empty, cancellationToken).ConfigureAwait(false);
getUsersResult.IsSuccess.ShouldBeTrue();
List? usersList = getUsersResult.Data;
+ AssertExpectedResults(usersList!, expectedDetails, user => user.UserName);
foreach (UserResponse expectedRecord in expectedDetails)
{
UserResponse? foundRecord = usersList.SingleOrDefault(a => a.UserName == expectedRecord.UserName);
@@ -329,16 +457,16 @@ public async Task WhenIGetTheUsersUsersDetailsAreReturnedAsFollows(List expectedRecordClaim in expectedRecord.Claims){
- var claimName = $"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/{expectedRecordClaim.Key}";
- foundRecord.Claims.ContainsKey(claimName).ShouldBeTrue(claimName);
- String? claim = foundRecord.Claims[claimName];
+ String claimType = GetClaimType(expectedRecordClaim.Key);
+ foundRecord.Claims.ContainsKey(claimType).ShouldBeTrue(expectedRecordClaim.Key);
+ String? claim = foundRecord.Claims[claimType];
claim.ShouldBe(expectedRecordClaim.Value);
}
}
}
public async Task WhenIGetTheUserWithUserNameTheUserDetailsAreReturnedAsFollows(List expectedDetails, String userId, CancellationToken cancellationToken){
- var userDetails = await this.SecurityServiceClient.GetUser(this.AccessToken, userId, CancellationToken.None).ConfigureAwait(false);
+ var userDetails = await this.SecurityServiceClient.GetUser(this.AccessToken, userId, cancellationToken).ConfigureAwait(false);
var expectedRecord = expectedDetails.Single();
userDetails.IsSuccess.ShouldBeTrue();
@@ -351,9 +479,9 @@ public async Task WhenIGetTheUserWithUserNameTheUserDetailsAreReturnedAsFollows(
}
foreach (KeyValuePair expectedRecordClaim in expectedRecord.Claims){
- var claimName = $"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/{expectedRecordClaim.Key}";
- userDetails.Data.Claims.ContainsKey(claimName).ShouldBeTrue(claimName);
- String? claim = userDetails.Data.Claims[claimName];
+ String claimType = GetClaimType(expectedRecordClaim.Key);
+ userDetails.Data.Claims.ContainsKey(claimType).ShouldBeTrue(expectedRecordClaim.Key);
+ String? claim = userDetails.Data.Claims[claimType];
claim.ShouldBe(expectedRecordClaim.Value);
}
// TODO: Add back in
diff --git a/SecurityService.IntegrationTests/ApiResource/ApiResourceSteps.cs b/SecurityService.IntegrationTests/ApiResource/ApiResourceSteps.cs
index 3d5c889..04afcf8 100644
--- a/SecurityService.IntegrationTests/ApiResource/ApiResourceSteps.cs
+++ b/SecurityService.IntegrationTests/ApiResource/ApiResourceSteps.cs
@@ -68,6 +68,7 @@ public async Task GivenICreateTheFollowingApiResources(Table table)
[When(@"I get the api resources (.*) api resource details are returned as follows")]
public async Task WhenIGetTheApiResourcesApiResourceDetailsAreReturnedAsFollows(Int32 numberOfApiResources,
Table table){
+ table.Rows.Count.ShouldBe(numberOfApiResources);
List expectedDetails = table.Rows.ToApiResourceResponses();
await this.SecurityServiceSteps.WhenIGetTheApiResourcesApiResourceDetailsAreReturnedAsFollows(expectedDetails, CancellationToken.None);
}
@@ -90,4 +91,4 @@ public async Task WhenIGetTheApiResourceWithNameTheApiResourceDetailsAreReturned
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/SecurityService.IntegrationTests/ApiScopes/ApiScopeSteps.cs b/SecurityService.IntegrationTests/ApiScopes/ApiScopeSteps.cs
index 52a9fc9..44c75d9 100644
--- a/SecurityService.IntegrationTests/ApiScopes/ApiScopeSteps.cs
+++ b/SecurityService.IntegrationTests/ApiScopes/ApiScopeSteps.cs
@@ -76,6 +76,7 @@ public async Task WhenIGetTheApiScopeWithNameTheApiScopeDetailsAreReturnedAsFoll
[When(@"I get the api scopes (.*) api scope details are returned as follows")]
public async Task WhenIGetTheApiScopesApiScopeDetailsAreReturnedAsFollows(Int32 numberOfApiScopes, DataTable table)
{
+ table.Rows.Count.ShouldBe(numberOfApiScopes);
List expectedDetails = table.Rows.ToApiScopeResponses();
await this.SecurityServiceSteps.WhenIGetTheApiScopesApiScopeDetailsAreReturnedAsFollows(expectedDetails, CancellationToken.None);
}
diff --git a/SecurityService.IntegrationTests/Clients/ClientsSteps.cs b/SecurityService.IntegrationTests/Clients/ClientsSteps.cs
index 45c8e7c..e2116ce 100644
--- a/SecurityService.IntegrationTests/Clients/ClientsSteps.cs
+++ b/SecurityService.IntegrationTests/Clients/ClientsSteps.cs
@@ -11,6 +11,7 @@
using IntegrationTesting.Helpers;
using IntergrationTests.Common;
using Reqnroll;
+ using Shouldly;
///
///
@@ -69,6 +70,7 @@ public async Task GivenICreateTheFollowingClients(DataTable table)
[When(@"I get the clients (.*) clients details are returned as follows")]
public async Task WhenIGetTheClientsClientsDetailsAreReturnedAsFollows(Int32 numberOfClients,
DataTable table){
+ table.Rows.Count.ShouldBe(numberOfClients);
List expectedClientDetails = table.Rows.ToClientResponses();
await this.SecurityServiceSteps.WhenIGetTheClientsClientsDetailsAreReturnedAsFollows(expectedClientDetails, CancellationToken.None);
}
@@ -86,4 +88,4 @@ public async Task WhenIGetTheClientWithClientIdTheClientDetailsAreReturnedAsFoll
}
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/SecurityService.IntegrationTests/Common/DockerHelper.cs b/SecurityService.IntegrationTests/Common/DockerHelper.cs
index ecfb28b..5ca087b 100644
--- a/SecurityService.IntegrationTests/Common/DockerHelper.cs
+++ b/SecurityService.IntegrationTests/Common/DockerHelper.cs
@@ -20,7 +20,6 @@ namespace SecurityService.IntergrationTests.Common
public class DockerHelper : Shared.IntegrationTesting.TestContainers.DockerHelper
{
public ISecurityServiceClient? SecurityServiceClient;
- private IntegrationTestCertificate? _integrationTestCertificate;
String Serialise(Object arg)
{
@@ -31,27 +30,30 @@ Object Deserialise(String arg, Type type)
{
return StringSerialiser.DeserializeObject
public ISecurityServiceClient SecurityServiceClient;
public HttpClient httpClient = new HttpClient();
- private IntegrationTestCertificate? _integrationTestCertificate;
public String AccessToken;
///
/// The security service test UI container name
@@ -79,7 +78,8 @@ public override void SetupContainerNames()
this.SecurityServiceTestUIContainerName = $"securityservicetestui{this.TestId:N}";
}
- public override async Task CreateSubscriptions(){
+ public override async Task CreateSubscriptions(CancellationToken cancellationToken)
+ {
// Nothing to set up here
}
@@ -97,14 +97,13 @@ Object Deserialise(String arg, Type type)
/// Starts the containers for scenario run.
///
/// Name of the scenario.
- public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices)
+ public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices, CancellationToken cancellationToken)
{
this.Trace($"Test Id is {this.TestId} and Scenario {scenarioName}");
- this._integrationTestCertificate = IntegrationTestCertificate.Create();
try
{
- await base.StartContainersForScenarioRun(scenarioName,dockerServices);
+ await base.StartContainersForScenarioRun(scenarioName,dockerServices, cancellationToken);
await StartContainer(SetupSecurityServiceTestUIContainer, this.TestNetworks);
@@ -254,7 +253,7 @@ public override ContainerBuilder SetupSecurityServiceContainer()
{
this.Trace("About to Start Security Container");
- if (this._integrationTestCertificate is null)
+ if (this.IntegrationTestCertificate is null)
{
throw new InvalidOperationException("Integration test certificate was not initialised before container setup.");
}
@@ -264,8 +263,8 @@ public override ContainerBuilder SetupSecurityServiceContainer()
environmentVariables.Add($"ServiceOptions:IssuerUrl",$"https://{this.SecurityServiceContainerName}:{DockerPorts.SecurityServiceDockerPort}");
environmentVariables.Add("ASPNETCORE_ENVIRONMENT","IntegrationTest");
environmentVariables.Add($"urls",$"https://*:{DockerPorts.SecurityServiceDockerPort}");
- environmentVariables.Add("ServiceOptions:KestrelOptions:Path", this._integrationTestCertificate.GetContainerCertificatePath());
- environmentVariables.Add("ServiceOptions:KestrelOptions:Password", this._integrationTestCertificate.Password);
+ environmentVariables.Add("ServiceOptions:KestrelOptions:Path", this.IntegrationTestCertificate.GetContainerCertificatePath());
+ environmentVariables.Add("ServiceOptions:KestrelOptions:Password", this.IntegrationTestCertificate.Password);
environmentVariables.Add($"ServiceOptions:PasswordOptions:RequiredLength","6");
environmentVariables.Add($"ServiceOptions:PasswordOptions:RequireDigit","false");
@@ -298,21 +297,15 @@ public override ContainerBuilder SetupSecurityServiceContainer()
.WithName(this.SecurityServiceContainerName)
.WithEnvironment(environmentVariables)
.WithPortBinding(DockerPorts.SecurityServiceDockerPort, DockerPorts.SecurityServiceDockerPort)
- .WithBindMount(this._integrationTestCertificate.CertificateDirectory, this._integrationTestCertificate.ContainerCertificateDirectory, AccessMode.ReadOnly)
- .MountHostFolder(this.DockerPlatform, this.HostTraceFolder);
+ .WithBindMount(this.IntegrationTestCertificate.CertificateDirectory, this.IntegrationTestCertificate.ContainerCertificateDirectory, AccessMode.ReadOnly)
+ .MountHostFolder(this.HostTraceFolder);
return securityServiceContainer;
}
- public void DisposeIntegrationTestCertificate()
- {
- this._integrationTestCertificate?.Dispose();
- this._integrationTestCertificate = null;
- }
-
private HttpClient CreatePinnedHttpClient()
{
- if (this._integrationTestCertificate is null)
+ if (this.IntegrationTestCertificate is null)
{
throw new InvalidOperationException("Integration test certificate was not initialised before HttpClient creation.");
}
@@ -321,7 +314,7 @@ private HttpClient CreatePinnedHttpClient()
{
ServerCertificateCustomValidationCallback = (_, certificate, _, _) =>
certificate is X509Certificate2 certificate2 &&
- String.Equals(certificate2.Thumbprint, this._integrationTestCertificate.Thumbprint, StringComparison.OrdinalIgnoreCase)
+ String.Equals(certificate2.Thumbprint, this.IntegrationTestCertificate.Thumbprint, StringComparison.OrdinalIgnoreCase)
};
return new HttpClient(handler, disposeHandler: true);
diff --git a/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs b/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs
index 5d77973..495f811 100644
--- a/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs
+++ b/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs
@@ -4,11 +4,12 @@
namespace SecurityService.IntergrationTests.Common
{
- using System.Threading.Tasks;
using NLog;
using Reqnroll;
using Shared.IntegrationTesting;
using Shared.Logger;
+ using System.Threading;
+ using System.Threading.Tasks;
[Binding]
[Scope(Tag = "base")]
@@ -52,7 +53,8 @@ public async Task StartSystem()
this.TestingContext.Logger = logger;
this.TestingContext.Logger.LogInformation("About to Start Containers for Scenario Run");
- await this.TestingContext.DockerHelper.StartContainersForScenarioRun(scenarioName, dockerServices).ConfigureAwait(false);
+ await this.TestingContext.DockerHelper.StartContainersForScenarioRun(scenarioName, dockerServices,
+ CancellationToken.None).ConfigureAwait(false);
this.TestingContext.Logger.LogInformation("Containers for Scenario Run Started");
}
diff --git a/SecurityService.OpenIdConnect.IntegrationTests/Common/SharedSteps.cs b/SecurityService.OpenIdConnect.IntegrationTests/Common/SharedSteps.cs
index 6fd6462..f4f45a3 100644
--- a/SecurityService.OpenIdConnect.IntegrationTests/Common/SharedSteps.cs
+++ b/SecurityService.OpenIdConnect.IntegrationTests/Common/SharedSteps.cs
@@ -78,28 +78,30 @@ private async Task CreateIdentityResource(CreateIdentityResourceRequest createId
Result> identityResourceListResult = await this.TestingContext.DockerHelper.SecurityServiceClient.GetIdentityResources(this.TestingContext.DockerHelper.AccessToken,cancellationToken);
identityResourceListResult.IsSuccess.ShouldBeTrue();
List identityResourceList = identityResourceListResult.Data;
- if (identityResourceList == null || identityResourceList.Any() == false) {
- Result result = await this.TestingContext.DockerHelper.SecurityServiceClient.CreateIdentityResource(this.TestingContext.DockerHelper.AccessToken,createIdentityResourceRequest, cancellationToken).ConfigureAwait(false);
- result.IsSuccess.ShouldBeTrue();
+ IdentityResourceResponse existingIdentityResource = identityResourceList?
+ .SingleOrDefault(identityResource => identityResource.Name == createIdentityResourceRequest.Name);
- this.TestingContext.IdentityResources.Add(createIdentityResourceRequest.Name);
- }
- else
+ if (existingIdentityResource is not null)
{
- if (identityResourceList.Any(i => i.Name == createIdentityResourceRequest.Name))
- {
- return;
- }
-
- Result result = await this
- .TestingContext.DockerHelper.SecurityServiceClient
- .CreateIdentityResource(this.TestingContext.DockerHelper.AccessToken,createIdentityResourceRequest, cancellationToken)
- .ConfigureAwait(false);
- result.IsSuccess.ShouldBeTrue();
-
+ existingIdentityResource.DisplayName.ShouldBe(createIdentityResourceRequest.DisplayName);
+ existingIdentityResource.Description.ShouldBe(createIdentityResourceRequest.Description);
+
+ List expectedClaims = createIdentityResourceRequest.Claims ?? new List();
+ existingIdentityResource.Claims
+ .OrderBy(claim => claim)
+ .ShouldBe(expectedClaims.OrderBy(claim => claim));
this.TestingContext.IdentityResources.Add(createIdentityResourceRequest.Name);
+ return;
}
+
+ Result result = await this
+ .TestingContext.DockerHelper.SecurityServiceClient
+ .CreateIdentityResource(this.TestingContext.DockerHelper.AccessToken,createIdentityResourceRequest, cancellationToken)
+ .ConfigureAwait(false);
+ result.IsSuccess.ShouldBeTrue(result.Message);
+
+ this.TestingContext.IdentityResources.Add(createIdentityResourceRequest.Name);
}
[Given(@"I create the following clients")]