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(arg, type, new SerialiserOptions(SerialiserPropertyFormat.SnakeCase)); } + public String AccessToken; - public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices) + public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices, CancellationToken cancellationToken) { - this._integrationTestCertificate = IntegrationTestCertificate.Create(); - try { - await base.StartContainersForScenarioRun(scenarioName, dockerServices); + await base.StartContainersForScenarioRun(scenarioName, dockerServices, cancellationToken).ConfigureAwait(false); Func securityServiceBaseAddressResolver = _ => $"https://localhost:{this.SecurityServicePort}"; HttpClient httpClient = this.CreatePinnedHttpClient(); this.SecurityServiceClient = new SecurityServiceClient(securityServiceBaseAddressResolver, httpClient, Serialise, Deserialise); - SimpleResults.Result bootstrapToken = await this.SecurityServiceClient.GetToken("management-bootstrap", "management-bootstrap-secret", CancellationToken.None); + SimpleResults.Result bootstrapToken = + await this.SecurityServiceClient + .GetToken("management-bootstrap", "management-bootstrap-secret", CancellationToken.None) + .ConfigureAwait(false); + if (bootstrapToken.IsFailed || String.IsNullOrWhiteSpace(bootstrapToken.Data?.AccessToken)) { throw new InvalidOperationException("Unable to obtain the integration-test management bootstrap token."); } - this.AccessToken = bootstrapToken.Data.AccessToken; + this.AccessToken = bootstrapToken.Data.AccessToken; ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault; } catch @@ -65,7 +67,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."); } @@ -75,8 +77,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"); environmentVariables.Add("ServiceOptions:PasswordOptions:RequireUpperCase", "false"); @@ -111,7 +113,7 @@ public override ContainerBuilder SetupSecurityServiceContainer() ContainerBuilder securityServiceContainer = new ContainerBuilder(imageDetailsResult.Data.imageName) .WithName(this.SecurityServiceContainerName) .WithEnvironment(environmentVariables) - .WithBindMount(this._integrationTestCertificate.CertificateDirectory, this._integrationTestCertificate.ContainerCertificateDirectory, AccessMode.ReadOnly); + .WithBindMount(this.IntegrationTestCertificate.CertificateDirectory, this.IntegrationTestCertificate.ContainerCertificateDirectory, AccessMode.ReadOnly); Int32? hostPort = this.GetHostPort(ContainerType.SecurityService); if (hostPort is null || hostPort <= 0) @@ -126,20 +128,14 @@ public override ContainerBuilder SetupSecurityServiceContainer() return securityServiceContainer; } - public override Task CreateSubscriptions() + public override Task CreateSubscriptions(CancellationToken cancellationToken) { return Task.CompletedTask; } - 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."); } @@ -148,7 +144,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.IntegrationTests/Common/GenericSteps.cs b/SecurityService.IntegrationTests/Common/GenericSteps.cs index 4a144ba..bc10bf1 100644 --- a/SecurityService.IntegrationTests/Common/GenericSteps.cs +++ b/SecurityService.IntegrationTests/Common/GenericSteps.cs @@ -4,17 +4,18 @@ namespace SecurityService.IntergrationTests.Common { + using Ductus.FluentDocker.Builders; + using Ductus.FluentDocker.Common; + using Ductus.FluentDocker.Model.Builders; + using Ductus.FluentDocker.Services; + using Ductus.FluentDocker.Services.Extensions; using NLog; + using Reqnroll; using Shared.IntegrationTesting; using Shared.Logger; - using System.Threading.Tasks; - using Ductus.FluentDocker.Services; - using Ductus.FluentDocker.Model.Builders; - using Ductus.FluentDocker.Services.Extensions; using System.IO; - using Ductus.FluentDocker.Builders; - using Ductus.FluentDocker.Common; - using Reqnroll; + using System.Threading; + using System.Threading.Tasks; [Binding] [Scope(Tag = "base")] @@ -58,7 +59,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.IntegrationTests/IdentityResource/IdentityResourceSteps.cs b/SecurityService.IntegrationTests/IdentityResource/IdentityResourceSteps.cs index aae6f4f..52839b8 100644 --- a/SecurityService.IntegrationTests/IdentityResource/IdentityResourceSteps.cs +++ b/SecurityService.IntegrationTests/IdentityResource/IdentityResourceSteps.cs @@ -61,8 +61,9 @@ public async Task WhenIGetTheIdentityResourceWithNameTheIdentityResourceDetailsA [When(@"I get the identity resources (.*) identity resource details are returned as follows")] public async Task WhenIGetTheIdentityResourcesIdentityResourceDetailsAreReturnedAsFollows(Int32 numberOfIdentityResources, DataTable table) { + table.Rows.Count.ShouldBe(numberOfIdentityResources); List expectedIdentityResourceDetailsList = table.Rows.ToIdentityResourceResponses(); await this.SecurityServiceSteps.WhenIGetTheIdentityResourcesIdentityResourceDetailsAreReturnedAsFollows(expectedIdentityResourceDetailsList, CancellationToken.None); } } -} \ No newline at end of file +} diff --git a/SecurityService.IntegrationTests/Roles/RolesSteps.cs b/SecurityService.IntegrationTests/Roles/RolesSteps.cs index f48441b..439569b 100644 --- a/SecurityService.IntegrationTests/Roles/RolesSteps.cs +++ b/SecurityService.IntegrationTests/Roles/RolesSteps.cs @@ -62,6 +62,7 @@ public async Task WhenIGetTheRoleWithNameTheRoleDetailsAreReturnedAsFollows(Stri [When(@"I get the roles (.*) roles details are returned as follows")] public async Task WhenIGetTheRolesRolesDetailsAreReturnedAsFollows(Int32 numberOfRoles, Table table) { + table.Rows.Count.ShouldBe(numberOfRoles); List requests = table.Rows.ToRoleResponses(); await this.SecurityServiceSteps.WhenIGetTheRolesRolesDetailsAreReturnedAsFollows(requests, CancellationToken.None); } diff --git a/SecurityService.IntegrationTests/Users/Users.feature b/SecurityService.IntegrationTests/Users/Users.feature index 0bdabc0..b9c8a04 100644 --- a/SecurityService.IntegrationTests/Users/Users.feature +++ b/SecurityService.IntegrationTests/Users/Users.feature @@ -39,8 +39,9 @@ Scenario: Require and remove MFA for a user Then the user MFA policy operation succeeds When I remove MFA for user 'mfauser@testing.co.uk' Then the user MFA policy operation succeeds - When I get the users 3 users details are returned as follows + When I get the users 4 users details are returned as follows | Email Address | Phone Number | Given Name | Middle Name | Family Name | Claims | Roles |RegistrationDate | | testuser1@testing.co.uk | 123456789 | Test | | User 1 | emailaddress:testuser1@testing.co.uk, givenname:Test, surname:User 1 | TestRole1 |Today | | testuser2@testing.co.uk | 123456789 | Test | | User 2 | emailaddress:testuser2@testing.co.uk, givenname:Test, surname:User 2 | TestRole2 |Today | | testuser3@testing.co.uk | 123456789 | Test | | User 3 | emailaddress:testuser3@testing.co.uk, givenname:Test, surname:User 3 | TestRole3 |Today | + | mfauser@testing.co.uk | 123456789 | MFA | | User | emailaddress:mfauser@testing.co.uk, givenname:MFA, surname:User | TestRole1 |Today | diff --git a/SecurityService.IntegrationTests/Users/Users.feature.cs b/SecurityService.IntegrationTests/Users/Users.feature.cs index 12263e1..cf93845 100644 --- a/SecurityService.IntegrationTests/Users/Users.feature.cs +++ b/SecurityService.IntegrationTests/Users/Users.feature.cs @@ -383,8 +383,17 @@ await testRunner.WhenAsync("I get the user with user name \'testuser3@testing.co "emailaddress:testuser3@testing.co.uk, givenname:Test, surname:User 3", "TestRole3", "Today"}); + table7.AddRow(new string[] { + "mfauser@testing.co.uk", + "123456789", + "MFA", + "", + "User", + "emailaddress:mfauser@testing.co.uk, givenname:MFA, surname:User", + "TestRole1", + "Today"}); #line 42 - await testRunner.WhenAsync("I get the users 3 users details are returned as follows", ((string)(null)), table7, "When "); + await testRunner.WhenAsync("I get the users 4 users details are returned as follows", ((string)(null)), table7, "When "); #line hidden } await this.ScenarioCleanupAsync(); diff --git a/SecurityService.IntegrationTests/Users/UsersSteps.cs b/SecurityService.IntegrationTests/Users/UsersSteps.cs index fcb33d6..0f74072 100644 --- a/SecurityService.IntegrationTests/Users/UsersSteps.cs +++ b/SecurityService.IntegrationTests/Users/UsersSteps.cs @@ -65,6 +65,7 @@ public async Task GivenICreateTheFollowingUsers(DataTable table){ [When(@"I get the users (.*) users details are returned as follows")] public async Task WhenIGetTheUsersUsersDetailsAreReturnedAsFollows(Int32 numberOfUsers, DataTable table){ + table.Rows.Count.ShouldBe(numberOfUsers); List userDetailsList = table.Rows.ToUserResponses(); await this.SecurityServiceSteps.WhenIGetTheUsersUsersDetailsAreReturnedAsFollows(userDetailsList, CancellationToken.None); } diff --git a/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs b/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs index 99dd7c9..db77680 100644 --- a/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs +++ b/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs @@ -39,7 +39,6 @@ public class DockerHelper : Shared.IntegrationTesting.TestContainers.DockerHelpe /// 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")]