From 502d3bf8731715357e1cacd94f402c1a26246a58 Mon Sep 17 00:00:00 2001 From: "Khaled Y.M" Date: Wed, 17 Jan 2024 20:40:44 +0000 Subject: [PATCH 1/5] Prepare development version 0.22.0 --- api/pom.xml | 2 +- basic-auth/pom.xml | 2 +- benchmarks/pom.xml | 2 +- bindings/pom.xml | 2 +- bom/pom.xml | 2 +- bootstrap-steps/pom.xml | 2 +- bootstrap/pom.xml | 2 +- config/pom.xml | 2 +- dal/cache/pom.xml | 2 +- dal/dal-common/pom.xml | 2 +- dal/persistence/pom.xml | 2 +- dal/pom.xml | 2 +- emb/pom.xml | 2 +- external/email/pom.xml | 2 +- external/pom.xml | 2 +- external/sms/pom.xml | 2 +- injection/pom.xml | 2 +- jwt/pom.xml | 2 +- ldap/pom.xml | 2 +- plugins/account-lock/pom.xml | 2 +- plugins/pom.xml | 2 +- plugins/verification-plugin/pom.xml | 2 +- pom.xml | 2 +- rest/pom.xml | 2 +- service-api/pom.xml | 2 +- service/pom.xml | 2 +- sessions/pom.xml | 2 +- 27 files changed, 27 insertions(+), 27 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 15ad8d70..71c4c786 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/basic-auth/pom.xml b/basic-auth/pom.xml index afeafb78..61041fe6 100644 --- a/basic-auth/pom.xml +++ b/basic-auth/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index c8774bab..d8f25a7c 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/bindings/pom.xml b/bindings/pom.xml index 04fe8010..9d2361f5 100644 --- a/bindings/pom.xml +++ b/bindings/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/bom/pom.xml b/bom/pom.xml index 8fbd1e84..433694a4 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/bootstrap-steps/pom.xml b/bootstrap-steps/pom.xml index d138d3c3..ff68a933 100644 --- a/bootstrap-steps/pom.xml +++ b/bootstrap-steps/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 27d72704..0eaaf134 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/config/pom.xml b/config/pom.xml index b7b2fd87..77377671 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/dal/cache/pom.xml b/dal/cache/pom.xml index 02e08020..f84f9cc7 100644 --- a/dal/cache/pom.xml +++ b/dal/cache/pom.xml @@ -5,7 +5,7 @@ dal com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/dal/dal-common/pom.xml b/dal/dal-common/pom.xml index 8ca7e5ae..13abc02b 100644 --- a/dal/dal-common/pom.xml +++ b/dal/dal-common/pom.xml @@ -5,7 +5,7 @@ dal com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/dal/persistence/pom.xml b/dal/persistence/pom.xml index 303dcc72..b445417d 100644 --- a/dal/persistence/pom.xml +++ b/dal/persistence/pom.xml @@ -5,7 +5,7 @@ dal com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/dal/pom.xml b/dal/pom.xml index 4d4180da..6a980d84 100644 --- a/dal/pom.xml +++ b/dal/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/emb/pom.xml b/emb/pom.xml index 1c752c4d..fcf9bb87 100644 --- a/emb/pom.xml +++ b/emb/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/external/email/pom.xml b/external/email/pom.xml index 7ad907a3..bdf762fc 100644 --- a/external/email/pom.xml +++ b/external/email/pom.xml @@ -5,7 +5,7 @@ external com.nexblocks.authguard - 0.21.0 + 0.22.0 4.0.0 diff --git a/external/pom.xml b/external/pom.xml index 668eb97f..76f5d851 100644 --- a/external/pom.xml +++ b/external/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/external/sms/pom.xml b/external/sms/pom.xml index 4cd3b256..354df590 100644 --- a/external/sms/pom.xml +++ b/external/sms/pom.xml @@ -5,7 +5,7 @@ external com.nexblocks.authguard - 0.21.0 + 0.22.0 4.0.0 diff --git a/injection/pom.xml b/injection/pom.xml index 9a6590b4..b957c51b 100644 --- a/injection/pom.xml +++ b/injection/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/jwt/pom.xml b/jwt/pom.xml index f99c4009..36a6cd9b 100644 --- a/jwt/pom.xml +++ b/jwt/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/ldap/pom.xml b/ldap/pom.xml index 8f0930a6..0ea9d6c6 100644 --- a/ldap/pom.xml +++ b/ldap/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/plugins/account-lock/pom.xml b/plugins/account-lock/pom.xml index 41e4150f..8ede7144 100644 --- a/plugins/account-lock/pom.xml +++ b/plugins/account-lock/pom.xml @@ -5,7 +5,7 @@ plugins com.nexblocks.authguard - 0.21.0 + 0.22.0 4.0.0 diff --git a/plugins/pom.xml b/plugins/pom.xml index fa20e29d..c76c7244 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/plugins/verification-plugin/pom.xml b/plugins/verification-plugin/pom.xml index 3b65fad8..098281a2 100644 --- a/plugins/verification-plugin/pom.xml +++ b/plugins/verification-plugin/pom.xml @@ -5,7 +5,7 @@ plugins com.nexblocks.authguard - 0.21.0 + 0.22.0 4.0.0 diff --git a/pom.xml b/pom.xml index fd707af3..5d0fe245 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.nexblocks.authguard authguard pom - 0.21.0 + 0.22.0 diff --git a/rest/pom.xml b/rest/pom.xml index 45ba0cb1..5aacdae5 100644 --- a/rest/pom.xml +++ b/rest/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/service-api/pom.xml b/service-api/pom.xml index 1e394ac3..a8433b00 100644 --- a/service-api/pom.xml +++ b/service-api/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/service/pom.xml b/service/pom.xml index b0db1b4e..89af9a1c 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 diff --git a/sessions/pom.xml b/sessions/pom.xml index 6dde9eab..346243f6 100644 --- a/sessions/pom.xml +++ b/sessions/pom.xml @@ -5,7 +5,7 @@ authguard com.nexblocks.authguard - 0.21.0 + 0.22.0 ../pom.xml 4.0.0 From fa259e1f5f084f12c5113e08de51f0be81d24dfe Mon Sep 17 00:00:00 2001 From: "Khaled Y.M" Date: Wed, 17 Jan 2024 21:30:54 +0000 Subject: [PATCH 2/5] Change DTOs to use string IDs --- .../authguard/api/dto/entities/Account.java | 2 +- .../api/dto/entities/AccountLock.java | 2 +- .../api/dto/entities/ActionToken.java | 2 +- .../authguard/api/dto/entities/ApiKey.java | 4 +- .../authguard/api/dto/entities/App.java | 4 +- .../authguard/api/dto/entities/Client.java | 4 +- .../api/dto/entities/Credentials.java | 2 +- .../api/dto/entities/ExchangeAttempt.java | 4 +- .../api/dto/entities/Permission.java | 2 +- .../authguard/api/dto/entities/Role.java | 2 +- .../api/dto/requests/ApiKeyRequest.java | 2 +- .../api/dto/requests/CreateAppRequest.java | 2 +- .../api/dto/requests/OtpRequest.java | 2 +- .../api/dto/validation/IdParser.java | 14 + api/src/main/resources/openapi.yml | 286 +++++++----------- .../ApiKeysRequestValidatorTest.java | 12 +- .../validators/OtpRequestValidatorTest.java | 2 +- .../rest/routes/ActionTokensRoute.java | 3 +- .../authguard/rest/routes/ApiKeysRoute.java | 5 +- .../authguard/rest/routes/OtpRoute.java | 3 +- .../authguard/rest/AccountsApiTest.java | 2 +- .../rest/mappers/RestMapperTest.java | 102 ++++--- .../service/exceptions/codes/ErrorCode.java | 3 +- 23 files changed, 202 insertions(+), 264 deletions(-) create mode 100644 api/src/main/java/com/nexblocks/authguard/api/dto/validation/IdParser.java diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java index b24dca26..d2d7b3c7 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java @@ -15,7 +15,7 @@ @JsonSerialize(as = AccountDTO.class) @JsonDeserialize(as = AccountDTO.class) public interface Account { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/AccountLock.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/AccountLock.java index 6e1351a8..842524cd 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/AccountLock.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/AccountLock.java @@ -8,6 +8,6 @@ @Value.Immutable @DTOStyle public interface AccountLock { - long getAccountId(); + String getAccountId(); Instant getExpiresAt(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ActionToken.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ActionToken.java index 1539ac4e..5191b0c8 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ActionToken.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ActionToken.java @@ -12,6 +12,6 @@ public interface ActionToken { String getToken(); String getAction(); - Long getAccountId(); + String getAccountId(); long getValidFor(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java index f3a5f44e..84ce4036 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java @@ -12,10 +12,10 @@ @JsonSerialize(as = ApiKeyDTO.class) @JsonDeserialize(as = ApiKeyDTO.class) public interface ApiKey { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); - Long getAppId(); + String getAppId(); String getKey(); String getType(); boolean isForClient(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java index 63dd52a2..c265ea61 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java @@ -13,12 +13,12 @@ @JsonSerialize(as = AppDTO.class) @JsonDeserialize(as = AppDTO.class) public interface App { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); String getExternalId(); String getName(); - Long getAccountId(); + String getAccountId(); String getDomain(); String getBaseUrl(); List getPermissions(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java index 6558ccd9..dae10f28 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java @@ -12,12 +12,12 @@ @JsonSerialize(as = ClientDTO.class) @JsonDeserialize(as = ClientDTO.class) public interface Client { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); String getExternalId(); String getName(); - Long getAccountId(); + String getAccountId(); String getDomain(); String getBaseUrl(); String getClientType(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Credentials.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Credentials.java index 36add5df..401dab9a 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Credentials.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Credentials.java @@ -11,7 +11,7 @@ @DTOStyle @JsonDeserialize(as = CredentialsDTO.class) public interface Credentials { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); Instant getPasswordUpdatedAt(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ExchangeAttempt.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ExchangeAttempt.java index 69460c73..115c15f5 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ExchangeAttempt.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ExchangeAttempt.java @@ -8,10 +8,10 @@ @Value.Immutable @DTOStyle public interface ExchangeAttempt { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); - Long getEntityId(); + String getEntityId(); String getExchangeFrom(); String getExchangeTo(); boolean isSuccessful(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java index 27941bc0..d540fcd0 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java @@ -12,7 +12,7 @@ @JsonSerialize(as = PermissionDTO.class) @JsonDeserialize(as = PermissionDTO.class) public interface Permission { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); String getGroup(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java index 82603953..031b904a 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java @@ -12,7 +12,7 @@ @JsonDeserialize(as = RoleDTO.class) @JsonSerialize(as = RoleDTO.class) public interface Role { - long getId(); + String getId(); Instant getCreatedAt(); Instant getLastModified(); String getName(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java index ca80417b..9cde487d 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java @@ -14,7 +14,7 @@ public interface ApiKeyRequest { boolean isForClient(); String getKeyType(); - Long getAppId(); + String getAppId(); Instant getExpiresAt(); DurationRequestDTO getValidFor(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java index 9dcf8d41..0811b638 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java @@ -15,7 +15,7 @@ public interface CreateAppRequest { String getExternalId(); String getName(); - Long getAccountId(); + String getAccountId(); String getDomain(); List getPermissions(); List getScopes(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/OtpRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/OtpRequest.java index 3ce6e272..01430e89 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/OtpRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/OtpRequest.java @@ -10,6 +10,6 @@ @JsonDeserialize(as = OtpRequestDTO.class) @JsonSerialize(as = OtpRequestDTO.class) public interface OtpRequest { - Long getPasswordId(); + String getPasswordId(); String getPassword(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/validation/IdParser.java b/api/src/main/java/com/nexblocks/authguard/api/dto/validation/IdParser.java new file mode 100644 index 00000000..74b02a0d --- /dev/null +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/validation/IdParser.java @@ -0,0 +1,14 @@ +package com.nexblocks.authguard.api.dto.validation; + +import com.nexblocks.authguard.service.exceptions.ServiceException; +import com.nexblocks.authguard.service.exceptions.codes.ErrorCode; + +public class IdParser { + public static Long from(final String idString) { + try { + return Long.parseLong(idString); + } catch (NumberFormatException ex) { + throw new ServiceException(ErrorCode.INVALID_REQUEST_VALUE, "Value '" + idString + "' is not a valid ID"); + } + } +} diff --git a/api/src/main/resources/openapi.yml b/api/src/main/resources/openapi.yml index b669f8fe..cb96974b 100644 --- a/api/src/main/resources/openapi.yml +++ b/api/src/main/resources/openapi.yml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: AuthGuard API - version: 0.13.0 + version: 0.20.0 security: - apiKey: [] @@ -255,108 +255,31 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/complete: - post: - operationId: createAccountWithCredentials - description: Create an account as well as its credentials - tags: - - Accounts - parameters: - - $ref: "#/components/parameters/IdempotentKeyHeader" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreateCompleteAccountRequest" - responses: - 201: - description: Success - $ref: "#/components/responses/CompleteAccountResponse" - 400: - description: Bad request - $ref: "#/components/responses/ErrorResponse" - 409: - description: Conflict - if the idempotent key header was used to create an account before - $ref: "#/components/responses/ErrorResponse" - - # ----------------- credentials ----------------- - /credentials: - post: - operationId: createCredentials - description: Create credentials - tags: - - Credentials - parameters: - - $ref: "#/components/parameters/IdempotentKeyHeader" - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CreateCredentialsRequest" - responses: - 201: - description: Success - $ref: "#/components/responses/CredentialsResponse" - 400: - description: Bad request - $ref: "#/components/responses/ErrorResponse" - 409: - description: Conflict - if the idempotent key header was used to create credentials before - $ref: "#/components/responses/ErrorResponse" - - /credentials/{id}: - get: - operationId: getCredentialsById - description: Get credentials by ID - tags: - - Credentials - parameters: - - $ref: "#/components/parameters/IdParameter" - responses: - 200: - description: Success - $ref: "#/components/responses/CredentialsResponse" - 404: - description: Not found - $ref: "#/components/responses/ErrorResponse" - delete: - operationId: deleteCredentials - description: Delete credentials by ID - tags: - - Credentials - parameters: - - $ref: "#/components/parameters/IdParameter" - responses: - 200: - description: Success - $ref: "#/components/responses/CredentialsResponse" - 404: - description: Not found - $ref: "#/components/responses/ErrorResponse" - - /credentials/identifier/{identifier}: + /accounts/domain/{domain}/identifier/{identifier}: get: - operationId: getCredentialsByIdentifier - description: Get credentials by identifier + operationId: getAccountByIdentifier + description: Get account by identifier tags: - - Credentials + - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdentifierParameter" responses: 200: description: Success - $ref: "#/components/responses/CredentialsResponse" + $ref: "#/components/responses/AccountResponse" 404: description: Not found $ref: "#/components/responses/ErrorResponse" - /credentials/identifier/{identifier}/exists: + /accounts/domain/{domain}/identifier/{identifier}/exists: get: operationId: identifierExists description: Check whether an identifier exists or not tags: - - Credentials + - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdentifierParameter" responses: 200: @@ -364,6 +287,7 @@ paths: 404: description: Not found + # ----------------- credentials ----------------- /credentials/{id}/password: patch: operationId: updatePassword @@ -380,7 +304,7 @@ paths: responses: 200: description: Success - $ref: "#/components/responses/CredentialsResponse" + $ref: "#/components/responses/AccountResponse" 404: description: Not found $ref: "#/components/responses/ErrorResponse" @@ -401,7 +325,7 @@ paths: responses: 200: description: Success - $ref: "#/components/responses/CredentialsResponse" + $ref: "#/components/responses/AccountResponse" 404: description: Not found $ref: "#/components/responses/ErrorResponse" @@ -424,7 +348,7 @@ paths: responses: 200: description: Success - $ref: "#/components/responses/CredentialsResponse" + $ref: "#/components/responses/AccountResponse" 404: description: Not found $ref: "#/components/responses/ErrorResponse" @@ -468,7 +392,7 @@ paths: responses: 200: description: Success - $ref: "#/components/responses/CredentialsResponse" + $ref: "#/components/responses/AccountResponse" 404: description: Not found $ref: "#/components/responses/ErrorResponse" @@ -753,6 +677,8 @@ paths: description: Get an API key by ID tags: - API Keys + parameters: + - $ref: "#/components/parameters/IdParameter" requestBody: content: application/json: @@ -770,6 +696,8 @@ paths: description: Delete an API key by ID tags: - API Keys + parameters: + - $ref: "#/components/parameters/IdParameter" requestBody: content: application/json: @@ -802,19 +730,6 @@ paths: 400: description: Bad request $ref: "#/components/responses/ErrorResponse" - - get: - operationId: getAllRoles - description: Get all roles - tags: - - Roles - responses: - 201: - description: Success - $ref: "#/components/responses/RolesArrayResponse" - 400: - description: Bad request - $ref: "#/components/responses/ErrorResponse" /roles/{id}: get: @@ -822,6 +737,8 @@ paths: description: Get role by ID tags: - Roles + parameters: + - $ref: "#/components/parameters/IdParameter" responses: 200: description: Success @@ -835,6 +752,8 @@ paths: description: Delete a role tags: - Roles + parameters: + - $ref: "#/components/parameters/IdParameter" responses: 200: description: Success @@ -843,12 +762,31 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /roles/name/{name}: + /roles/domain/{domain}/: + get: + operationId: getAllRoles + description: Get all roles + tags: + - Roles + parameters: + - $ref: "#/components/parameters/DomainParameter" + responses: + 201: + description: Success + $ref: "#/components/responses/RolesArrayResponse" + 400: + description: Bad request + $ref: "#/components/responses/ErrorResponse" + + /roles/domain/{domain}/roles/name/{name}: get: operationId: getRoleByName description: Get a role by its name tags: - Roles + parameters: + - $ref: "#/components/parameters/DomainParameter" + - $ref: "#/components/parameters/NameParameter" responses: 200: description: Success @@ -876,20 +814,6 @@ paths: 400: description: Bad request $ref: "#/components/responses/ErrorResponse" - get: - operationId: getPermissions - description: Get all permissions - tags: - - Permissions - parameters: - - name: group - in: query - schema: - type: string - responses: - 200: - description: Success - $ref: "#/components/responses/PermissionsArrayResponse" /permissions/{id}: get: @@ -897,6 +821,8 @@ paths: description: Get a permission by ID tags: - Permissions + parameters: + - $ref: "#/components/parameters/IdParameter" responses: 200: description: Success @@ -909,6 +835,8 @@ paths: description: Delete a permission by ID tags: - Permissions + parameters: + - $ref: "#/components/parameters/IdParameter" responses: 200: description: Success @@ -916,13 +844,29 @@ paths: 404: description: Not found $ref: "#/components/responses/ErrorResponse" + + /permissions/domain/{domain}/: + get: + operationId: getAllPermissions + description: Get all permissions + tags: + - Permissions + parameters: + - $ref: "#/components/parameters/DomainParameter" + responses: + 200: + description: Success + $ref: "#/components/responses/PermissionsArrayResponse" - /permissions/group/{group}: + /permissions/domain/{domain}/group/{group}: get: operationId: getPermissionsByGroupName description: Get a permissions by group name tags: - Permissions + parameters: + - $ref: "#/components/parameters/DomainParameter" + - $ref: "#/components/parameters/GroupParameter" responses: 200: description: Success @@ -1197,6 +1141,13 @@ components: name: id in: path required: true + schema: + type: number + + DomainParameter: + name: domain + in: path + required: true schema: type: string @@ -1207,6 +1158,20 @@ components: schema: type: string + NameParameter: + name: name + in: path + required: true + schema: + type: string + + GroupParameter: + name: group + in: path + required: true + schema: + type: string + EmailParameter: name: email in: path @@ -1243,20 +1208,6 @@ components: schema: $ref: "#/components/schemas/Account" - CredentialsResponse: - description: _ - content: - application/json: - schema: - $ref: "#/components/schemas/Credentials" - - CompleteAccountResponse: - description: _ - content: - application/json: - schema: - $ref: "#/components/schemas/AccountWithCredentials" - ApplicationResponse: description: _ content: @@ -1361,16 +1312,6 @@ components: $ref: "#/components/schemas/ActionToken" schemas: - AccountWithCredentials: - type: object - properties: - account: - type: object - $ref: "#/components/schemas/Account" - credentials: - type: object - $ref: "#/components/schemas/Credentials" - Account: type: object properties: @@ -1412,18 +1353,6 @@ components: description: A collection of custom information to be attached to the account type: object - Credentials: - type: object - properties: - accountId: - type: string - domain: - type: string - identifiers: - type: array - items: - $ref: "#/components/schemas/UserIdentifier" - UserIdentifier: type: object properties: @@ -1435,8 +1364,8 @@ components: - PHONE_NUMBER identifier: type: string - domain: - type: string + active: + type: boolean Application: type: object @@ -1503,6 +1432,8 @@ components: Permission: type: object properties: + id: + type: string group: type: string name: @@ -1522,6 +1453,8 @@ components: Role: type: object properties: + id: + type: string name: type: string domain: @@ -1627,6 +1560,7 @@ components: type: object required: - domain + - identifiers properties: externalId: type: string @@ -1640,6 +1574,12 @@ components: type: string fullName: type: string + identifiers: + type: array + items: + $ref: "#/components/schemas/UserIdentifier" + plainPassword: + type: string email: type: object $ref: "#/components/schemas/Email" @@ -1689,9 +1629,12 @@ components: type: object required: - domain + - name properties: externalId: type: string + name: + type: string domain: type: string accountId: @@ -1731,39 +1674,12 @@ components: - AUTH - ADMIN - CreateCredentialsRequest: - type: object - required: - - domain - - identifiers - properties: - accountId: - type: string - domain: - type: string - identifiers: - type: array - items: - $ref: "#/components/schemas/UserIdentifier" - plainPassword: - type: string - UpdateCredentialsPasswordRequest: type: object properties: plainPassword: type: string - CreateCompleteAccountRequest: - type: object - properties: - account: - type: object - $ref: "#/components/schemas/CreateAccountRequest" - credentials: - type: object - $ref: "#/components/schemas/CreateCredentialsRequest" - ApiKeyRequest: type: object required: diff --git a/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/ApiKeysRequestValidatorTest.java b/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/ApiKeysRequestValidatorTest.java index 5459f088..5fa42a81 100644 --- a/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/ApiKeysRequestValidatorTest.java +++ b/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/ApiKeysRequestValidatorTest.java @@ -17,7 +17,7 @@ class ApiKeysRequestValidatorTest { @Test void validate() { final ApiKeyRequestDTO request = ApiKeyRequestDTO.builder() - .appId(1L) + .appId("1") .keyType("default") .build(); @@ -44,7 +44,7 @@ void validateMissingValues() { @Test void validateWithZeroValidity() { final ApiKeyRequestDTO request = ApiKeyRequestDTO.builder() - .appId(1L) + .appId("1") .keyType("default") .validFor(DurationRequestDTO.builder() .build()) @@ -59,7 +59,7 @@ void validateWithZeroValidity() { @Test void validateWithNonZeroValidity() { final ApiKeyRequestDTO request = ApiKeyRequestDTO.builder() - .appId(1L) + .appId("1") .keyType("default") .validFor(DurationRequestDTO.builder() .days(1) @@ -77,7 +77,7 @@ void validateWithNonZeroValidity() { @Test void validateWithNegativeDaysValidity() { final ApiKeyRequestDTO request = ApiKeyRequestDTO.builder() - .appId(1L) + .appId("1") .keyType("default") .validFor(DurationRequestDTO.builder() .days(-1) @@ -95,7 +95,7 @@ void validateWithNegativeDaysValidity() { @Test void validateWithNegativeHoursValidity() { final ApiKeyRequestDTO request = ApiKeyRequestDTO.builder() - .appId(1L) + .appId("1") .keyType("default") .validFor(DurationRequestDTO.builder() .hours(-1) @@ -113,7 +113,7 @@ void validateWithNegativeHoursValidity() { @Test void validateWithNegativeMinutesValidity() { final ApiKeyRequestDTO request = ApiKeyRequestDTO.builder() - .appId(1L) + .appId("1") .keyType("default") .validFor(DurationRequestDTO.builder() .minutes(-1) diff --git a/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/OtpRequestValidatorTest.java b/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/OtpRequestValidatorTest.java index bf350faf..c905c085 100644 --- a/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/OtpRequestValidatorTest.java +++ b/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/OtpRequestValidatorTest.java @@ -15,7 +15,7 @@ class OtpRequestValidatorTest { @Test void validateValid() { final OtpRequestDTO request = OtpRequestDTO.builder() - .passwordId(1L) + .passwordId("1") .password("password") .build(); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java index a8ca38b5..9fc2e297 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java @@ -6,6 +6,7 @@ import com.nexblocks.authguard.api.dto.entities.ActionTokenRequestType; import com.nexblocks.authguard.api.dto.entities.AuthResponseDTO; import com.nexblocks.authguard.api.dto.requests.ActionTokenRequestDTO; +import com.nexblocks.authguard.api.dto.validation.IdParser; import com.nexblocks.authguard.api.dto.validation.violations.Violation; import com.nexblocks.authguard.api.dto.validation.violations.ViolationType; import com.nexblocks.authguard.api.routes.ActionTokensApi; @@ -64,7 +65,7 @@ public void createToken(final Context context) { CompletableFuture result; if (request.getType() == ActionTokenRequestType.OTP) { - result = actionTokenService.generateFromOtp(request.getOtp().getPasswordId(), + result = actionTokenService.generateFromOtp(IdParser.from(request.getOtp().getPasswordId()), request.getOtp().getPassword(), request.getAction()); } else { AuthRequestBO authRequest = restMapper.toBO(request.getBasic()); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java index 5cf52c73..41816d7f 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java @@ -4,6 +4,7 @@ import com.nexblocks.authguard.api.dto.entities.ApiKeyDTO; import com.nexblocks.authguard.api.dto.requests.ApiKeyRequestDTO; import com.nexblocks.authguard.api.dto.requests.ApiKeyVerificationRequestDTO; +import com.nexblocks.authguard.api.dto.validation.IdParser; import com.nexblocks.authguard.api.dto.validation.violations.Violation; import com.nexblocks.authguard.api.dto.validation.violations.ViolationType; import com.nexblocks.authguard.api.routes.ApiKeysApi; @@ -46,8 +47,8 @@ public void generate(final Context context) { Duration validFor = request.getValidFor() == null ? Duration.ZERO : request.getValidFor().toDuration(); CompletableFuture key = request.isForClient() ? - apiKeysService.generateClientApiKey(request.getAppId(), request.getKeyType(), validFor) : - apiKeysService.generateApiKey(request.getAppId(), request.getKeyType(), validFor); + apiKeysService.generateClientApiKey(IdParser.from(request.getAppId()), request.getKeyType(), validFor) : + apiKeysService.generateApiKey(IdParser.from(request.getAppId()), request.getKeyType(), validFor); context.status(201).json(key.thenApply(restMapper::toDTO)); } diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/OtpRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/OtpRoute.java index 77dbc2d9..ef89f3db 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/OtpRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/OtpRoute.java @@ -4,6 +4,7 @@ import com.nexblocks.authguard.api.annotations.DependsOnConfiguration; import com.nexblocks.authguard.api.dto.entities.AuthResponseDTO; import com.nexblocks.authguard.api.dto.requests.OtpRequestDTO; +import com.nexblocks.authguard.api.dto.validation.IdParser; import com.nexblocks.authguard.api.routes.OtpApi; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; @@ -32,7 +33,7 @@ public void verify(final Context context) { OtpRequestDTO body = otpRequestBodyHandler.getValidated(context); RequestContextBO requestContext = RequestContextExtractor.extractWithoutIdempotentKey(context); - CompletableFuture tokens = otpService.authenticate(body.getPasswordId(), body.getPassword(), requestContext) + CompletableFuture tokens = otpService.authenticate(IdParser.from(body.getPasswordId()), body.getPassword(), requestContext) .thenApply(restMapper::toDTO); context.json(tokens); diff --git a/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java b/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java index 9382fc5d..ccc81cfa 100644 --- a/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java +++ b/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java @@ -94,6 +94,6 @@ void create() { assertThat(response).isEqualToIgnoringGivenFields(requestDTO, "id", "deleted", "createdAt", "lastModified", "passwordUpdatedAt", "social", "identityProvider", "passwordVersion"); - assertThat(response.getId()).isEqualTo(serviceResponse.getId()); + assertThat(response.getId()).isEqualTo(String.valueOf(serviceResponse.getId())); } } \ No newline at end of file diff --git a/rest/src/test/java/com/nexblocks/authguard/rest/mappers/RestMapperTest.java b/rest/src/test/java/com/nexblocks/authguard/rest/mappers/RestMapperTest.java index 8e7ab655..ec1a9b48 100644 --- a/rest/src/test/java/com/nexblocks/authguard/rest/mappers/RestMapperTest.java +++ b/rest/src/test/java/com/nexblocks/authguard/rest/mappers/RestMapperTest.java @@ -6,8 +6,8 @@ import com.nexblocks.authguard.service.model.*; import org.jeasy.random.EasyRandom; import org.jeasy.random.EasyRandomParameters; -import org.jeasy.random.api.Randomizer; import org.jeasy.random.randomizers.misc.BooleanRandomizer; +import org.jeasy.random.randomizers.number.NumberRandomizer; import org.jeasy.random.randomizers.text.StringRandomizer; import org.junit.jupiter.api.Test; @@ -16,25 +16,37 @@ import static org.assertj.core.api.Assertions.assertThat; class RestMapperTest { - private final EasyRandom easyRandom = new EasyRandom(new EasyRandomParameters() + private final EasyRandom boRandomizer = new EasyRandom(new EasyRandomParameters() .collectionSizeRange(1, 3) - .randomize(UserIdentifierBO.class, new Randomizer() { - @Override - public UserIdentifierBO getRandomValue() { - return UserIdentifierBO.builder() - .identifier(new StringRandomizer().getRandomValue()) - .active(new BooleanRandomizer().getRandomValue()) - .build(); - } - }) + .randomize(UserIdentifierBO.class, () -> UserIdentifierBO.builder() + .identifier(new StringRandomizer().getRandomValue()) + .active(new BooleanRandomizer().getRandomValue()) + .build()) ); + + private final EasyRandom dtoRandomizer = new EasyRandom(new EasyRandomParameters() + .collectionSizeRange(1, 3) + .randomize(field -> field.getName().equals("id") || field.getName().endsWith("Id"), () -> String.valueOf(new NumberRandomizer().getRandomValue())) + ); + private final RestMapper restMapper = new RestMapperImpl(); - private void convertAndBack(final Class fromClass, final Function map, + private void boToDtoAndBack(final Class fromClass, final Function map, final Function inverse, final String... ignoreFields) { - final T original = easyRandom.nextObject(fromClass); - final R converted = map.apply(original); - final T back = inverse.apply(converted); + T original = boRandomizer.nextObject(fromClass); + R converted = map.apply(original); + T back = inverse.apply(converted); + + assertThat(back).usingRecursiveComparison() + .ignoringFieldsMatchingRegexes(ignoreFields) + .isEqualTo(original); + } + + private void dtoToBoAndBack(final Class fromClass, final Function map, + final Function inverse, final String... ignoreFields) { + T original = dtoRandomizer.nextObject(fromClass); + R converted = map.apply(original); + T back = inverse.apply(converted); assertThat(back).usingRecursiveComparison() .ignoringFieldsMatchingRegexes(ignoreFields) @@ -43,99 +55,99 @@ private void convertAndBack(final Class fromClass, final Function Date: Fri, 19 Jan 2024 21:50:31 +0000 Subject: [PATCH 3/5] Add domain to all relevant endpoints --- .../authguard/api/dto/entities/Account.java | 3 +- .../authguard/api/dto/entities/App.java | 3 +- .../authguard/api/dto/entities/Client.java | 3 +- .../api/dto/entities/DomainScoped.java | 5 +++ .../api/dto/entities/Permission.java | 3 +- .../authguard/api/dto/entities/Role.java | 3 +- .../api/dto/entities/UserIdentifier.java | 3 +- .../dto/requests/CreateAccountRequest.java | 8 +--- .../api/dto/requests/CreateAppRequest.java | 4 +- .../api/dto/requests/CreateClientRequest.java | 4 +- .../dto/requests/CreatePermissionRequest.java | 4 +- .../api/dto/requests/CreateRoleRequest.java | 4 +- .../dto/requests/PasswordResetRequest.java | 4 +- .../requests/PasswordResetTokenRequest.java | 4 +- .../authguard/api/routes/AccountsApi.java | 10 ++--- .../authguard/api/routes/ActionTokensApi.java | 2 +- .../authguard/api/routes/ApiKeysApi.java | 2 +- .../authguard/api/routes/ApplicationsApi.java | 2 +- .../authguard/api/routes/AuthApi.java | 2 +- .../authguard/api/routes/ClientsApi.java | 2 +- .../authguard/api/routes/CredentialsApi.java | 2 +- .../authguard/api/routes/OtpApi.java | 2 +- .../authguard/api/routes/PasswordlessApi.java | 2 +- .../authguard/api/routes/PermissionsApi.java | 6 +-- .../authguard/api/routes/RolesApi.java | 6 +-- .../authguard/api/routes/VerificationApi.java | 2 +- .../access/DomainAuthorizationHandler.java | 40 +++++++++++++++++++ .../rest/access/EntityDomainChecker.java | 40 +++++++++++++++++++ .../authguard/rest/routes/ApiKeysRoute.java | 4 +- .../rest/server/ServerMiddlewareHandlers.java | 3 ++ .../authguard/rest/util/BodyHandler.java | 13 ++++-- .../authguard/rest/AccountsApiTest.java | 14 +++---- .../authguard/rest/AuthRouteTest.java | 2 +- .../nexblocks/authguard/service/Domains.java | 5 +++ .../ServiceAuthorizationException.java | 10 +++++ .../service/exceptions/codes/ErrorCode.java | 3 +- 36 files changed, 166 insertions(+), 63 deletions(-) create mode 100644 api/src/main/java/com/nexblocks/authguard/api/dto/entities/DomainScoped.java create mode 100644 rest/src/main/java/com/nexblocks/authguard/rest/access/DomainAuthorizationHandler.java create mode 100644 rest/src/main/java/com/nexblocks/authguard/rest/access/EntityDomainChecker.java create mode 100644 service-api/src/main/java/com/nexblocks/authguard/service/Domains.java diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java index d2d7b3c7..0ee73fe5 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Account.java @@ -14,7 +14,7 @@ @DTOStyle @JsonSerialize(as = AccountDTO.class) @JsonDeserialize(as = AccountDTO.class) -public interface Account { +public interface Account extends DomainScoped { String getId(); Instant getCreatedAt(); Instant getLastModified(); @@ -28,7 +28,6 @@ public interface Account { String getMiddleName(); String getLastName(); String getFullName(); - String getDomain(); List getPermissions(); List getRoles(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java index c265ea61..b35f85b8 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/App.java @@ -12,14 +12,13 @@ @DTOStyle @JsonSerialize(as = AppDTO.class) @JsonDeserialize(as = AppDTO.class) -public interface App { +public interface App extends DomainScoped { String getId(); Instant getCreatedAt(); Instant getLastModified(); String getExternalId(); String getName(); String getAccountId(); - String getDomain(); String getBaseUrl(); List getPermissions(); List getRoles(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java index dae10f28..91ba0e49 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Client.java @@ -11,14 +11,13 @@ @DTOStyle @JsonSerialize(as = ClientDTO.class) @JsonDeserialize(as = ClientDTO.class) -public interface Client { +public interface Client extends DomainScoped { String getId(); Instant getCreatedAt(); Instant getLastModified(); String getExternalId(); String getName(); String getAccountId(); - String getDomain(); String getBaseUrl(); String getClientType(); boolean isActive(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/DomainScoped.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/DomainScoped.java new file mode 100644 index 00000000..87ce936b --- /dev/null +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/DomainScoped.java @@ -0,0 +1,5 @@ +package com.nexblocks.authguard.api.dto.entities; + +public interface DomainScoped { + String getDomain(); +} diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java index d540fcd0..29d08f87 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Permission.java @@ -11,11 +11,10 @@ @DTOStyle @JsonSerialize(as = PermissionDTO.class) @JsonDeserialize(as = PermissionDTO.class) -public interface Permission { +public interface Permission extends DomainScoped { String getId(); Instant getCreatedAt(); Instant getLastModified(); String getGroup(); String getName(); - String getDomain(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java index 031b904a..31c0671f 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/Role.java @@ -11,10 +11,9 @@ @DTOStyle @JsonDeserialize(as = RoleDTO.class) @JsonSerialize(as = RoleDTO.class) -public interface Role { +public interface Role extends DomainScoped { String getId(); Instant getCreatedAt(); Instant getLastModified(); String getName(); - String getDomain(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/UserIdentifier.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/UserIdentifier.java index 31b60c27..9fd973c5 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/UserIdentifier.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/UserIdentifier.java @@ -9,10 +9,9 @@ @DTOStyle @JsonSerialize(as = UserIdentifierDTO.class) @JsonDeserialize(as = UserIdentifierDTO.class) -public interface UserIdentifier { +public interface UserIdentifier extends DomainScoped { Type getType(); String getIdentifier(); - String getDomain(); @Value.Default default boolean isActive() { diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAccountRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAccountRequest.java index e62a9869..9655f1dc 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAccountRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAccountRequest.java @@ -2,10 +2,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.nexblocks.authguard.api.dto.entities.AccountEmailDTO; -import com.nexblocks.authguard.api.dto.entities.PermissionDTO; -import com.nexblocks.authguard.api.dto.entities.PhoneNumberDTO; -import com.nexblocks.authguard.api.dto.entities.UserIdentifierDTO; +import com.nexblocks.authguard.api.dto.entities.*; import com.nexblocks.authguard.api.dto.style.DTOStyle; import org.immutables.value.Value; @@ -16,14 +13,13 @@ @DTOStyle @JsonDeserialize(as = CreateAccountRequestDTO.class) @JsonSerialize(as = CreateAccountRequestDTO.class) -public interface CreateAccountRequest { +public interface CreateAccountRequest extends DomainScoped { String getExternalId(); String getFirstName(); String getMiddleName(); String getLastName(); String getFullName(); - String getDomain(); AccountEmailDTO getEmail(); AccountEmailDTO getBackupEmail(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java index 0811b638..dcd22d6b 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateAppRequest.java @@ -1,5 +1,6 @@ package com.nexblocks.authguard.api.dto.requests; +import com.nexblocks.authguard.api.dto.entities.DomainScoped; import com.nexblocks.authguard.api.dto.entities.PermissionDTO; import com.nexblocks.authguard.api.dto.style.DTOStyle; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -12,11 +13,10 @@ @DTOStyle @JsonDeserialize(as = CreateAppRequestDTO.class) @JsonSerialize(as = CreateAppRequestDTO.class) -public interface CreateAppRequest { +public interface CreateAppRequest extends DomainScoped { String getExternalId(); String getName(); String getAccountId(); - String getDomain(); List getPermissions(); List getScopes(); List getRoles(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateClientRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateClientRequest.java index 99498f31..b94ef3a0 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateClientRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateClientRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.nexblocks.authguard.api.dto.entities.DomainScoped; import com.nexblocks.authguard.api.dto.style.DTOStyle; import org.immutables.value.Value; @@ -10,11 +11,10 @@ @DTOStyle @JsonDeserialize(as = CreateClientRequestDTO.class) @JsonSerialize(as = CreateClientRequestDTO.class) -public interface CreateClientRequest { +public interface CreateClientRequest extends DomainScoped { String getExternalId(); String getName(); Long getAccountId(); - String getDomain(); String getBaseUrl(); ClientType getClientType(); diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreatePermissionRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreatePermissionRequest.java index 34e2b062..d871d89c 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreatePermissionRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreatePermissionRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.nexblocks.authguard.api.dto.entities.DomainScoped; import com.nexblocks.authguard.api.dto.style.DTOStyle; import org.immutables.value.Value; @@ -9,8 +10,7 @@ @DTOStyle @JsonSerialize(as = CreatePermissionRequestDTO.class) @JsonDeserialize(as = CreatePermissionRequestDTO.class) -public interface CreatePermissionRequest { +public interface CreatePermissionRequest extends DomainScoped { String getGroup(); String getName(); - String getDomain(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateRoleRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateRoleRequest.java index 1accc872..3531972e 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateRoleRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/CreateRoleRequest.java @@ -1,5 +1,6 @@ package com.nexblocks.authguard.api.dto.requests; +import com.nexblocks.authguard.api.dto.entities.DomainScoped; import com.nexblocks.authguard.api.dto.style.DTOStyle; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -9,7 +10,6 @@ @DTOStyle @JsonDeserialize(as = CreateRoleRequestDTO.class) @JsonSerialize(as = CreateRoleRequestDTO.class) -public interface CreateRoleRequest { +public interface CreateRoleRequest extends DomainScoped { String getName(); - String getDomain(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetRequest.java index dd1fdb91..ad306f8b 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.nexblocks.authguard.api.dto.entities.DomainScoped; import com.nexblocks.authguard.api.dto.style.DTOStyle; import org.immutables.value.Value; @@ -9,11 +10,10 @@ @DTOStyle @JsonSerialize(as = PasswordResetRequestDTO.class) @JsonDeserialize(as = PasswordResetRequestDTO.class) -public interface PasswordResetRequest { +public interface PasswordResetRequest extends DomainScoped { boolean isByToken(); String getResetToken(); String getIdentifier(); String getOldPassword(); String getNewPassword(); - String getDomain(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetTokenRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetTokenRequest.java index db41cbe9..ac24e8a2 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetTokenRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/PasswordResetTokenRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.nexblocks.authguard.api.dto.entities.DomainScoped; import com.nexblocks.authguard.api.dto.style.DTOStyle; import org.immutables.value.Value; @@ -9,7 +10,6 @@ @DTOStyle @JsonSerialize(as = PasswordResetTokenRequestDTO.class) @JsonDeserialize(as = PasswordResetTokenRequestDTO.class) -public interface PasswordResetTokenRequest { +public interface PasswordResetTokenRequest extends DomainScoped { String getIdentifier(); - String getDomain(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/AccountsApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/AccountsApi.java index 6bba3a28..5d25047b 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/AccountsApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/AccountsApi.java @@ -9,7 +9,7 @@ public abstract class AccountsApi implements ApiRoute { @Override public String getPath() { - return "accounts"; + return "/domains/:domain/accounts"; } public void addEndpoints() { @@ -18,12 +18,12 @@ public void addEndpoints() { get("/:id", this::getById, ActorRoles.adminClient()); delete("/:id", this::deleteAccount, ActorRoles.adminClient()); patch("/:id", this::patchAccount, ActorRoles.adminClient()); - get("/domain/:domain/identifier/:identifier", this::getByIdentifier, ActorRoles.adminClient()); - get("/domain/:domain/identifier/:identifier/exists", this::identifierExists, ActorRoles.adminOrAuthClient()); + get("/identifier/:identifier", this::getByIdentifier, ActorRoles.adminClient()); + get("/identifier/:identifier/exists", this::identifierExists, ActorRoles.adminOrAuthClient()); get("/externalId/:id", this::getByExternalId, ActorRoles.adminClient()); - get("/domain/:domain/email/:email", this::getByEmail, ActorRoles.adminClient()); - get("/domain/:domain/email/:email/exists", this::emailExists, ActorRoles.adminOrAuthClient()); + get("/email/:email", this::getByEmail, ActorRoles.adminClient()); + get("/email/:email/exists", this::emailExists, ActorRoles.adminOrAuthClient()); patch("/:id/permissions", this::updatePermissions, ActorRoles.adminClient()); patch("/:id/roles", this::updateRoles, ActorRoles.adminClient()); diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/ActionTokensApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/ActionTokensApi.java index 4eba95c8..2d5a7a10 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/ActionTokensApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/ActionTokensApi.java @@ -8,7 +8,7 @@ public abstract class ActionTokensApi implements ApiRoute { @Override public String getPath() { - return "actions"; + return "/domains/:domain/actions"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/ApiKeysApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/ApiKeysApi.java index e06aa2ca..9f69f9a2 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/ApiKeysApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/ApiKeysApi.java @@ -9,7 +9,7 @@ public abstract class ApiKeysApi implements ApiRoute { @Override public String getPath() { - return "keys"; + return "/domains/:domain/keys"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/ApplicationsApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/ApplicationsApi.java index fda77c32..05a1d311 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/ApplicationsApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/ApplicationsApi.java @@ -9,7 +9,7 @@ public abstract class ApplicationsApi implements ApiRoute { @Override public String getPath() { - return "apps"; + return "/domains/:domain/apps"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/AuthApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/AuthApi.java index a1ab05e0..2e90bdc2 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/AuthApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/AuthApi.java @@ -10,7 +10,7 @@ public abstract class AuthApi implements ApiRoute { @Override public String getPath() { - return "auth"; + return "/domains/:domain/auth"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/ClientsApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/ClientsApi.java index 3d367334..9b4418a2 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/ClientsApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/ClientsApi.java @@ -8,7 +8,7 @@ public abstract class ClientsApi implements ApiRoute { @Override public String getPath() { - return "clients"; + return "/domains/:domain/clients"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/CredentialsApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/CredentialsApi.java index 9141803c..5ac64ab1 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/CredentialsApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/CredentialsApi.java @@ -9,7 +9,7 @@ public abstract class CredentialsApi implements ApiRoute { @Override public String getPath() { - return "credentials"; + return "/domains/:domain/credentials"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/OtpApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/OtpApi.java index a8edcfb5..3057a3b5 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/OtpApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/OtpApi.java @@ -9,7 +9,7 @@ public abstract class OtpApi implements ApiRoute { @Override public String getPath() { - return "otp"; + return "/domains/:domain/otp"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/PasswordlessApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/PasswordlessApi.java index 0a0f946d..493a6fbd 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/PasswordlessApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/PasswordlessApi.java @@ -9,7 +9,7 @@ public abstract class PasswordlessApi implements ApiRoute { @Override public String getPath() { - return "passwordless"; + return "/domains/:domain/passwordless"; } @Override diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/PermissionsApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/PermissionsApi.java index 047d9d38..c2fbc301 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/PermissionsApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/PermissionsApi.java @@ -9,7 +9,7 @@ public abstract class PermissionsApi implements ApiRoute { @Override public String getPath() { - return "permissions"; + return "/domains/:domain/permissions"; } @Override @@ -17,8 +17,8 @@ public void addEndpoints() { post("/", this::create, ActorRoles.adminClient()); get("/:id", this::getById, ActorRoles.adminClient()); delete("/:id", this::getById, ActorRoles.adminClient()); - get("/domain/:domain/group/:group", this::getByGroup, ActorRoles.adminClient()); - get("/domain/:domain", this::getAll, ActorRoles.adminClient()); + get("/group/:group", this::getByGroup, ActorRoles.adminClient()); + get("", this::getAll, ActorRoles.adminClient()); } public abstract void create(final Context context); diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/RolesApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/RolesApi.java index dd6d9382..c2daa7b4 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/RolesApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/RolesApi.java @@ -9,7 +9,7 @@ public abstract class RolesApi implements ApiRoute { @Override public String getPath() { - return "roles"; + return "/domains/:domain/roles"; } @Override @@ -17,8 +17,8 @@ public void addEndpoints() { post("/", this::create, ActorRoles.adminClient()); get("/:id", this::getById, ActorRoles.adminClient());; delete("/:id", this::deleteById, ActorRoles.adminClient());; - get("/domain/:domain/name/:name", this::getByName, ActorRoles.adminClient()); - get("/domain/:domain", this::getAll, ActorRoles.adminClient()); + get("/name/:name", this::getByName, ActorRoles.adminClient()); + get("", this::getAll, ActorRoles.adminClient()); } public abstract void create(final Context context); diff --git a/api/src/main/java/com/nexblocks/authguard/api/routes/VerificationApi.java b/api/src/main/java/com/nexblocks/authguard/api/routes/VerificationApi.java index abf28d81..00d46663 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/routes/VerificationApi.java +++ b/api/src/main/java/com/nexblocks/authguard/api/routes/VerificationApi.java @@ -9,7 +9,7 @@ public abstract class VerificationApi implements ApiRoute { @Override public String getPath() { - return "verification"; + return "/domains/:domain/verification"; } @Override diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/access/DomainAuthorizationHandler.java b/rest/src/main/java/com/nexblocks/authguard/rest/access/DomainAuthorizationHandler.java new file mode 100644 index 00000000..ad76c5c3 --- /dev/null +++ b/rest/src/main/java/com/nexblocks/authguard/rest/access/DomainAuthorizationHandler.java @@ -0,0 +1,40 @@ +package com.nexblocks.authguard.rest.access; + +import com.nexblocks.authguard.api.dto.entities.Error; +import com.nexblocks.authguard.service.Domains; +import com.nexblocks.authguard.service.model.AccountBO; +import com.nexblocks.authguard.service.model.ClientBO; +import io.javalin.http.Context; +import io.javalin.http.Handler; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class DomainAuthorizationHandler implements Handler { + @Override + public void handle(@NotNull Context ctx) throws Exception { + Object actor = ctx.attribute("actor"); + + if (actor == null) { + return; + } + + String domain = ctx.pathParam("domain"); + + if (actor instanceof AccountBO) { + String actorDomain = ((AccountBO) actor).getDomain(); + + if (Objects.equals(actorDomain, domain) || Objects.equals(actorDomain, Domains.GLOBAL_RESERVED_DOMAIN)) { + return; + } + } else if (actor instanceof ClientBO) { + String actorDomain = ((ClientBO) actor).getDomain(); + + if (Objects.equals(actorDomain, domain) || Objects.equals(actorDomain, Domains.GLOBAL_RESERVED_DOMAIN)) { + return; + } + } + + ctx.status(403).json(new Error("401", "Domain is outside the scope of the actor")); + } +} diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/access/EntityDomainChecker.java b/rest/src/main/java/com/nexblocks/authguard/rest/access/EntityDomainChecker.java new file mode 100644 index 00000000..efa2a67e --- /dev/null +++ b/rest/src/main/java/com/nexblocks/authguard/rest/access/EntityDomainChecker.java @@ -0,0 +1,40 @@ +package com.nexblocks.authguard.rest.access; + +import com.nexblocks.authguard.api.dto.entities.DomainScoped; +import com.nexblocks.authguard.service.Domains; +import com.nexblocks.authguard.service.exceptions.ServiceAuthorizationException; +import com.nexblocks.authguard.service.exceptions.codes.ErrorCode; +import io.javalin.http.Context; + +import java.util.Objects; + +public class EntityDomainChecker { + public static T checkActorAuthorized(final T entity, final Context actor) { + String actorDomain = ((DomainScoped) actor).getDomain(); + + boolean isAuthorized = Objects.equals(actorDomain, Domains.GLOBAL_RESERVED_DOMAIN) || + Objects.equals(actorDomain, entity.getDomain()); + + if (isAuthorized) { + return entity; + } + + throw new ServiceAuthorizationException(ErrorCode.ENTITY_OUT_OF_SCOPE, + "Entity is out of the scope of the actor", true); + } + + public static void checkEntityDomainOrFail(final T entity, final Context context) { + String domain = context.pathParam("domain"); + + if (domain == null || domain.isBlank()) { + return; + } + + if (Objects.equals(entity.getDomain(), domain)) { + return; + } + + throw new ServiceAuthorizationException(ErrorCode.ENTITY_OUT_OF_SCOPE, + "Request does not match the domain", true); + } +} diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java index 41816d7f..36fea9fd 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java @@ -2,6 +2,7 @@ import com.google.inject.Inject; import com.nexblocks.authguard.api.dto.entities.ApiKeyDTO; +import com.nexblocks.authguard.api.dto.entities.AppDTO; import com.nexblocks.authguard.api.dto.requests.ApiKeyRequestDTO; import com.nexblocks.authguard.api.dto.requests.ApiKeyVerificationRequestDTO; import com.nexblocks.authguard.api.dto.validation.IdParser; @@ -72,7 +73,8 @@ public void getById(final Context context) { public void verify(final Context context) { ApiKeyVerificationRequestDTO verificationRequest = verificationRequestBodyHandler.getValidated(context); - CompletableFuture app = apiKeysService.validateApiKey(verificationRequest.getKey(), verificationRequest.getKeyType()); + CompletableFuture app = apiKeysService.validateApiKey(verificationRequest.getKey(), verificationRequest.getKeyType()) + .thenApply(restMapper::toDTO); context.json(app); } diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/server/ServerMiddlewareHandlers.java b/rest/src/main/java/com/nexblocks/authguard/rest/server/ServerMiddlewareHandlers.java index 62fcdf0d..aad81024 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/server/ServerMiddlewareHandlers.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/server/ServerMiddlewareHandlers.java @@ -2,6 +2,7 @@ import com.nexblocks.authguard.rest.access.AuthorizationHandler; import com.google.inject.Injector; +import com.nexblocks.authguard.rest.access.DomainAuthorizationHandler; import io.javalin.Javalin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +21,8 @@ public void configure(final Javalin app) { app.before(context -> context.attribute("time", System.currentTimeMillis())); app.before(injector.getInstance(AuthorizationHandler.class)); + app.before("/domain/:domain", new DomainAuthorizationHandler()); + app.after(context -> { final Long now = System.currentTimeMillis(); final Long start = context.attribute("time"); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/util/BodyHandler.java b/rest/src/main/java/com/nexblocks/authguard/rest/util/BodyHandler.java index 756ff3dc..f3155acd 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/util/BodyHandler.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/util/BodyHandler.java @@ -1,8 +1,10 @@ package com.nexblocks.authguard.rest.util; +import com.nexblocks.authguard.api.dto.entities.DomainScoped; import com.nexblocks.authguard.api.dto.validation.Validator; import com.nexblocks.authguard.api.dto.validation.validators.Validators; import com.nexblocks.authguard.api.dto.validation.violations.Violation; +import com.nexblocks.authguard.rest.access.EntityDomainChecker; import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestJsonMapper; import io.javalin.http.Context; @@ -23,8 +25,13 @@ public T get(final Context context) { } public T getValidated(final Context context) { - final T body = RestJsonMapper.asClass(context.body(), bodyClass); - final List violations = validator.validate(body); + T body = RestJsonMapper.asClass(context.body(), bodyClass); + + if (DomainScoped.class.isAssignableFrom(body.getClass())) { + EntityDomainChecker.checkEntityDomainOrFail((DomainScoped) body, context); + } + + List violations = validator.validate(body); if (!violations.isEmpty()) { throw new RequestValidationException(violations); @@ -46,7 +53,7 @@ public Builder bodyClass(final Class bodyClass) { } public BodyHandler build() { - final Validator validator = Validators.getForClass(bodyClass); + Validator validator = Validators.getForClass(bodyClass); if (validator == null) { throw new IllegalStateException("No validator was found for class " + bodyClass); diff --git a/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java b/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java index ccc81cfa..6c5d9045 100644 --- a/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java +++ b/rest/src/test/java/com/nexblocks/authguard/rest/AccountsApiTest.java @@ -30,7 +30,7 @@ class AccountsApiTest extends AbstractRouteTest { private static final Logger LOG = LoggerFactory.getLogger(AccountsApiTest.class); - private static final String ENDPOINT = "accounts"; + private static final String ENDPOINT = "domains/main/accounts"; AccountsApiTest() { super(ENDPOINT); @@ -50,7 +50,7 @@ void reset() { @Test void create() { - final CreateAccountRequestDTO requestDTO = CreateAccountRequestDTO.builder() + CreateAccountRequestDTO requestDTO = CreateAccountRequestDTO.builder() .externalId("external") .email(AccountEmailDTO.builder() .email("email@server.com") @@ -65,19 +65,19 @@ void create() { )) .plainPassword("password") .build(); - final RequestContextBO requestContext = RequestContextBO.builder() + RequestContextBO requestContext = RequestContextBO.builder() .idempotentKey(UUID.randomUUID().toString()) .build(); - final AccountBO accountBO = mapper().toBO(requestDTO); - final AccountBO serviceResponse = accountBO.withId(UUID.randomUUID().getMostSignificantBits()); + AccountBO accountBO = mapper().toBO(requestDTO); + AccountBO serviceResponse = accountBO.withId(UUID.randomUUID().getMostSignificantBits()); Mockito.when(accountsService.create(Mockito.eq(accountBO), Mockito.any())) .thenReturn(CompletableFuture.completedFuture(serviceResponse)); LOG.info("Request {}", requestDTO); - final ValidatableResponse httpResponse = given().body(requestDTO) + ValidatableResponse httpResponse = given().body(requestDTO) .contentType(ContentType.JSON) .header(IdempotencyHeader.HEADER_NAME, requestContext.getIdempotentKey()) .post(url()) @@ -85,7 +85,7 @@ void create() { .statusCode(201) .contentType(ContentType.JSON); - final AccountDTO response = httpResponse + AccountDTO response = httpResponse .extract() .response() .getBody() diff --git a/rest/src/test/java/com/nexblocks/authguard/rest/AuthRouteTest.java b/rest/src/test/java/com/nexblocks/authguard/rest/AuthRouteTest.java index 8d5638a2..f6317a1d 100644 --- a/rest/src/test/java/com/nexblocks/authguard/rest/AuthRouteTest.java +++ b/rest/src/test/java/com/nexblocks/authguard/rest/AuthRouteTest.java @@ -26,7 +26,7 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class AuthRouteTest extends AbstractRouteTest { - private static String ENDPOINT = "auth"; + private static String ENDPOINT = "domains/main/auth"; AuthRouteTest() { super(ENDPOINT); diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/Domains.java b/service-api/src/main/java/com/nexblocks/authguard/service/Domains.java new file mode 100644 index 00000000..ff9ec833 --- /dev/null +++ b/service-api/src/main/java/com/nexblocks/authguard/service/Domains.java @@ -0,0 +1,5 @@ +package com.nexblocks.authguard.service; + +public class Domains { + public static String GLOBAL_RESERVED_DOMAIN = "global"; +} diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/ServiceAuthorizationException.java b/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/ServiceAuthorizationException.java index 83a58f3b..20443a37 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/ServiceAuthorizationException.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/ServiceAuthorizationException.java @@ -6,11 +6,20 @@ public class ServiceAuthorizationException extends ServiceException { private final EntityType entityType; private final Long entityId; + private final boolean isForbidden; public ServiceAuthorizationException(final ErrorCode errorCode, final String message) { super(errorCode, message); this.entityType = null; this.entityId = null; + this.isForbidden = false; + } + + public ServiceAuthorizationException(final ErrorCode errorCode, final String message, final boolean isForbidden) { + super(errorCode, message); + this.entityType = null; + this.entityId = null; + this.isForbidden = isForbidden; } public ServiceAuthorizationException(final ErrorCode errorCode, final String message, @@ -18,6 +27,7 @@ public ServiceAuthorizationException(final ErrorCode errorCode, final String mes super(errorCode, message); this.entityType = entityType; this.entityId = entityId; + this.isForbidden = false; } public EntityType getEntityType() { diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/codes/ErrorCode.java b/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/codes/ErrorCode.java index f6e7c241..e46d8083 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/codes/ErrorCode.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/exceptions/codes/ErrorCode.java @@ -50,7 +50,8 @@ public enum ErrorCode { MISSING_REQUEST_QUERY("RQ.011"), INVALID_REQUEST_QUERY("RQ.012"), - INVALID_REQUEST_VALUE("RQ.013"); + INVALID_REQUEST_VALUE("RQ.013"), + ENTITY_OUT_OF_SCOPE("RQ.0.014"); private final String code; From cd7623cb8d5bf35ecc07a8f56f14ab0d8801185b Mon Sep 17 00:00:00 2001 From: "Khaled Y.M" Date: Sun, 21 Jan 2024 21:04:15 +0000 Subject: [PATCH 4/5] Make all relevant services domain-scoped --- .../PasswordResetRequestValidator.java | 1 + .../jwt/exchange/AccountsServiceAdapter.java | 2 +- .../jwt/exchange/OtpToAccessToken.java | 2 +- .../authguard/jwt/exchange/OtpToOidc.java | 2 +- .../exchange/PasswordlessToAccessToken.java | 2 +- .../jwt/exchange/PasswordlessToOidc.java | 2 +- .../jwt/exchange/RefreshToAccessToken.java | 2 +- .../jwt/oauth/service/OAuthService.java | 2 +- .../exchange/AuthorizationCodeToOidcTest.java | 4 +- .../exchange/RefreshToAccessTokenTest.java | 8 +-- .../jwt/oauth/service/OAuthServiceTest.java | 4 +- .../authguard/rest/routes/AccountsRoute.java | 23 ++++--- .../rest/routes/ActionTokensRoute.java | 5 +- .../authguard/rest/routes/ApiKeysRoute.java | 15 ++-- .../rest/routes/ApplicationsRoute.java | 21 +++--- .../authguard/rest/routes/ClientsRoute.java | 13 ++-- .../rest/routes/CredentialsRoute.java | 15 ++-- .../rest/routes/PermissionsRoute.java | 5 +- .../authguard/rest/routes/RolesRoute.java | 5 +- .../rest/routes/VerificationRoute.java | 3 +- .../nexblocks/authguard/rest/util/Domain.java | 9 +++ .../service/AccountCredentialsService.java | 10 +-- .../authguard/service/AccountsService.java | 22 +++--- .../authguard/service/ActionTokenService.java | 4 +- .../authguard/service/ApiKeysService.java | 8 +-- .../service/ApplicationsService.java | 8 +-- .../authguard/service/ClientsService.java | 9 +-- .../authguard/service/CrudService.java | 6 +- .../service/VerificationService.java | 6 +- .../impl/AccountCredentialsServiceImpl.java | 39 ++++++----- .../service/impl/AccountsServiceImpl.java | 69 ++++++++++++------- .../service/impl/ActionTokenServiceImpl.java | 8 +-- .../service/impl/ApiKeysServiceImpl.java | 30 ++++---- .../service/impl/ApplicationsServiceImpl.java | 37 +++++----- .../service/impl/ClientsServiceImpl.java | 35 ++++++---- .../impl/ExchangeAttemptsServiceImpl.java | 6 +- .../service/impl/PermissionsServiceImpl.java | 6 +- .../service/impl/RolesServiceImpl.java | 6 +- .../service/impl/VerificationServiceImpl.java | 22 +++--- .../AccountCredentialsServiceImplTest.java | 36 +++++----- .../service/impl/AccountsServiceImplTest.java | 34 ++++----- .../impl/ActionTokenServiceImplTest.java | 8 +-- .../service/impl/ApiKeysServiceImplTest.java | 47 +++++++------ .../impl/ApplicationsServiceImplTest.java | 16 +++-- .../impl/PermissionsServiceImplTest.java | 6 +- .../impl/VerificationServiceImplTest.java | 38 +++++----- .../sessions/exchange/OtpToSession.java | 2 +- .../exchange/PasswordlessToSession.java | 2 +- 48 files changed, 367 insertions(+), 298 deletions(-) create mode 100644 rest/src/main/java/com/nexblocks/authguard/rest/util/Domain.java diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidator.java b/api/src/main/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidator.java index 82fddf2a..0670d682 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidator.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidator.java @@ -13,6 +13,7 @@ public class PasswordResetRequestValidator implements Validator validate(final PasswordResetRequestDTO obj) { return FluentValidator.begin() + .validate("domain", obj.getDomain(), Constraints.required) .validate("identifier", obj.getIdentifier(), identifier -> { if (!obj.isByToken() && identifier == null) { return Collections.singletonList( diff --git a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/AccountsServiceAdapter.java b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/AccountsServiceAdapter.java index 797a6bc1..ebac67f0 100644 --- a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/AccountsServiceAdapter.java +++ b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/AccountsServiceAdapter.java @@ -22,7 +22,7 @@ public AccountsServiceAdapter(final AccountsService accountsService) { } public CompletableFuture getAccount(final long accountId) { - return accountsService.getById(accountId) + return accountsService.getByIdUnchecked(accountId) .thenCompose(opt -> opt.map(CompletableFuture::completedFuture) .orElseGet(() -> CompletableFuture.failedFuture(new ServiceAuthorizationException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, "Account does not exist")))); diff --git a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToAccessToken.java b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToAccessToken.java index 33e0b6c6..cbfa278e 100644 --- a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToAccessToken.java +++ b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToAccessToken.java @@ -32,7 +32,7 @@ public OtpToAccessToken(final AccountsService accountsService, final OtpVerifier @Override public CompletableFuture exchange(final AuthRequestBO request) { return otpVerifier.verifyAccountTokenAsync(request.getToken()) - .thenCompose(accountsService::getById) + .thenCompose(id -> accountsService.getById(id, request.getDomain())) .thenCompose(opt -> { if (opt.isEmpty()) { return CompletableFuture.failedFuture(new ServiceAuthorizationException(ErrorCode.GENERIC_AUTH_FAILURE, diff --git a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToOidc.java b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToOidc.java index 29fd988a..a8fb3a3c 100644 --- a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToOidc.java +++ b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/OtpToOidc.java @@ -32,7 +32,7 @@ public OtpToOidc(final AccountsService accountsService, final OtpVerifier otpVer @Override public CompletableFuture exchange(final AuthRequestBO request) { return otpVerifier.verifyAccountTokenAsync(request.getToken()) - .thenCompose(accountsService::getById) + .thenCompose(id -> accountsService.getById(id, request.getDomain())) .thenCompose(opt -> { if (opt.isEmpty()) { return CompletableFuture.failedFuture(new ServiceAuthorizationException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, diff --git a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToAccessToken.java b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToAccessToken.java index 2e26f4da..6407f096 100644 --- a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToAccessToken.java +++ b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToAccessToken.java @@ -33,7 +33,7 @@ public PasswordlessToAccessToken(final AccountsService accountsService, @Override public CompletableFuture exchange(final AuthRequestBO request) { return passwordlessVerifier.verifyAccountTokenAsync(request.getToken()) - .thenCompose(accountsService::getById) + .thenCompose(id -> accountsService.getById(id, request.getDomain())) .thenCompose(opt -> { if (opt.isEmpty()) { return CompletableFuture.failedFuture( diff --git a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToOidc.java b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToOidc.java index 2caa4a32..e891e0a3 100644 --- a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToOidc.java +++ b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/PasswordlessToOidc.java @@ -33,7 +33,7 @@ public PasswordlessToOidc(final AccountsService accountsService, @Override public CompletableFuture exchange(final AuthRequestBO request) { return passwordlessVerifier.verifyAccountTokenAsync(request.getToken()) - .thenCompose(accountsService::getById) + .thenCompose(id -> accountsService.getById(id, request.getDomain())) .thenCompose(opt -> { if (opt.isEmpty()) { return CompletableFuture.failedFuture( diff --git a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessToken.java b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessToken.java index d7891fd0..bd8280b7 100644 --- a/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessToken.java +++ b/jwt/src/main/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessToken.java @@ -120,7 +120,7 @@ private CompletableFuture generateNewTokens(final AccountTokenDO } private CompletableFuture getAccount(final long accountId, final AccountTokenDO accountToken) { - return accountsService.getById(accountId) + return accountsService.getByIdUnchecked(accountId) .thenCompose(opt -> { if (opt.isEmpty()) { return CompletableFuture.failedFuture(new ServiceAuthorizationException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, diff --git a/jwt/src/main/java/com/nexblocks/authguard/jwt/oauth/service/OAuthService.java b/jwt/src/main/java/com/nexblocks/authguard/jwt/oauth/service/OAuthService.java index 9ae3a316..368f4947 100644 --- a/jwt/src/main/java/com/nexblocks/authguard/jwt/oauth/service/OAuthService.java +++ b/jwt/src/main/java/com/nexblocks/authguard/jwt/oauth/service/OAuthService.java @@ -138,7 +138,7 @@ private CompletableFuture getOrCreateAccount(final OAuthServiceClient DecodedJWT decoded = JWT.decode(idToken); String externalId = decoded.getSubject(); - return accountsService.getByExternalId(externalId) + return accountsService.getByExternalIdUnchecked(externalId) .thenCompose(account -> { if (account.isPresent()) { return CompletableFuture.completedFuture(account.get()); diff --git a/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/AuthorizationCodeToOidcTest.java b/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/AuthorizationCodeToOidcTest.java index b1618c87..31dd4dee 100644 --- a/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/AuthorizationCodeToOidcTest.java +++ b/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/AuthorizationCodeToOidcTest.java @@ -67,7 +67,7 @@ void exchange() { Mockito.when(authorizationCodeVerifier.verifyAndGetAccountTokenAsync(authRequest.getToken())) .thenReturn(CompletableFuture.completedFuture(accountToken)); - Mockito.when(accountsService.getById(accountToken.getAssociatedAccountId())) + Mockito.when(accountsService.getByIdUnchecked(accountToken.getAssociatedAccountId())) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(openIdConnectTokenProvider.generateToken(account, (TokenRestrictionsBO) null)) @@ -103,7 +103,7 @@ void exchangeWithRestrictions() { Mockito.when(authorizationCodeVerifier.verifyAndGetAccountTokenAsync(authRequest.getToken())) .thenReturn(CompletableFuture.completedFuture(accountToken)); - Mockito.when(accountsService.getById(accountToken.getAssociatedAccountId())) + Mockito.when(accountsService.getByIdUnchecked(accountToken.getAssociatedAccountId())) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(openIdConnectTokenProvider.generateToken(account, serviceMapper.toBO(accountToken.getTokenRestrictions()))) diff --git a/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessTokenTest.java b/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessTokenTest.java index ccab265c..525d5772 100644 --- a/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessTokenTest.java +++ b/jwt/src/test/java/com/nexblocks/authguard/jwt/exchange/RefreshToAccessTokenTest.java @@ -71,7 +71,7 @@ void exchange() throws InterruptedException { Mockito.when(accountTokensRepository.getByToken(authRequest.getToken())) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountToken))); - Mockito.when(accountsService.getById(accountId)) + Mockito.when(accountsService.getByIdUnchecked(accountId)) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accessTokenProvider.generateToken(account, null, TokenOptionsBO.builder().build())) @@ -125,7 +125,7 @@ void exchangeWithRestrictions() throws InterruptedException { Mockito.when(accountTokensRepository.getByToken(authRequest.getToken())) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountToken))); - Mockito.when(accountsService.getById(accountId)) + Mockito.when(accountsService.getByIdUnchecked(accountId)) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accessTokenProvider.generateToken(account, restrictions, TokenOptionsBO.builder().build())) @@ -189,7 +189,7 @@ void exchangeNoAccount() { Mockito.when(accountTokensRepository.getByToken(authRequest.getToken())) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountToken))); - Mockito.when(accountsService.getById(accountId)) + Mockito.when(accountsService.getByIdUnchecked(accountId)) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); // do @@ -262,7 +262,7 @@ void exchangeWithTokenOptionChecks() throws InterruptedException { Mockito.when(accountTokensRepository.getByToken(authRequest.getToken())) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountToken))); - Mockito.when(accountsService.getById(accountId)) + Mockito.when(accountsService.getByIdUnchecked(accountId)) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accessTokenProvider.generateToken(account, null, tokenOptions)) diff --git a/jwt/src/test/java/com/nexblocks/authguard/jwt/oauth/service/OAuthServiceTest.java b/jwt/src/test/java/com/nexblocks/authguard/jwt/oauth/service/OAuthServiceTest.java index 1a7477d2..52b539e3 100644 --- a/jwt/src/test/java/com/nexblocks/authguard/jwt/oauth/service/OAuthServiceTest.java +++ b/jwt/src/test/java/com/nexblocks/authguard/jwt/oauth/service/OAuthServiceTest.java @@ -186,7 +186,7 @@ void exchangeAuthorizationCodeAndCreateAccount() { return CompletableFuture.completedFuture(argWithId); }); - Mockito.when(accountsService.getByExternalId(Mockito.any())) + Mockito.when(accountsService.getByExternalIdUnchecked(Mockito.any())) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); TokensResponse actual = oAuthService.exchangeAuthorizationCode("account_test", "random", "code") @@ -210,7 +210,7 @@ void exchangeAuthorizationCodeAndGetAccount() { return CompletableFuture.completedFuture(Optional.of(session)); }); - Mockito.when(accountsService.getByExternalId("1")) + Mockito.when(accountsService.getByExternalIdUnchecked("1")) .thenReturn(CompletableFuture.completedFuture(Optional.of(AccountBO.builder().id(1).build()))); TokensResponse actual = oAuthService.exchangeAuthorizationCode("account_test", "random", "code") diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/AccountsRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/AccountsRoute.java index ba4b76d0..d8239fef 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/AccountsRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/AccountsRoute.java @@ -12,6 +12,7 @@ import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.rest.util.IdempotencyHeader; import com.nexblocks.authguard.service.AccountLocksService; import com.nexblocks.authguard.service.AccountsService; @@ -99,7 +100,7 @@ public void getById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture account = accountsService.getById(accountId.get()) + CompletableFuture account = accountsService.getById(accountId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAccountOptional) .thenApply(restMapper::toDTO); @@ -147,7 +148,7 @@ public void deleteAccount(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture account = accountsService.delete(accountId.get()) + CompletableFuture account = accountsService.delete(accountId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAccountOptional) .thenApply(restMapper::toDTO); @@ -164,7 +165,7 @@ public void patchAccount(final Context context) { UpdateAccountRequestDTO request = updateAccountRequestBodyHandler.getValidated(context); - CompletableFuture account = accountsService.patch(accountId.get(), restMapper.toBO(request)) + CompletableFuture account = accountsService.patch(accountId.get(), restMapper.toBO(request), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAccountOptional) .thenApply(restMapper::toDTO); @@ -175,7 +176,7 @@ public void patchAccount(final Context context) { public void getByExternalId(final Context context) { String accountId = context.pathParam("id"); - CompletableFuture account = accountsService.getByExternalId(accountId) + CompletableFuture account = accountsService.getByExternalId(accountId, Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAccountOptional) .thenApply(restMapper::toDTO); @@ -229,9 +230,9 @@ public void updatePermissions(final Context context) { CompletableFuture> updatedAccount; if (request.getAction() == PermissionsRequest.Action.GRANT) { - updatedAccount = accountsService.grantPermissions(accountId.get(), permissions); + updatedAccount = accountsService.grantPermissions(accountId.get(), permissions, Domain.fromContext(context)); } else { - updatedAccount = accountsService.revokePermissions(accountId.get(), permissions); + updatedAccount = accountsService.revokePermissions(accountId.get(), permissions, Domain.fromContext(context)); } context.json(updatedAccount.thenCompose(AsyncUtils::fromAccountOptional).thenApply(restMapper::toDTO)); @@ -250,9 +251,9 @@ public void updateRoles(final Context context) { CompletableFuture> updatedAccount; if (request.getAction() == RolesRequest.Action.GRANT) { - updatedAccount = accountsService.grantRoles(accountId.get(), request.getRoles()); + updatedAccount = accountsService.grantRoles(accountId.get(), request.getRoles(), Domain.fromContext(context)); } else { - updatedAccount = accountsService.revokeRoles(accountId.get(), request.getRoles()); + updatedAccount = accountsService.revokeRoles(accountId.get(), request.getRoles(), Domain.fromContext(context)); } context.json(updatedAccount.thenCompose(AsyncUtils::fromAccountOptional).thenApply(restMapper::toDTO)); @@ -266,7 +267,7 @@ public void getApps(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture> apps = applicationsService.getByAccountId(accountId.get()) + CompletableFuture> apps = applicationsService.getByAccountId(accountId.get(), Domain.fromContext(context)) .thenApply(list -> list.stream() .map(restMapper::toDTO) .collect(Collectors.toList())); @@ -282,7 +283,7 @@ public void activate(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture account = accountsService.activate(accountId.get()) + CompletableFuture account = accountsService.activate(accountId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAccountOptional) .thenApply(restMapper::toDTO); @@ -297,7 +298,7 @@ public void deactivate(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture account = accountsService.deactivate(accountId.get()) + CompletableFuture account = accountsService.deactivate(accountId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAccountOptional) .thenApply(restMapper::toDTO); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java index 9fc2e297..35fa41c0 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ActionTokensRoute.java @@ -13,6 +13,7 @@ import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.service.ActionTokenService; import com.nexblocks.authguard.service.model.ActionTokenBO; import com.nexblocks.authguard.service.model.AuthRequestBO; @@ -52,7 +53,7 @@ public void createOtp(final Context context) { )); } - CompletableFuture result = actionTokenService.generateOtp(accountId.get()) + CompletableFuture result = actionTokenService.generateOtp(accountId.get(), Domain.fromContext(context)) .thenApply(restMapper::toDTO); context.status(201).json(result); @@ -66,7 +67,7 @@ public void createToken(final Context context) { if (request.getType() == ActionTokenRequestType.OTP) { result = actionTokenService.generateFromOtp(IdParser.from(request.getOtp().getPasswordId()), - request.getOtp().getPassword(), request.getAction()); + Domain.fromContext(context), request.getOtp().getPassword(), request.getAction()); } else { AuthRequestBO authRequest = restMapper.toBO(request.getBasic()); result = actionTokenService.generateFromBasicAuth(authRequest, request.getAction()); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java index 36fea9fd..5d9bfcfa 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java @@ -12,10 +12,10 @@ import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.service.ApiKeysService; import com.nexblocks.authguard.service.exceptions.codes.ErrorCode; import com.nexblocks.authguard.service.model.ApiKeyBO; -import com.nexblocks.authguard.service.model.AppBO; import com.nexblocks.authguard.service.util.AsyncUtils; import io.javalin.core.validation.Validator; import io.javalin.http.Context; @@ -46,10 +46,11 @@ public ApiKeysRoute(final ApiKeysService apiKeysService, final RestMapper restMa public void generate(final Context context) { ApiKeyRequestDTO request = apiKeyRequestBodyHandler.getValidated(context); Duration validFor = request.getValidFor() == null ? Duration.ZERO : request.getValidFor().toDuration(); + String domain = Domain.fromContext(context); CompletableFuture key = request.isForClient() ? - apiKeysService.generateClientApiKey(IdParser.from(request.getAppId()), request.getKeyType(), validFor) : - apiKeysService.generateApiKey(IdParser.from(request.getAppId()), request.getKeyType(), validFor); + apiKeysService.generateClientApiKey(IdParser.from(request.getAppId()), domain, request.getKeyType(), validFor) : + apiKeysService.generateApiKey(IdParser.from(request.getAppId()), domain, request.getKeyType(), validFor); context.status(201).json(key.thenApply(restMapper::toDTO)); } @@ -62,7 +63,7 @@ public void getById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture apiKey = apiKeysService.getById(apiKeyId.get()) + CompletableFuture apiKey = apiKeysService.getById(apiKeyId.get(), Domain.fromContext(context)) .thenCompose(opt -> AsyncUtils.fromOptional(opt, ErrorCode.API_KEY_DOES_NOT_EXIST, "API key does not exist")) .thenApply(restMapper::toDTO); @@ -73,8 +74,8 @@ public void getById(final Context context) { public void verify(final Context context) { ApiKeyVerificationRequestDTO verificationRequest = verificationRequestBodyHandler.getValidated(context); - CompletableFuture app = apiKeysService.validateApiKey(verificationRequest.getKey(), verificationRequest.getKeyType()) - .thenApply(restMapper::toDTO); + CompletableFuture app = apiKeysService.validateApiKey(verificationRequest.getKey(), Domain.fromContext(context), + verificationRequest.getKeyType()).thenApply(restMapper::toDTO); context.json(app); } @@ -87,7 +88,7 @@ public void deleteById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture apiKey = apiKeysService.delete(apiKeyId.get()) + CompletableFuture apiKey = apiKeysService.delete(apiKeyId.get(), Domain.fromContext(context)) .thenCompose(opt -> AsyncUtils.fromOptional(opt, ErrorCode.API_KEY_DOES_NOT_EXIST, "API key does not exist")) .thenApply(restMapper::toDTO); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApplicationsRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApplicationsRoute.java index 29623ae6..23b12e7f 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApplicationsRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApplicationsRoute.java @@ -3,19 +3,19 @@ import com.google.inject.Inject; import com.nexblocks.authguard.api.dto.entities.ApiKeyDTO; import com.nexblocks.authguard.api.dto.entities.AppDTO; -import com.nexblocks.authguard.api.dto.entities.Error; import com.nexblocks.authguard.api.dto.requests.CreateAppRequestDTO; import com.nexblocks.authguard.api.dto.validation.violations.Violation; import com.nexblocks.authguard.api.dto.validation.violations.ViolationType; import com.nexblocks.authguard.api.routes.ApplicationsApi; +import com.nexblocks.authguard.rest.access.EntityDomainChecker; import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestJsonMapper; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.rest.util.IdempotencyHeader; import com.nexblocks.authguard.service.ApiKeysService; import com.nexblocks.authguard.service.ApplicationsService; -import com.nexblocks.authguard.service.model.AppBO; import com.nexblocks.authguard.service.model.RequestContextBO; import com.nexblocks.authguard.service.util.AsyncUtils; import io.javalin.core.validation.Validator; @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -68,7 +67,7 @@ public void getById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture application = applicationsService.getById(applicationId.get()) + CompletableFuture application = applicationsService.getById(applicationId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAppOptional) .thenApply(restMapper::toDTO); @@ -82,7 +81,7 @@ public void getByExternalId(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture application = applicationsService.getByExternalId(applicationId.get()) + CompletableFuture application = applicationsService.getByExternalId(applicationId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAppOptional) .thenApply(restMapper::toDTO); @@ -92,7 +91,9 @@ public void getByExternalId(final Context context) { public void update(final Context context) { AppDTO app = RestJsonMapper.asClass(context.body(), AppDTO.class); - CompletableFuture application = applicationsService.update(restMapper.toBO(app)) + EntityDomainChecker.checkEntityDomainOrFail(app, context); + + CompletableFuture application = applicationsService.update(restMapper.toBO(app), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAppOptional) .thenApply(restMapper::toDTO); @@ -106,7 +107,7 @@ public void deleteById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture application = applicationsService.delete(applicationId.get()) + CompletableFuture application = applicationsService.delete(applicationId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromAppOptional) .thenApply(restMapper::toDTO); @@ -120,7 +121,7 @@ public void activate(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture application = applicationsService.activate(applicationId.get()) + CompletableFuture application = applicationsService.activate(applicationId.get(), Domain.fromContext(context)) .thenApply(restMapper::toDTO); context.json(application); @@ -133,7 +134,7 @@ public void deactivate(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture application = applicationsService.deactivate(applicationId.get()) + CompletableFuture application = applicationsService.deactivate(applicationId.get(), Domain.fromContext(context)) .thenApply(restMapper::toDTO); context.json(application); @@ -147,7 +148,7 @@ public void getApiKeys(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture> keys = apiKeysService.getByAppId(applicationId.get()) + CompletableFuture> keys = apiKeysService.getByAppId(applicationId.get(), Domain.fromContext(context)) .thenApply(list -> list .stream() .map(restMapper::toDTO) diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ClientsRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ClientsRoute.java index b7f35cfb..036aba9e 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ClientsRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ClientsRoute.java @@ -10,6 +10,7 @@ import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.rest.util.IdempotencyHeader; import com.nexblocks.authguard.service.ApiKeysService; import com.nexblocks.authguard.service.ClientsService; @@ -66,7 +67,7 @@ public void getById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture client = clientsService.getById(clientId.get()) + CompletableFuture client = clientsService.getById(clientId.get(), Domain.fromContext(context)) .thenApply(opt -> opt.map(restMapper::toDTO) .orElseThrow(() -> new ServiceNotFoundException(ErrorCode.APP_DOES_NOT_EXIST, "Client does not exist"))); @@ -80,7 +81,7 @@ public void getByExternalId(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture client = clientsService.getById(clientId.get()) + CompletableFuture client = clientsService.getById(clientId.get(), Domain.fromContext(context)) .thenApply(opt -> opt.map(restMapper::toDTO) .orElseThrow(() -> new ServiceNotFoundException(ErrorCode.APP_DOES_NOT_EXIST, "Client does not exist"))); @@ -94,7 +95,7 @@ public void deleteById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture client = clientsService.delete(clientId.get()) + CompletableFuture client = clientsService.delete(clientId.get(), Domain.fromContext(context)) .thenCompose(AsyncUtils::fromClientOptional) .thenApply(restMapper::toDTO); @@ -108,7 +109,7 @@ public void activate(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture client = clientsService.activate(clientId.get()) + CompletableFuture client = clientsService.activate(clientId.get(), Domain.fromContext(context)) .thenApply(restMapper::toDTO); context.json(client); @@ -121,7 +122,7 @@ public void deactivate(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture client = clientsService.deactivate(clientId.get()) + CompletableFuture client = clientsService.deactivate(clientId.get(), Domain.fromContext(context)) .thenApply(restMapper::toDTO); context.json(client); @@ -135,7 +136,7 @@ public void getApiKeys(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture> keys = apiKeysService.getByAppId(clientId.get()) + CompletableFuture> keys = apiKeysService.getByAppId(clientId.get(), Domain.fromContext(context)) .thenApply(list -> list .stream() .map(restMapper::toDTO) diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/CredentialsRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/CredentialsRoute.java index 912fba95..d943c275 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/CredentialsRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/CredentialsRoute.java @@ -16,6 +16,7 @@ import com.nexblocks.authguard.rest.mappers.RestJsonMapper; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.service.AccountCredentialsService; import com.nexblocks.authguard.service.model.AccountBO; import com.nexblocks.authguard.service.model.Client; @@ -58,7 +59,8 @@ public void updatePassword(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture result = credentialsService.updatePassword(credentialsId.get(), credentials.getPlainPassword()) + CompletableFuture result = credentialsService.updatePassword(credentialsId.get(), + credentials.getPlainPassword(), Domain.fromContext(context)) .thenApply(restMapper::toDTO); context.json(result); @@ -86,9 +88,10 @@ public void addIdentifiers(final Context context) { CompletableFuture result; if (request.getOldIdentifier() != null) { - result = credentialsService.replaceIdentifier(credentialsId.get(), request.getOldIdentifier(), identifiers.get(0)); + result = credentialsService.replaceIdentifier(credentialsId.get(), request.getOldIdentifier(), + identifiers.get(0), Domain.fromContext(context)); } else { - result = credentialsService.addIdentifiers(credentialsId.get(), identifiers); + result = credentialsService.addIdentifiers(credentialsId.get(), identifiers, Domain.fromContext(context)); } context.json(result.thenApply(restMapper::toDTO)); @@ -106,7 +109,8 @@ public void removeIdentifiers(final Context context) { .map(UserIdentifierDTO::getIdentifier) .collect(Collectors.toList()); - CompletableFuture result = credentialsService.removeIdentifiers(credentialsId.get(), identifiers) + CompletableFuture result = credentialsService.removeIdentifiers(credentialsId.get(), identifiers, + Domain.fromContext(context)) .thenApply(restMapper::toDTO); context.json(result); @@ -142,7 +146,8 @@ public void resetPassword(final Context context) { CompletableFuture result; if (request.isByToken()) { - result = credentialsService.resetPasswordByToken(request.getResetToken(), request.getNewPassword()); + result = credentialsService.resetPasswordByToken(request.getResetToken(), request.getNewPassword(), + Domain.fromContext(context)); } else { result = credentialsService.replacePassword(request.getIdentifier(), request.getOldPassword(), request.getNewPassword(), request.getDomain()); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/PermissionsRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/PermissionsRoute.java index 980f189e..8ae946ef 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/PermissionsRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/PermissionsRoute.java @@ -9,6 +9,7 @@ import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.service.PermissionsService; import com.nexblocks.authguard.service.exceptions.codes.ErrorCode; import com.nexblocks.authguard.service.util.AsyncUtils; @@ -51,7 +52,7 @@ public void getById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture permission = permissionsService.getById(id.get()) + CompletableFuture permission = permissionsService.getById(id.get(), Domain.fromContext(context)) .thenCompose(opt -> AsyncUtils.fromOptional(opt, ErrorCode.PERMISSION_DOES_NOT_EXIST, "Permission does not exist")) .thenApply(restMapper::toDTO); @@ -66,7 +67,7 @@ public void deleteById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture permission = permissionsService.delete(id.get()) + CompletableFuture permission = permissionsService.delete(id.get(), Domain.fromContext(context)) .thenCompose(opt -> AsyncUtils.fromOptional(opt, ErrorCode.PERMISSION_DOES_NOT_EXIST, "Permission does not exist")) .thenApply(restMapper::toDTO); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/RolesRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/RolesRoute.java index 95a9c584..ea1a5780 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/RolesRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/RolesRoute.java @@ -9,6 +9,7 @@ import com.nexblocks.authguard.rest.exceptions.RequestValidationException; import com.nexblocks.authguard.rest.mappers.RestMapper; import com.nexblocks.authguard.rest.util.BodyHandler; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.service.RolesService; import com.nexblocks.authguard.service.exceptions.codes.ErrorCode; import com.nexblocks.authguard.service.util.AsyncUtils; @@ -52,7 +53,7 @@ public void getById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture role = rolesService.getById(id.get()) + CompletableFuture role = rolesService.getById(id.get(), Domain.fromContext(context)) .thenCompose(opt -> AsyncUtils.fromOptional(opt, ErrorCode.ROLE_DOES_NOT_EXIST, "Role does not exist")) .thenApply(restMapper::toDTO); @@ -67,7 +68,7 @@ public void deleteById(final Context context) { throw new RequestValidationException(Collections.singletonList(new Violation("id", ViolationType.INVALID_VALUE))); } - CompletableFuture role = rolesService.delete(id.get()) + CompletableFuture role = rolesService.delete(id.get(), Domain.fromContext(context)) .thenCompose(opt -> AsyncUtils.fromOptional(opt, ErrorCode.ROLE_DOES_NOT_EXIST, "Role does not exist")) .thenApply(restMapper::toDTO); diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/VerificationRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/VerificationRoute.java index bf2d6024..5cb459e6 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/VerificationRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/VerificationRoute.java @@ -5,6 +5,7 @@ import com.nexblocks.authguard.api.dto.validation.violations.Violation; import com.nexblocks.authguard.api.dto.validation.violations.ViolationType; import com.nexblocks.authguard.api.routes.VerificationApi; +import com.nexblocks.authguard.rest.util.Domain; import com.nexblocks.authguard.service.VerificationService; import com.google.inject.Inject; import io.javalin.http.Context; @@ -29,7 +30,7 @@ public void verifyEmail(final Context context) { ))); } - verificationService.verifyEmail(token); + verificationService.verifyEmail(token, Domain.fromContext(context)); context.status(200); } diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/util/Domain.java b/rest/src/main/java/com/nexblocks/authguard/rest/util/Domain.java new file mode 100644 index 00000000..6be5c7d7 --- /dev/null +++ b/rest/src/main/java/com/nexblocks/authguard/rest/util/Domain.java @@ -0,0 +1,9 @@ +package com.nexblocks.authguard.rest.util; + +import io.javalin.http.Context; + +public class Domain { + public static String fromContext(final Context context) { + return context.pathParam("domain"); + } +} diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/AccountCredentialsService.java b/service-api/src/main/java/com/nexblocks/authguard/service/AccountCredentialsService.java index 7a8b7bdc..532611b0 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/AccountCredentialsService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/AccountCredentialsService.java @@ -8,12 +8,12 @@ import java.util.concurrent.CompletableFuture; public interface AccountCredentialsService { - CompletableFuture updatePassword(long id, String plainPassword); - CompletableFuture addIdentifiers(long id, List identifiers); - CompletableFuture removeIdentifiers(long id, List identifiers); - CompletableFuture replaceIdentifier(long id, String oldIdentifier, UserIdentifierBO newIdentifier); + CompletableFuture updatePassword(long id, String plainPassword, String domain); + CompletableFuture addIdentifiers(long id, List identifiers, String domain); + CompletableFuture removeIdentifiers(long id, List identifiers, String domain); + CompletableFuture replaceIdentifier(long id, String oldIdentifier, UserIdentifierBO newIdentifier, String domain); CompletableFuture generateResetToken(String identifier, boolean returnToken, String domain); - CompletableFuture resetPasswordByToken(String token, String plainPassword); + CompletableFuture resetPasswordByToken(String token, String plainPassword, String domain); CompletableFuture replacePassword(String identifier, String oldPassword, String newPassword, String domain); } diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/AccountsService.java b/service-api/src/main/java/com/nexblocks/authguard/service/AccountsService.java index 4d514201..6937a563 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/AccountsService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/AccountsService.java @@ -12,20 +12,24 @@ * AccountDO service interface. */ public interface AccountsService extends IdempotentCrudService { - CompletableFuture getByIdUnsafe(long id); + CompletableFuture getByIdUnsafe(long id, String domain); - CompletableFuture> getByExternalId(String externalId); + CompletableFuture> getByIdUnchecked(long id); + + CompletableFuture> getByExternalId(String externalId, String domain); + + CompletableFuture> getByExternalIdUnchecked(String externalId); CompletableFuture> getByEmail(String email, String domain); CompletableFuture> getByIdentifier(String identifier, String domain); CompletableFuture> getByIdentifierUnsafe(String identifier, String domain); - CompletableFuture> activate(long accountId); + CompletableFuture> activate(long accountId, String domain); - CompletableFuture> deactivate(long accountId); + CompletableFuture> deactivate(long accountId, String domain); - CompletableFuture> patch(long accountId, AccountBO account); + CompletableFuture> patch(long accountId, AccountBO account, String domain); /** * Grant permissions to an account. This should only updatePatch @@ -33,7 +37,7 @@ public interface AccountsService extends IdempotentCrudService { * * @throws ServiceNotFoundException if no account was found. */ - CompletableFuture> grantPermissions(long accountId, List permissions); + CompletableFuture> grantPermissions(long accountId, List permissions, String domain); /** * Revoke permissions of an account. This should only updatePatch @@ -41,7 +45,7 @@ public interface AccountsService extends IdempotentCrudService { * * @throws ServiceNotFoundException if no account was found. */ - CompletableFuture> revokePermissions(long accountId, List permissions); + CompletableFuture> revokePermissions(long accountId, List permissions, String domain); /** * Grant roles to an account. This should only updatePatch the roles @@ -49,7 +53,7 @@ public interface AccountsService extends IdempotentCrudService { * * @throws ServiceNotFoundException if no account was found. */ - CompletableFuture> grantRoles(long accountId, List roles); + CompletableFuture> grantRoles(long accountId, List roles, String domain); /** * Revoke roles of an account. This should only updatePatch the roles @@ -57,7 +61,7 @@ public interface AccountsService extends IdempotentCrudService { * * @throws ServiceNotFoundException if no account was found. */ - CompletableFuture> revokeRoles(long accountId, List roles); + CompletableFuture> revokeRoles(long accountId, List roles, String domain); /** * Finds a list of all admins. This is useful only when deciding diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/ActionTokenService.java b/service-api/src/main/java/com/nexblocks/authguard/service/ActionTokenService.java index 3bb3c48b..ddf6dbf0 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/ActionTokenService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/ActionTokenService.java @@ -7,8 +7,8 @@ import java.util.concurrent.CompletableFuture; public interface ActionTokenService { - CompletableFuture generateOtp(long accountId); + CompletableFuture generateOtp(long accountId, String domain); CompletableFuture generateFromBasicAuth(AuthRequestBO authRequest, String action); - CompletableFuture generateFromOtp(long passwordId, String otp, String action); + CompletableFuture generateFromOtp(long passwordId, String domain, String otp, String action); CompletableFuture verifyToken(String token, String action); } diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java b/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java index 6ef5e84b..8225fcc9 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java @@ -9,15 +9,15 @@ import java.util.concurrent.CompletableFuture; public interface ApiKeysService extends CrudService { - CompletableFuture generateApiKey(long appId, String type, Duration duration); - CompletableFuture generateClientApiKey(long clientId, String type, Duration duration); + CompletableFuture generateApiKey(long appId, String domain, String type, Duration duration); + CompletableFuture generateClientApiKey(long clientId, String domain, String type, Duration duration); CompletableFuture generateApiKey(AppBO app, String type, Duration duration); CompletableFuture generateClientApiKey(ClientBO client, String type, Duration duration); - CompletableFuture> getByAppId(long appId); + CompletableFuture> getByAppId(long appId, String domain); - CompletableFuture validateApiKey(String key, String type); + CompletableFuture validateApiKey(String key, String domain, String type); CompletableFuture validateClientApiKey(String key, String type); } diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/ApplicationsService.java b/service-api/src/main/java/com/nexblocks/authguard/service/ApplicationsService.java index 13511cde..599016e9 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/ApplicationsService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/ApplicationsService.java @@ -7,8 +7,8 @@ import java.util.concurrent.CompletableFuture; public interface ApplicationsService extends IdempotentCrudService { - CompletableFuture> getByExternalId(long externalId); - CompletableFuture activate(long id); - CompletableFuture deactivate(long id); - CompletableFuture> getByAccountId(long accountId); + CompletableFuture> getByExternalId(long externalId, String domain); + CompletableFuture activate(long id, String domain); + CompletableFuture deactivate(long id, String domain); + CompletableFuture> getByAccountId(long accountId, String domain); } diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/ClientsService.java b/service-api/src/main/java/com/nexblocks/authguard/service/ClientsService.java index f8b9bc2e..6527aa9c 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/ClientsService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/ClientsService.java @@ -7,8 +7,9 @@ import java.util.concurrent.CompletableFuture; public interface ClientsService extends IdempotentCrudService { - CompletableFuture> getByExternalId(String externalId); - CompletableFuture activate(long id); - CompletableFuture deactivate(long id); - CompletableFuture> getByAccountId(long accountId); + CompletableFuture> getByIdUnchecked(long id); + CompletableFuture> getByExternalId(String externalId, String domain); + CompletableFuture activate(long id, String domain); + CompletableFuture deactivate(long id, String domain); + CompletableFuture> getByAccountId(long accountId, String domain); } diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/CrudService.java b/service-api/src/main/java/com/nexblocks/authguard/service/CrudService.java index c4d51c5c..45fe288a 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/CrudService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/CrudService.java @@ -8,9 +8,9 @@ public interface CrudService { CompletableFuture create(T entity); - CompletableFuture> getById(long id); + CompletableFuture> getById(long id, String domain); - CompletableFuture> update(T entity); + CompletableFuture> update(T entity, String domain); - CompletableFuture> delete(long id); + CompletableFuture> delete(long id, String domain); } diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/VerificationService.java b/service-api/src/main/java/com/nexblocks/authguard/service/VerificationService.java index 467c27f9..bd9a18cd 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/VerificationService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/VerificationService.java @@ -3,8 +3,8 @@ import com.nexblocks.authguard.service.model.AuthResponseBO; public interface VerificationService { - void verifyEmail(String verificationToken); - AuthResponseBO sendPhoneNumberVerification(long accountId); + void verifyEmail(String verificationToken, String domain); + AuthResponseBO sendPhoneNumberVerification(long accountId, String domain); AuthResponseBO sendPhoneNumberVerificationByIdentifier(String identifier, String domain); - void verifyPhoneNumber(long passwordId, String otp, String phoneNumber); + void verifyPhoneNumber(long passwordId, String domain, String otp, String phoneNumber); } diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImpl.java index a51c994c..4896b4aa 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImpl.java @@ -70,15 +70,15 @@ public AccountCredentialsServiceImpl(final AccountsService accountsService, } @Override - public CompletableFuture updatePassword(final long id, final String plainPassword) { - return accountsService.getByIdUnsafe(id) + public CompletableFuture updatePassword(final long id, final String plainPassword, final String domain) { + return accountsService.getByIdUnsafe(id, domain) .thenCompose(existing -> { HashedPasswordBO newPassword = credentialsManager.verifyAndHashPassword(plainPassword); AccountBO update = existing .withHashedPassword(newPassword) .withPasswordUpdatedAt(Instant.now()); - return doUpdate(existing, update) + return doUpdate(existing, update, domain) .thenApply(result -> { storePasswordUpdateRecord(existing); @@ -90,8 +90,9 @@ public CompletableFuture updatePassword(final long id, final String p } @Override - public CompletableFuture addIdentifiers(final long id, final List identifiers) { - return accountsService.getByIdUnsafe(id) + public CompletableFuture addIdentifiers(final long id, final List identifiers, + final String domain) { + return accountsService.getByIdUnsafe(id, domain) .thenCompose(existing -> { LOG.info("Add identifiers request. accountId={}, domain={}", id, existing.getDomain()); @@ -116,13 +117,13 @@ public CompletableFuture addIdentifiers(final long id, final List removeIdentifiers(final long id, final List identifiers) { - return accountsService.getByIdUnsafe(id) + public CompletableFuture removeIdentifiers(final long id, final List identifiers, final String domain) { + return accountsService.getByIdUnsafe(id, domain) .thenCompose(existing -> { LOG.info("Remove identifiers request. accountId={}, domain={}", id, existing.getDomain()); @@ -140,7 +141,7 @@ public CompletableFuture removeIdentifiers(final long id, final List< .identifiers(updatedIdentifiers) .build(); - return doUpdate(existing, updated) + return doUpdate(existing, updated, domain) .thenApply(result -> { updatedIdentifiers.forEach(oldIdentifier -> storeIdentifierUpdateRecord(existing, oldIdentifier, CredentialsAudit.Action.DEACTIVATED)); @@ -151,8 +152,9 @@ public CompletableFuture removeIdentifiers(final long id, final List< } @Override - public CompletableFuture replaceIdentifier(final long id, final String oldIdentifier, final UserIdentifierBO newIdentifier) { - return accountsService.getByIdUnsafe(id) + public CompletableFuture replaceIdentifier(final long id, final String oldIdentifier, + final UserIdentifierBO newIdentifier, final String domain) { + return accountsService.getByIdUnsafe(id, domain) .thenCompose(existing -> { LOG.info("Replace identifiers request. accountId={}, domain={}", id, existing.getDomain()); @@ -183,7 +185,7 @@ public CompletableFuture replaceIdentifier(final long id, final Strin final AccountBO update = existing.withIdentifiers(newIdentifiers); - return doUpdate(existing, update) + return doUpdate(existing, update, domain) .thenApply(result -> { storeIdentifierUpdateRecord(existing, matchedIdentifier.get(), CredentialsAudit.Action.UPDATED); @@ -193,7 +195,8 @@ public CompletableFuture replaceIdentifier(final long id, final Strin } @Override - public CompletableFuture generateResetToken(final String identifier, final boolean returnToken, final String domain) { + public CompletableFuture generateResetToken(final String identifier, final boolean returnToken, + final String domain) { return accountsService.getByIdentifier(identifier, domain) .thenCompose(AsyncUtils::fromAccountOptional) .thenCompose(account -> { @@ -228,7 +231,7 @@ public CompletableFuture generateResetToken(final String i } @Override - public CompletableFuture resetPasswordByToken(final String token, final String plainPassword) { + public CompletableFuture resetPasswordByToken(final String token, final String plainPassword, final String domain) { return accountTokensRepository.getByToken(token) .thenCompose(opt -> { AccountTokenDO accountToken = opt.orElseThrow(() -> new ServiceNotFoundException(ErrorCode.TOKEN_EXPIRED_OR_DOES_NOT_EXIST, @@ -240,7 +243,7 @@ public CompletableFuture resetPasswordByToken(final String token, fin throw new ServiceException(ErrorCode.EXPIRED_TOKEN, "Token " + token + " has expired"); } - return updatePassword(accountToken.getAssociatedAccountId(), plainPassword); + return updatePassword(accountToken.getAssociatedAccountId(), plainPassword, domain); }); } @@ -264,7 +267,7 @@ public CompletableFuture replacePassword(final String identifier, .withHashedPassword(newHashedPassword) .withPasswordUpdatedAt(Instant.now()); - return doUpdate(credentials, update) + return doUpdate(credentials, update, domain) .thenApply(result -> { storePasswordUpdateRecord(credentials); @@ -273,11 +276,11 @@ public CompletableFuture replacePassword(final String identifier, }); } - private CompletableFuture doUpdate(final AccountBO existing, final AccountBO updated) { + private CompletableFuture doUpdate(final AccountBO existing, final AccountBO updated, final String domain) { LOG.info("Account credentials update. accountId={}, domain={}", existing.getId(), existing.getDomain()); storeAuditAttempt(existing); - return accountsService.update(updated) + return accountsService.update(updated, domain) .thenApply(persisted -> persisted.map(c -> { messageBus.publish(CREDENTIALS_CHANNEL, Messages.updated(c)); diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/AccountsServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/AccountsServiceImpl.java index 00cf3c0f..aaa93528 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/AccountsServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/AccountsServiceImpl.java @@ -113,22 +113,41 @@ private CompletableFuture doCreate(final AccountBO account) { } @Override - public CompletableFuture> getById(final long accountId) { + public CompletableFuture> getById(final long accountId, final String domain) { return persistenceService.getById(accountId) - .thenApply(opt -> opt.map(credentialsManager::removeSensitiveInformation)); + .thenApply(opt -> opt + .filter(account -> Objects.equals(account.getDomain(), domain)) + .map(credentialsManager::removeSensitiveInformation)); } @Override - public CompletableFuture getByIdUnsafe(final long id) { + public CompletableFuture getByIdUnsafe(final long id, final String domain) { return persistenceService.getById(id) - .thenCompose(opt -> opt.map(CompletableFuture::completedFuture) + .thenCompose(opt -> opt + .filter(account -> Objects.equals(account.getDomain(), domain)) + .map(CompletableFuture::completedFuture) .orElseGet(() -> CompletableFuture.failedFuture(new ServiceNotFoundException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, "Account does not exist")))); } @Override - public CompletableFuture> getByExternalId(final String externalId) { + public CompletableFuture> getByIdUnchecked(final long id) { + return persistenceService.getById(id); + } + + @Override + public CompletableFuture> getByExternalId(final String externalId, String domain) { + return accountsRepository.getByExternalId(externalId) + .thenApply(opt -> opt + .filter(account -> Objects.equals(account.getDomain(), domain)) + .map(serviceMapper::toBO) + .map(credentialsManager::removeSensitiveInformation)); + } + + @Override + public CompletableFuture> getByExternalIdUnchecked(String externalId) { return accountsRepository.getByExternalId(externalId) - .thenApply(opt -> opt.map(serviceMapper::toBO) + .thenApply(opt -> opt + .map(serviceMapper::toBO) .map(credentialsManager::removeSensitiveInformation)); } @@ -155,27 +174,27 @@ public CompletableFuture> getByIdentifierUnsafe(final String } @Override - public CompletableFuture> update(final AccountBO account) { + public CompletableFuture> update(final AccountBO account, String domain) { LOG.info("Account update request. accountId={}, domain={}", account.getId(), account.getDomain()); return persistenceService.update(account); } @Override - public CompletableFuture> delete(final long accountId) { + public CompletableFuture> delete(final long accountId, String domain) { LOG.info("Account delete request. accountId={}", accountId); return persistenceService.delete(accountId); } @Override - public CompletableFuture> activate(final long accountId) { - return getByIdUnsafe(accountId) + public CompletableFuture> activate(final long accountId, String domain) { + return getByIdUnsafe(accountId, domain) .thenCompose(account -> { AccountBO activated = account.withActive(true); LOG.info("Activate account request. accountId={}, domain={}", activated.getId(), activated.getDomain()); - return this.update(activated); + return this.update(activated, account.getDomain()); }).thenApply(persisted -> { if (persisted.isPresent()) { LOG.info("Account activated. accountId={}, domain={}", accountId, persisted.get().getDomain()); @@ -188,13 +207,13 @@ public CompletableFuture> activate(final long accountId) { } @Override - public CompletableFuture> deactivate(final long accountId) { - return getByIdUnsafe(accountId) + public CompletableFuture> deactivate(final long accountId, String domain) { + return getByIdUnsafe(accountId, domain) .thenCompose(account -> { AccountBO deactivated = account.withActive(false); LOG.info("Deactivate account request. accountId={}, domain={}", deactivated.getId(), deactivated.getDomain()); - return this.update(deactivated); + return this.update(deactivated, account.getDomain()); }).thenApply(persisted -> { if (persisted.isPresent()) { LOG.info("Account deactivated. accountId={}, domain={}", persisted.get().getId(), persisted.get().getDomain()); @@ -207,8 +226,8 @@ public CompletableFuture> deactivate(final long accountId) { } @Override - public CompletableFuture> patch(final long accountId, final AccountBO account) { - return getByIdUnsafe(accountId) + public CompletableFuture> patch(final long accountId, final AccountBO account, String domain) { + return getByIdUnsafe(accountId, domain) .thenCompose(existing -> { AccountBO merged = AccountUpdateMerger.merge(existing, account); @@ -244,7 +263,7 @@ public CompletableFuture> patch(final long accountId, final } AccountBO accountUpdate = merged; - return update(accountUpdate) + return update(accountUpdate, existing.getDomain()) .thenApply(updated -> { updated.ifPresent(updatedAccount -> { // we could merge both email and backup email messages, but we kept them separate for now @@ -278,8 +297,8 @@ public CompletableFuture> patch(final long accountId, final } @Override - public CompletableFuture> grantPermissions(final long accountId, final List permissions) { - return getByIdUnsafe(accountId) + public CompletableFuture> grantPermissions(final long accountId, final List permissions, String domain) { + return getByIdUnsafe(accountId, domain) .thenCompose(account -> { List verifiedPermissions = permissionsService.validate(permissions, account.getDomain()); @@ -311,8 +330,8 @@ public CompletableFuture> grantPermissions(final long accoun } @Override - public CompletableFuture> revokePermissions(final long accountId, final List permissions) { - return getByIdUnsafe(accountId) + public CompletableFuture> revokePermissions(final long accountId, final List permissions, String domain) { + return getByIdUnsafe(accountId, domain) .thenCompose(account -> { Set permissionsFullNames = permissions.stream() .map(Permission::getFullName) @@ -338,8 +357,8 @@ public CompletableFuture> revokePermissions(final long accou } @Override - public CompletableFuture> grantRoles(final long accountId, final List roles) { - return getByIdUnsafe(accountId) + public CompletableFuture> grantRoles(final long accountId, final List roles, String domain) { + return getByIdUnsafe(accountId, domain) .thenCompose(account -> { verifyRolesOrFail(roles, account.getDomain()); @@ -363,8 +382,8 @@ public CompletableFuture> grantRoles(final long accountId, f } @Override - public CompletableFuture> revokeRoles(final long accountId, final List roles) { - return getByIdUnsafe(accountId) + public CompletableFuture> revokeRoles(final long accountId, final List roles, String domain) { + return getByIdUnsafe(accountId, domain) .thenCompose(account -> { LOG.info("Revoke account roles request. accountId={}, domain={}, permissions={}", account.getId(), account.getDomain(), roles); diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImpl.java index 2d395a96..42ea96e4 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImpl.java @@ -53,8 +53,8 @@ public ActionTokenServiceImpl(final AccountsService accountsService, final Basic } @Override - public CompletableFuture generateOtp(final long accountId) { - return accountsService.getById(accountId) + public CompletableFuture generateOtp(final long accountId, final String domain) { + return accountsService.getById(accountId, domain) .thenCompose(AsyncUtils::fromAccountOptional) .thenCompose(account -> { LOG.info("Generate OTP for action token request. accountId={}, domain={}", account.getId(), account.getDomain()); @@ -81,11 +81,11 @@ public CompletableFuture generateFromBasicAuth(final AuthRequestB } @Override - public CompletableFuture generateFromOtp(final long passwordId, final String otp, final String action) { + public CompletableFuture generateFromOtp(final long passwordId, String domain, final String otp, final String action) { String otpToken = passwordId + ":" + otp; return otpVerifier.verifyAccountTokenAsync(otpToken) - .thenCompose(accountsService::getById) + .thenCompose(id -> accountsService.getById(id, domain)) .thenCompose(result -> { if (result.isEmpty()) { LOG.warn("Verified OTP request but the account doesn't exist. passwordId={}", passwordId); diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java index a8452086..f0343471 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java @@ -8,7 +8,6 @@ import com.nexblocks.authguard.service.ApplicationsService; import com.nexblocks.authguard.service.ClientsService; import com.nexblocks.authguard.service.exceptions.ServiceException; -import com.nexblocks.authguard.service.exceptions.ServiceNotFoundException; import com.nexblocks.authguard.service.exceptions.codes.ErrorCode; import com.nexblocks.authguard.service.exchange.ApiKeyExchange; import com.nexblocks.authguard.service.exchange.KeyExchange; @@ -70,32 +69,34 @@ public CompletableFuture create(final ApiKeyBO apiKey) { } @Override - public CompletableFuture> getById(final long apiKeyId) { + public CompletableFuture> getById(final long apiKeyId, final String domain) { return persistenceService.getById(apiKeyId); } @Override - public CompletableFuture> update(final ApiKeyBO entity) { + public CompletableFuture> update(final ApiKeyBO entity, final String domain) { throw new UnsupportedOperationException("API keys cannot be updated"); } @Override - public CompletableFuture> delete(final long id) { + public CompletableFuture> delete(final long id, final String domain) { LOG.info("API key delete request. accountId={}", id); - return persistenceService.delete(id); + return getById(id, domain).thenCompose(ignored -> persistenceService.delete(id)); } @Override - public CompletableFuture generateApiKey(final long appId, final String type, final Duration duration) { - return applicationsService.getById(appId) + public CompletableFuture generateApiKey(final long appId, final String domain, final String type, + final Duration duration) { + return applicationsService.getById(appId, domain) .thenCompose(AsyncUtils::fromAppOptional) .thenCompose(app -> generateApiKey(app, type, duration)); } @Override - public CompletableFuture generateClientApiKey(final long clientId, final String type, final Duration duration) { - return clientsService.getById(clientId) + public CompletableFuture generateClientApiKey(final long clientId, final String domain, final String type, + final Duration duration) { + return clientsService.getById(clientId, domain) .thenCompose(AsyncUtils::fromClientOptional) .thenCompose(client -> generateClientApiKey(client, type, duration)); } @@ -145,7 +146,7 @@ public CompletableFuture generateClientApiKey(ClientBO client, String } @Override - public CompletableFuture> getByAppId(final long appId) { + public CompletableFuture> getByAppId(final long appId, final String domain) { return apiKeysRepository.getByAppId(appId) .thenApply(list -> list.stream() .map(serviceMapper::toBO) @@ -153,7 +154,7 @@ public CompletableFuture> getByAppId(final long appId) { } @Override - public CompletableFuture validateApiKey(final String key, final String type) { + public CompletableFuture validateApiKey(final String key, final String domain, final String type) { ApiKeyExchange apiKeyExchange = getExchangeOrFail(type); return apiKeyExchange.verifyAndGetAppId(key) @@ -162,14 +163,13 @@ public CompletableFuture validateApiKey(final String key, final String ty return CompletableFuture.failedFuture(new ServiceException(ErrorCode.INVALID_TOKEN, "Token is invalid or expired")); } - return applicationsService.getById(optional.get()); + return applicationsService.getById(optional.get(), domain); }) .thenCompose(AsyncUtils::fromAppOptional); - } @Override - public CompletableFuture validateClientApiKey(String key, String type) { + public CompletableFuture validateClientApiKey(final String key, final String type) { ApiKeyExchange apiKeyExchange = getExchangeOrFail(type); return apiKeyExchange.verifyAndGetClientId(key) @@ -178,7 +178,7 @@ public CompletableFuture validateClientApiKey(String key, String type) return CompletableFuture.failedFuture(new ServiceException(ErrorCode.INVALID_TOKEN, "Token is invalid or expired")); } - return clientsService.getById(optional.get()); + return clientsService.getByIdUnchecked(optional.get()); }) .thenCompose(AsyncUtils::fromClientOptional); } diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImpl.java index fc9e949f..fb54ddbc 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImpl.java @@ -1,23 +1,23 @@ package com.nexblocks.authguard.service.impl; +import com.google.inject.Inject; import com.nexblocks.authguard.dal.model.AppDO; import com.nexblocks.authguard.dal.persistence.ApplicationsRepository; import com.nexblocks.authguard.emb.MessageBus; import com.nexblocks.authguard.service.AccountsService; import com.nexblocks.authguard.service.ApplicationsService; import com.nexblocks.authguard.service.IdempotencyService; -import com.nexblocks.authguard.service.exceptions.ServiceException; import com.nexblocks.authguard.service.exceptions.ServiceNotFoundException; import com.nexblocks.authguard.service.exceptions.codes.ErrorCode; import com.nexblocks.authguard.service.mappers.ServiceMapper; import com.nexblocks.authguard.service.model.AppBO; import com.nexblocks.authguard.service.model.RequestContextBO; -import com.google.inject.Inject; import com.nexblocks.authguard.service.util.AsyncUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -61,7 +61,7 @@ private CompletableFuture doCreate(final AppBO app) { * account exists if it's set. */ if (app.getParentAccountId() != null) { - return accountsService.getById(app.getParentAccountId()) + return accountsService.getById(app.getParentAccountId(), app.getDomain()) .thenCompose(opt -> { if (opt.isEmpty()) { throw new ServiceNotFoundException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, "No account with ID " + app.getParentAccountId() + " exists"); @@ -75,40 +75,43 @@ private CompletableFuture doCreate(final AppBO app) { } @Override - public CompletableFuture> getById(final long id) { - return persistenceService.getById(id); + public CompletableFuture> getById(final long id, String domain) { + return persistenceService.getById(id) + .thenApply(opt -> opt.filter(app -> Objects.equals(app.getDomain(), domain))); } @Override - public CompletableFuture> getByExternalId(final long externalId) { + public CompletableFuture> getByExternalId(final long externalId, String domain) { return applicationsRepository.getById(externalId) - .thenApply(optional -> optional.map(serviceMapper::toBO)); + .thenApply(optional -> optional + .filter(app -> Objects.equals(app.getDomain(), domain)) + .map(serviceMapper::toBO)); } @Override - public CompletableFuture> update(final AppBO app) { + public CompletableFuture> update(final AppBO app, final String domain) { LOG.info("Application update request. accountId={}", app.getId()); // FIXME accountId cannot be updated - return persistenceService.update(app); + return getById(app.getId(), domain).thenCompose(ignored -> persistenceService.update(app)); } @Override - public CompletableFuture> delete(final long id) { + public CompletableFuture> delete(final long id, final String domain) { LOG.info("Application delete request. accountId={}", id); return persistenceService.delete(id); } @Override - public CompletableFuture activate(final long id) { - return getById(id) + public CompletableFuture activate(final long id, final String domain) { + return getById(id, domain) .thenCompose(AsyncUtils::fromAppOptional) .thenCompose(app -> { LOG.info("Activate application request. appId={}, domain={}", app.getId(), app.getDomain()); AppBO activated = app.withActive(true); - return update(activated) + return update(activated, domain) .thenApply(persisted -> { if (persisted.isPresent()) { LOG.info("Application activated. appId={}, domain={}", app.getId(), app.getDomain()); @@ -122,14 +125,14 @@ public CompletableFuture activate(final long id) { } @Override - public CompletableFuture deactivate(final long id) { - return getById(id) + public CompletableFuture deactivate(final long id, final String domain) { + return getById(id, domain) .thenCompose(AsyncUtils::fromAppOptional) .thenCompose(app -> { LOG.info("Activate application request. appId={}, domain={}", app.getId(), app.getDomain()); AppBO deactivated = app.withActive(false); - return update(deactivated) + return update(deactivated, domain) .thenApply(persisted -> { if (persisted.isPresent()) { LOG.info("Application deactivated. appId={}, domain={}", app.getId(), app.getDomain()); @@ -143,7 +146,7 @@ public CompletableFuture deactivate(final long id) { } @Override - public CompletableFuture> getByAccountId(final long accountId) { + public CompletableFuture> getByAccountId(final long accountId, final String domain) { return applicationsRepository.getAllForAccount(accountId) .thenApply(list -> list.stream().map(serviceMapper::toBO).collect(Collectors.toList())); } diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/ClientsServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/ClientsServiceImpl.java index 72fc42d8..44def994 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/ClientsServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/ClientsServiceImpl.java @@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -56,7 +57,7 @@ public CompletableFuture create(final ClientBO client, final RequestCo private CompletableFuture doCreate(final ClientBO client) { if (client.getAccountId() != null) { - return accountsService.getById(client.getAccountId()) + return accountsService.getById(client.getAccountId(), client.getDomain()) .thenCompose(opt -> { if (opt.isEmpty()) { throw new ServiceNotFoundException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, @@ -71,18 +72,26 @@ private CompletableFuture doCreate(final ClientBO client) { } @Override - public CompletableFuture> getById(final long id) { + public CompletableFuture> getById(final long id, final String domain) { + return persistenceService.getById(id) + .thenApply(opt -> opt.filter(client -> Objects.equals(client.getDomain(), domain))); + } + + @Override + public CompletableFuture> getByIdUnchecked(final long id) { return persistenceService.getById(id); } @Override - public CompletableFuture> getByExternalId(final String externalId) { + public CompletableFuture> getByExternalId(final String externalId, final String domain) { return clientsRepository.getByExternalId(externalId) - .thenApply(optional -> optional.map(serviceMapper::toBO)); + .thenApply(optional -> optional + .filter(client -> Objects.equals(client.getDomain(), domain)) + .map(serviceMapper::toBO)); } @Override - public CompletableFuture> update(final ClientBO client) { + public CompletableFuture> update(final ClientBO client, final String domain) { LOG.info("Client update request. accountId={}", client.getId()); // FIXME accountId cannot be updated @@ -90,22 +99,22 @@ public CompletableFuture> update(final ClientBO client) { } @Override - public CompletableFuture> delete(final long id) { + public CompletableFuture> delete(final long id, String domain) { LOG.info("Client delete request. accountId={}", id); return persistenceService.delete(id); } @Override - public CompletableFuture activate(final long id) { - return getById(id) + public CompletableFuture activate(final long id, final String domain) { + return getById(id, domain) .thenCompose(AsyncUtils::fromClientOptional) .thenCompose(client -> { LOG.info("Activate client request. clientId={}, domain={}", client.getId(), client.getDomain()); ClientBO activated = client.withActive(true); - return update(activated) + return update(activated, domain) .thenApply(persisted -> { if (persisted.isPresent()) { LOG.info("Client activated. clientId={}, domain={}", client.getId(), client.getDomain()); @@ -119,15 +128,15 @@ public CompletableFuture activate(final long id) { } @Override - public CompletableFuture deactivate(final long id) { - return getById(id) + public CompletableFuture deactivate(final long id, final String domain) { + return getById(id, domain) .thenCompose(AsyncUtils::fromClientOptional) .thenCompose(client -> { LOG.info("Deactivate client request. clientId={}, domain={}", client.getId(), client.getDomain()); ClientBO deactivated = client.withActive(false); - return update(deactivated) + return update(deactivated, domain) .thenApply(persisted -> { if (persisted.isPresent()) { LOG.info("Client deactivated. clientId={}, domain={}", client.getId(), client.getDomain()); @@ -141,7 +150,7 @@ public CompletableFuture deactivate(final long id) { } @Override - public CompletableFuture> getByAccountId(final long accountId) { + public CompletableFuture> getByAccountId(final long accountId, final String domain) { return clientsRepository.getAllForAccount(accountId) .thenApply(list -> list.stream().map(serviceMapper::toBO).collect(Collectors.toList())); } diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/ExchangeAttemptsServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/ExchangeAttemptsServiceImpl.java index 214a9058..762bb6c4 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/ExchangeAttemptsServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/ExchangeAttemptsServiceImpl.java @@ -39,17 +39,17 @@ public CompletableFuture create(final ExchangeAttemptBO entit } @Override - public CompletableFuture> getById(final long id) { + public CompletableFuture> getById(final long id, String domain) { return persistenceService.getById(id); } @Override - public CompletableFuture> update(final ExchangeAttemptBO entity) { + public CompletableFuture> update(final ExchangeAttemptBO entity, String domain) { throw new UnsupportedOperationException("Exchange attempts cannot be updated"); } @Override - public CompletableFuture> delete(final long id) { + public CompletableFuture> delete(final long id, String domain) { throw new UnsupportedOperationException("Exchange attempts cannot be deleted"); } diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/PermissionsServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/PermissionsServiceImpl.java index 558d3fc6..fc500f72 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/PermissionsServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/PermissionsServiceImpl.java @@ -50,12 +50,12 @@ public CompletableFuture create(final PermissionBO permission) { } @Override - public CompletableFuture> getById(final long id) { + public CompletableFuture> getById(final long id, String domain) { return persistenceService.getById(id); } @Override - public CompletableFuture> update(final PermissionBO entity) { + public CompletableFuture> update(final PermissionBO entity, String domain) { throw new UnsupportedOperationException("Permissions cannot be updated"); } @@ -87,7 +87,7 @@ public CompletableFuture> getAllForGroup(final String group, } @Override - public CompletableFuture> delete(final long id) { + public CompletableFuture> delete(final long id, String domain) { LOG.info("Request to delete permission. permissionId={}", id); return persistenceService.delete(id); diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/RolesServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/RolesServiceImpl.java index 9b5e9ec3..18146668 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/RolesServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/RolesServiceImpl.java @@ -64,17 +64,17 @@ public CompletableFuture create(final RoleBO role) { } @Override - public CompletableFuture> getById(final long id) { + public CompletableFuture> getById(final long id, String domain) { return persistenceService.getById(id); } @Override - public CompletableFuture> update(final RoleBO entity) { + public CompletableFuture> update(final RoleBO entity, String domain) { throw new UnsupportedOperationException("Roles cannot be updated"); } @Override - public CompletableFuture> delete(final long id) { + public CompletableFuture> delete(final long id, String domain) { LOG.info("Request to delete role. roleId={}", id); return persistenceService.delete(id); diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/VerificationServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/VerificationServiceImpl.java index daa537d1..79d132ee 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/VerificationServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/VerificationServiceImpl.java @@ -48,7 +48,7 @@ public VerificationServiceImpl(final AccountTokensRepository accountTokensReposi } @Override - public void verifyEmail(final String verificationToken) { + public void verifyEmail(final String verificationToken, String domain) { final AccountTokenDO accountToken = accountTokensRepository.getByToken(verificationToken) .join() .orElseThrow(() -> new ServiceNotFoundException(ErrorCode.TOKEN_EXPIRED_OR_DOES_NOT_EXIST, @@ -64,15 +64,15 @@ public void verifyEmail(final String verificationToken) { LOG.info("Email verification request. tokenId={}, expiresAt={}, accountId={}", accountToken.getId(), accountToken.getExpiresAt(), accountToken.getAssociatedAccountId()); - final String verifiedEmail = Optional.ofNullable(accountToken.getAdditionalInformation()) + String verifiedEmail = Optional.ofNullable(accountToken.getAdditionalInformation()) .map(additional -> additional.get(TARGET_EMAIL_PROPERTY)) .orElseThrow(() -> new ServiceException(ErrorCode.INVALID_TOKEN, "Invalid account token: no valid additional information")); - final AccountBO account = accountsService.getById(accountToken.getAssociatedAccountId()).join() + AccountBO account = accountsService.getById(accountToken.getAssociatedAccountId(), domain).join() .orElseThrow(() -> new ServiceNotFoundException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, "Account " + accountToken.getAssociatedAccountId() + " does not exist")); - final AccountBO updated; + AccountBO updated; if (verifiedEmail.equals(account.getEmail().getEmail())) { updated = account.withEmail(account.getEmail().withVerified(true)); @@ -84,15 +84,15 @@ public void verifyEmail(final String verificationToken) { } try { - accountsService.update(updated); + accountsService.update(updated, domain); } catch (final Exception e) { LOG.error("Failed to update account after email verification", e); } } @Override - public AuthResponseBO sendPhoneNumberVerification(final long accountId) { - final AccountBO account = accountsService.getById(accountId).join() + public AuthResponseBO sendPhoneNumberVerification(final long accountId, String domain) { + AccountBO account = accountsService.getById(accountId, domain).join() .orElseThrow(() -> new ServiceNotFoundException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, "Account " + accountId + " does not exist")); @@ -101,7 +101,7 @@ public AuthResponseBO sendPhoneNumberVerification(final long accountId) { @Override public AuthResponseBO sendPhoneNumberVerificationByIdentifier(final String identifier, final String domain) { - final AccountBO account = accountsService.getByIdentifier(identifier, domain).join() + AccountBO account = accountsService.getByIdentifier(identifier, domain).join() .orElseThrow(() -> new ServiceNotFoundException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, "No account with that identifier exists")); @@ -109,11 +109,11 @@ public AuthResponseBO sendPhoneNumberVerificationByIdentifier(final String ident } @Override - public void verifyPhoneNumber(final long passwordId, final String otp, final String phoneNumber) { + public void verifyPhoneNumber(final long passwordId, final String domain, final String otp, final String phoneNumber) { String token = passwordId + ":" + otp; Long accountId = otpVerifier.verifyAccountTokenAsync(token).join(); - Optional account = accountsService.getById(accountId).join(); + Optional account = accountsService.getById(accountId, domain).join(); if (account.isEmpty()) { LOG.info("Phone number verification request for deleted account. passwordId={}, accountId={}", passwordId, accountId); @@ -138,7 +138,7 @@ public void verifyPhoneNumber(final long passwordId, final String otp, final Str .build()); try { - accountsService.update(updated); + accountsService.update(updated, domain); } catch (final Exception e) { LOG.error("Failed to update account after phone number verification", e); } diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImplTest.java index 84a687fa..4471d094 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/AccountCredentialsServiceImplTest.java @@ -99,10 +99,10 @@ void updatePassword() { AccountDO accountDO = serviceMapper.toDO(accountBO); - Mockito.when(accountsService.getByIdUnsafe(accountId)) + Mockito.when(accountsService.getByIdUnsafe(accountId, "main")) .thenReturn(CompletableFuture.completedFuture(accountBO)); - Mockito.when(accountsService.update(Mockito.any())) + Mockito.when(accountsService.update(Mockito.any(), Mockito.eq("main"))) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountBO.class)))); Mockito.when(accountAuditRepository.save(any())) @@ -113,7 +113,7 @@ void updatePassword() { .password("hashed_new_password") .build()); - AccountBO result = accountCredentialsService.updatePassword(accountId, newPassword).join(); + AccountBO result = accountCredentialsService.updatePassword(accountId, newPassword, "main").join(); assertThat(result).usingRecursiveComparison() .ignoringFields(SKIPPED_FIELDS) @@ -258,10 +258,10 @@ void resetPasswordByToken() { Mockito.when(accountTokensRepository.getByToken(resetToken)) .thenReturn(CompletableFuture.completedFuture(Optional.of(persistedToken))); - Mockito.when(accountsService.getByIdUnsafe(accountId)) + Mockito.when(accountsService.getByIdUnsafe(accountId, "main")) .thenReturn(CompletableFuture.completedFuture(accountBO)); - Mockito.when(accountsService.update(Mockito.any())) + Mockito.when(accountsService.update(Mockito.any(), Mockito.eq("main"))) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountBO.class)))); Mockito.when(accountAuditRepository.save(any())) @@ -273,7 +273,7 @@ void resetPasswordByToken() { .build()); // action - AccountBO result = accountCredentialsService.resetPasswordByToken(resetToken, newPassword).join(); + AccountBO result = accountCredentialsService.resetPasswordByToken(resetToken, newPassword, "main").join(); // verify assertThat(result).usingRecursiveComparison() @@ -291,7 +291,7 @@ void resetPasswordWrongToken() { Mockito.when(accountTokensRepository.getByToken(resetToken)) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - assertThatThrownBy(() -> accountCredentialsService.resetPasswordByToken(resetToken, newPassword).join()) + assertThatThrownBy(() -> accountCredentialsService.resetPasswordByToken(resetToken, newPassword, "main").join()) .hasCauseInstanceOf(ServiceNotFoundException.class); } @@ -310,7 +310,7 @@ void resetPasswordExpiredToken() { Mockito.when(accountTokensRepository.getByToken(resetToken)) .thenReturn(CompletableFuture.completedFuture(Optional.of(persistedToken))); - assertThatThrownBy(() -> accountCredentialsService.resetPasswordByToken(resetToken, newPassword).join()) + assertThatThrownBy(() -> accountCredentialsService.resetPasswordByToken(resetToken, newPassword, "main").join()) .hasCauseInstanceOf(ServiceException.class); } @@ -337,7 +337,7 @@ void replacePassword() { Mockito.when(accountsService.getByIdentifierUnsafe(identifier, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountBO))); - Mockito.when(accountsService.update(Mockito.any())) + Mockito.when(accountsService.update(Mockito.any(), Mockito.eq("main"))) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountBO.class)))); Mockito.when(accountAuditRepository.save(any())) @@ -423,7 +423,7 @@ void replacePasswordInvalidPassword() { // mocks Mockito.when(accountsService.getByIdentifierUnsafe(identifier, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountBO))); - Mockito.when(accountsService.update(any())) + Mockito.when(accountsService.update(any(), Mockito.eq("main"))) .thenAnswer(invocation -> Optional.of(invocation.getArgument(0, AccountBO.class))); Mockito.when(accountAuditRepository.save(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(invocation.getArgument(0, CredentialsAuditDO.class))); @@ -455,9 +455,9 @@ void replaceIdentifier() { .passwordVersion(1) .build(); - Mockito.when(accountsService.getByIdUnsafe(accountId)) + Mockito.when(accountsService.getByIdUnsafe(accountId, "main")) .thenReturn(CompletableFuture.completedFuture(accountBO)); - Mockito.when(accountsService.update(Mockito.any())) + Mockito.when(accountsService.update(Mockito.any(), Mockito.eq("main"))) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountBO.class)))); UserIdentifierBO newIdentifier = UserIdentifierBO.builder() @@ -465,7 +465,7 @@ void replaceIdentifier() { .active(true) .build(); - AccountBO actual = accountCredentialsService.replaceIdentifier(accountId, "username", newIdentifier).join(); + AccountBO actual = accountCredentialsService.replaceIdentifier(accountId, "username", newIdentifier, "main").join(); AccountBO expected = AccountBO.builder() .id(accountId) @@ -484,10 +484,10 @@ void replaceIdentifier() { void replaceIdentifierNoCredentials() { long accountId = 1; - Mockito.when(accountsService.getByIdUnsafe(accountId)) + Mockito.when(accountsService.getByIdUnsafe(accountId, "main")) .thenReturn(CompletableFuture.failedFuture(new ServiceNotFoundException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, ""))); - assertThatThrownBy(() -> accountCredentialsService.replaceIdentifier(accountId, "username", null).join()) + assertThatThrownBy(() -> accountCredentialsService.replaceIdentifier(accountId, "username", null, "main").join()) .hasCauseInstanceOf(ServiceNotFoundException.class); } @@ -507,12 +507,12 @@ void replaceIdentifierNoIdentifier() { .passwordVersion(1) .build(); - Mockito.when(accountsService.getByIdUnsafe(accountId)) + Mockito.when(accountsService.getByIdUnsafe(accountId, "main")) .thenReturn(CompletableFuture.completedFuture(accountBO)); - Mockito.when(accountsService.update(any())) + Mockito.when(accountsService.update(any(), Mockito.eq("main"))) .thenAnswer(invocation -> Optional.of(invocation.getArgument(0, AccountBO.class))); - assertThatThrownBy(() -> accountCredentialsService.replaceIdentifier(accountId, "none", null).join()) + assertThatThrownBy(() -> accountCredentialsService.replaceIdentifier(accountId, "none", null, "main").join()) .hasCauseInstanceOf(ServiceException.class); } } \ No newline at end of file diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/AccountsServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/AccountsServiceImplTest.java index 31d9c7b1..351aa3fb 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/AccountsServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/AccountsServiceImplTest.java @@ -269,7 +269,7 @@ void getById() { Mockito.when(accountsRepository.getById(Mockito.anyLong())) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountDO))); - Optional retrieved = accountService.getById(0).join(); + Optional retrieved = accountService.getById(0, "main").join(); List expectedPermissions = accountBO.getPermissions().stream() .map(permission -> permission.withEntityType(null)) .collect(Collectors.toList()); @@ -311,7 +311,7 @@ void grantPermissions() { RANDOM.nextObject(PermissionBO.class).withEntityType(null) ); - Optional updated = accountService.grantPermissions(account.getId(), permissions).join(); + Optional updated = accountService.grantPermissions(account.getId(), permissions, "main").join(); assertThat(updated).isPresent(); assertThat(serviceMapper.toDO(updated.get())).isNotEqualTo(account); @@ -330,7 +330,7 @@ void grantPermissionsInvalidPermission() { RANDOM.nextObject(PermissionBO.class) ); - assertThatThrownBy(() -> accountService.grantPermissions(account.getId(), permissions).join()) + assertThatThrownBy(() -> accountService.grantPermissions(account.getId(), permissions, "main").join()) .hasCauseInstanceOf(ServiceException.class); } @@ -348,7 +348,7 @@ void grantPermissionsFromDifferentDomain() { RANDOM.nextObject(PermissionBO.class) ); - assertThatThrownBy(() -> accountService.grantPermissions(account.getId(), permissions).join()) + assertThatThrownBy(() -> accountService.grantPermissions(account.getId(), permissions, "main").join()) .hasCauseInstanceOf(ServiceException.class); } @@ -372,7 +372,7 @@ void revokePermissions() { currentPermissions.get(1) ); - Optional updated = accountService.revokePermissions(account.getId(), permissionsToRevoke).join(); + Optional updated = accountService.revokePermissions(account.getId(), permissionsToRevoke, "main").join(); assertThat(updated).isPresent(); assertThat(serviceMapper.toDO(updated.get())).isNotEqualTo(account); @@ -395,7 +395,7 @@ void grantRoles() { Mockito.when(rolesService.verifyRoles(roles, "main")).thenReturn(roles); - Optional updated = accountService.grantRoles(account.getId(), roles).join(); + Optional updated = accountService.grantRoles(account.getId(), roles, "main").join(); assertThat(updated).isPresent(); assertThat(serviceMapper.toDO(updated.get())).isNotEqualTo(account); @@ -420,7 +420,7 @@ void grantRolesInvalidRoles() { Mockito.when(rolesService.verifyRoles(roles, "main")).thenReturn(validRoles); - assertThatThrownBy(() -> accountService.grantRoles(account.getId(), roles).join()) + assertThatThrownBy(() -> accountService.grantRoles(account.getId(), roles, "main").join()) .hasCauseInstanceOf(ServiceException.class); } @@ -440,7 +440,7 @@ void grantRolesFromDifferentDomain() { Mockito.when(rolesService.verifyRoles(roles, "other")).thenReturn(roles); - assertThatThrownBy(() -> accountService.grantRoles(account.getId(), roles).join()) + assertThatThrownBy(() -> accountService.grantRoles(account.getId(), roles, "main").join()) .hasCauseInstanceOf(ServiceException.class); } @@ -459,7 +459,7 @@ void revokeRoles() { currentRoles.get(1) ); - Optional updated = accountService.revokeRoles(account.getId(), rolesToRevoke).join(); + Optional updated = accountService.revokeRoles(account.getId(), rolesToRevoke, "main").join(); assertThat(updated).isPresent(); assertThat(serviceMapper.toDO(updated.get())).isNotEqualTo(account); @@ -495,7 +495,7 @@ void patchNoIdentifiers() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - Optional updated = accountService.patch(accountDO.getId(), update).join(); + Optional updated = accountService.patch(accountDO.getId(), update, "main").join(); AccountBO expected = accountBO .withIdentifiers(Arrays.asList( UserIdentifierBO.builder() @@ -567,7 +567,7 @@ void patchReplaceIdentifiers() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - Optional updated = accountService.patch(accountDO.getId(), update).join(); + Optional updated = accountService.patch(accountDO.getId(), update, "main").join(); AccountBO expected = accountBO .withIdentifiers(Arrays.asList( UserIdentifierBO.builder() @@ -624,7 +624,7 @@ void addEmail() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - Optional updated = accountService.patch(accountDO.getId(), update).join(); + Optional updated = accountService.patch(accountDO.getId(), update, "main").join(); List expectedPermissions = accountBO.getPermissions().stream() .map(permission -> permission.withEntityType(null)) @@ -665,7 +665,7 @@ void addBackupEmail() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - Optional updated = accountService.patch(accountDO.getId(), update).join(); + Optional updated = accountService.patch(accountDO.getId(), update, "main").join(); List expectedPermissions = accountBO.getPermissions().stream() .map(permission -> permission.withEntityType(null)) @@ -698,7 +698,7 @@ void addPhoneNumber() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - Optional updated = accountService.patch(accountDO.getId(), update).join(); + Optional updated = accountService.patch(accountDO.getId(), update, "main").join(); List expectedPermissions = accountBO.getPermissions().stream() .map(permission -> permission.withEntityType(null)) @@ -769,7 +769,7 @@ void patchWithIdentifiers() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - Optional updated = accountService.patch(accountDO.getId(), update).join(); + Optional updated = accountService.patch(accountDO.getId(), update, "main").join(); AccountBO expected = accountBO .withIdentifiers(Arrays.asList( UserIdentifierBO.builder() @@ -824,7 +824,7 @@ void activateAccount() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - AccountBO updated = accountService.activate(accountDO.getId()).join().orElse(null); + AccountBO updated = accountService.activate(accountDO.getId(), "main").join().orElse(null); List expectedPermissions = accountBO.getPermissions().stream() .map(permission -> permission.withEntityType(null)) .collect(Collectors.toList()); @@ -847,7 +847,7 @@ void deactivateAccount() { Mockito.when(accountsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AccountDO.class)))); - AccountBO updated = accountService.deactivate(accountDO.getId()).join().orElse(null); + AccountBO updated = accountService.deactivate(accountDO.getId(), "main").join().orElse(null); assertThat(updated).isNotNull(); compareAccounts(updated, accountBO.withPermissions(expectedPermissions).withActive(false)); diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImplTest.java index 1e9dbefe..45bd1d7b 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/ActionTokenServiceImplTest.java @@ -59,11 +59,11 @@ void generateOtp() { .token("password-id") .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(accountBO))); Mockito.when(otpProvider.generateToken(accountBO)).thenReturn(CompletableFuture.completedFuture(otpResponse)); - AuthResponseBO response = actionTokenService.generateOtp(101).join(); + AuthResponseBO response = actionTokenService.generateOtp(101, "main").join(); assertThat(response).isEqualTo(otpResponse); } @@ -103,12 +103,12 @@ void generateFromOtp() { String otpToken = "1:otp"; Mockito.when(otpVerifier.verifyAccountTokenAsync(otpToken)).thenReturn(CompletableFuture.completedFuture(account.getId())); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accountTokensRepository.save(Mockito.any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(invocation.getArgument(0, AccountTokenDO.class))); - ActionTokenBO actual = actionTokenService.generateFromOtp(1, "otp", "something").join(); + ActionTokenBO actual = actionTokenService.generateFromOtp(1, "main", "otp", "something").join(); ActionTokenBO expected = ActionTokenBO.builder() .accountId(account.getId()) .validFor(Duration.ofMinutes(5).toSeconds()) diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java index fb98d73e..c51e6cdd 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java @@ -106,7 +106,7 @@ void generateApiKeyWithoutExpiration() { .id(appId) .build(); - Mockito.when(applicationsService.getById(appId)) + Mockito.when(applicationsService.getById(appId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); Mockito.when(apiKeysRepository.save(Mockito.any())) @@ -117,7 +117,7 @@ void generateApiKeyWithoutExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateApiKey(appId, "test", Duration.ZERO).join(); + ApiKeyBO actual = apiKeysService.generateApiKey(appId, "main", "test", Duration.ZERO).join(); assertThat(actual.getAppId()).isEqualTo(appId); assertThat(actual.getKey()).isEqualTo(key); @@ -147,7 +147,7 @@ void generateApiKeyWithExpiration() { .build(); Duration duration = Duration.ofDays(1); - Mockito.when(applicationsService.getById(appId)) + Mockito.when(applicationsService.getById(appId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); Mockito.when(apiKeysRepository.save(Mockito.any())) @@ -158,7 +158,7 @@ void generateApiKeyWithExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateApiKey(appId, "test", duration).join(); + ApiKeyBO actual = apiKeysService.generateApiKey(appId, "main", "test", duration).join(); assertThat(actual.getAppId()).isEqualTo(appId); assertThat(actual.getKey()).isEqualTo(key); @@ -185,10 +185,10 @@ void generateApiKeyWithExpiration() { void generateApiKeyNonExistingApp() { long appId = 1; - Mockito.when(applicationsService.getById(appId)) + Mockito.when(applicationsService.getById(appId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "test", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "main", "test", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceNotFoundException.class); } @@ -199,10 +199,10 @@ void generateApiKeyInvalidType() { .id(appId) .build(); - Mockito.when(applicationsService.getById(appId)) + Mockito.when(applicationsService.getById(appId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); - assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "none", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "main", "none", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceException.class); } @@ -219,7 +219,7 @@ void getByAppId() { Mockito.when(apiKeysRepository.getByAppId(appId)) .thenReturn(CompletableFuture.completedFuture(Collections.singletonList(apiKeyDO))); - List actual = apiKeysService.getByAppId(appId).join(); + List actual = apiKeysService.getByAppId(appId, "main").join(); assertThat(actual).isEqualTo(Collections.singletonList(apiKeyBO)); } @@ -235,10 +235,10 @@ void validateApiKey() { Mockito.when(apiKeyExchange.verifyAndGetAppId(key)) .thenReturn(CompletableFuture.completedFuture(Optional.of(appId))); - Mockito.when(applicationsService.getById(appId)) + Mockito.when(applicationsService.getById(appId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); - AppBO actual = apiKeysService.validateApiKey(key, "test").join(); + AppBO actual = apiKeysService.validateApiKey(key, "main", "test").join(); assertThat(actual).isEqualTo(app); } @@ -251,7 +251,7 @@ void generateClientApiKeyWithoutExpiration() { .id(clientId) .build(); - Mockito.when(clientsService.getById(clientId)) + Mockito.when(clientsService.getById(clientId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); Mockito.when(apiKeysRepository.save(Mockito.any())) @@ -262,7 +262,7 @@ void generateClientApiKeyWithoutExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "test", Duration.ZERO).join(); + ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "main", "test", Duration.ZERO).join(); assertThat(actual.getAppId()).isEqualTo(clientId); assertThat(actual.getKey()).isEqualTo(key); @@ -292,7 +292,7 @@ void generateClientApiKeyWithExpiration() { .build(); Duration duration = Duration.ofDays(1); - Mockito.when(clientsService.getById(clientId)) + Mockito.when(clientsService.getById(clientId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(client))); Mockito.when(apiKeysRepository.save(Mockito.any())) @@ -303,7 +303,7 @@ void generateClientApiKeyWithExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "test", duration).join(); + ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "main", "test", duration).join(); assertThat(actual.getAppId()).isEqualTo(clientId); assertThat(actual.getKey()).isEqualTo(key); @@ -330,10 +330,10 @@ void generateClientApiKeyWithExpiration() { void generateClientApiKeyNonExistingApp() { long clientId = 2; - Mockito.when(clientsService.getById(clientId)) + Mockito.when(clientsService.getById(clientId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "test", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "main", "test", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceNotFoundException.class); } @@ -344,10 +344,10 @@ void generateClientApiKeyInvalidType() { .id(clientId) .build(); - Mockito.when(clientsService.getById(clientId)) + Mockito.when(clientsService.getById(clientId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(client))); - assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "none", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "main", "none", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceException.class); } @@ -362,7 +362,7 @@ void validateClientApiKey() { Mockito.when(apiKeyExchange.verifyAndGetAppId(key)) .thenReturn(CompletableFuture.completedFuture(Optional.of(clientId))); - Mockito.when(clientsService.getById(clientId)) + Mockito.when(clientsService.getByIdUnchecked(clientId)) .thenReturn(CompletableFuture.completedFuture(Optional.of(client))); ClientBO actual = apiKeysService.validateClientApiKey(key, "test").join(); @@ -383,7 +383,7 @@ void getById() { Mockito.when(apiKeysRepository.getById(id)) .thenReturn(CompletableFuture.completedFuture(Optional.of(apiKeyDO))); - Optional actual = apiKeysService.getById(id).join(); + Optional actual = apiKeysService.getById(id, "main").join(); assertThat(actual).contains(apiKeyBO); } @@ -398,10 +398,13 @@ void delete() { .id(id) .build(); + Mockito.when(apiKeysRepository.getById(id)) + .thenReturn(CompletableFuture.completedFuture(Optional.of(apiKeyDO))); + Mockito.when(apiKeysRepository.delete(id)) .thenReturn(CompletableFuture.completedFuture(Optional.of(apiKeyDO))); - Optional actual = apiKeysService.delete(id).join(); + Optional actual = apiKeysService.delete(id, "main").join(); assertThat(actual).contains(apiKeyBO); } diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImplTest.java index af30f625..0802ea66 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/ApplicationsServiceImplTest.java @@ -56,14 +56,15 @@ void setup() { @Test void create() { - AppBO app = random.nextObject(AppBO.class); + AppBO app = random.nextObject(AppBO.class) + .withDomain("main"); String idempotentKey = "idempotent-key"; RequestContextBO requestContext = RequestContextBO.builder() .idempotentKey(idempotentKey) .build(); - Mockito.when(accountsService.getById(app.getParentAccountId())) + Mockito.when(accountsService.getById(app.getParentAccountId(), "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(random.nextObject(AccountBO.class)))); Mockito.when(applicationsRepository.save(any())) @@ -89,12 +90,13 @@ void create() { @Test void getById() { AppBO app = random.nextObject(AppBO.class) + .withDomain("main") .withDeleted(false); Mockito.when(applicationsRepository.getById(Mockito.anyLong())) .thenReturn(CompletableFuture.completedFuture(Optional.of(serviceMapper.toDO(app)))); - Optional retrieved = applicationsService.getById(1).join(); + Optional retrieved = applicationsService.getById(1, "main").join(); List expectedPermissions = app.getPermissions().stream() .map(permission -> permission.withEntityType(null)) .collect(Collectors.toList()); @@ -114,7 +116,7 @@ void delete() { Mockito.when(applicationsRepository.delete(app.getId())) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); - applicationsService.delete(app.getId()); + applicationsService.delete(app.getId(), "main"); Mockito.verify(applicationsRepository).delete(app.getId()); } @@ -124,13 +126,14 @@ void activate() { AppDO app = random.nextObject(AppDO.class); app.setActive(false); + app.setDomain("main"); Mockito.when(applicationsRepository.getById(app.getId())) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); Mockito.when(applicationsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AppDO.class)))); - AppBO updated = applicationsService.activate(app.getId()).join(); + AppBO updated = applicationsService.activate(app.getId(), "main").join(); assertThat(updated).isNotNull(); assertThat(updated.isActive()).isTrue(); @@ -141,13 +144,14 @@ void deactivate() { AppDO app = random.nextObject(AppDO.class); app.setActive(true); + app.setDomain("main"); Mockito.when(applicationsRepository.getById(app.getId())) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); Mockito.when(applicationsRepository.update(any())) .thenAnswer(invocation -> CompletableFuture.completedFuture(Optional.of(invocation.getArgument(0, AppDO.class)))); - AppBO updated = applicationsService.deactivate(app.getId()).join(); + AppBO updated = applicationsService.deactivate(app.getId(), "main").join(); assertThat(updated).isNotNull(); assertThat(updated.isActive()).isFalse(); diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/PermissionsServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/PermissionsServiceImplTest.java index d58043aa..df5565bc 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/PermissionsServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/PermissionsServiceImplTest.java @@ -93,14 +93,14 @@ void getById() { Mockito.when(permissionsRepository.getById(permission.getId())) .thenReturn(CompletableFuture.completedFuture(Optional.of(permission))); - Optional actual = permissionsService.getById(permission.getId()).join(); + Optional actual = permissionsService.getById(permission.getId(), "main").join(); assertThat(actual).contains(expected); } @Test void update() { - assertThatThrownBy(() -> permissionsService.update(PermissionBO.builder().build())) + assertThatThrownBy(() -> permissionsService.update(PermissionBO.builder().build(), "main")) .isInstanceOf(UnsupportedOperationException.class); } @@ -161,7 +161,7 @@ void delete() { Mockito.when(permissionsRepository.delete(permission.getId())) .thenReturn(CompletableFuture.completedFuture(Optional.of(permission))); - Optional actual = permissionsService.delete(permission.getId()).join(); + Optional actual = permissionsService.delete(permission.getId(), "main").join(); assertThat(actual).contains(expected); } diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/VerificationServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/VerificationServiceImplTest.java index aea0b162..b7e6a1aa 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/VerificationServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/VerificationServiceImplTest.java @@ -59,7 +59,7 @@ void verifyEmail() { .build()) .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accountTokensRepository.getByToken("verification-token")) @@ -71,9 +71,9 @@ void verifyEmail() { final ArgumentCaptor accountCaptor = ArgumentCaptor.forClass(AccountBO.class); - verificationService.verifyEmail("verification-token"); + verificationService.verifyEmail("verification-token", "main"); - Mockito.verify(accountsService).update(accountCaptor.capture()); + Mockito.verify(accountsService).update(accountCaptor.capture(), Mockito.eq("main")); assertThat(accountCaptor.getValue()).isNotNull(); assertThat(accountCaptor.getValue()) @@ -94,7 +94,7 @@ void verifyEmailWrongEmail() { .build()) .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accountTokensRepository.getByToken("verification-token")) @@ -104,7 +104,7 @@ void verifyEmailWrongEmail() { .additionalInformation(ImmutableMap.of("email", "wrong@test.com")) .build()))); - assertThatThrownBy(() -> verificationService.verifyEmail("verification-token")) + assertThatThrownBy(() -> verificationService.verifyEmail("verification-token", "main")) .isInstanceOf(ServiceException.class); } @@ -122,7 +122,7 @@ void verifyEmailNoEmail() { .build()) .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accountTokensRepository.getByToken("verification-token")) @@ -131,7 +131,7 @@ void verifyEmailNoEmail() { .expiresAt(Instant.now().plusSeconds(2)) .build()))); - assertThatThrownBy(() -> verificationService.verifyEmail("verification-token")) + assertThatThrownBy(() -> verificationService.verifyEmail("verification-token", "main")) .isInstanceOf(ServiceException.class); } @@ -149,7 +149,7 @@ void verifyEmailExpiredToken() { .build()) .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(accountTokensRepository.getByToken("verification-token")) @@ -159,7 +159,7 @@ void verifyEmailExpiredToken() { .additionalInformation(ImmutableMap.of("email", "wrong@test.com")) .build()))); - assertThatThrownBy(() -> verificationService.verifyEmail("verification-token")) + assertThatThrownBy(() -> verificationService.verifyEmail("verification-token", "main")) .isInstanceOf(ServiceException.class); } @@ -178,13 +178,13 @@ void sendPhoneNumberVerification() { .token("123456") .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(otpProvider.generateToken(account)) .thenReturn(CompletableFuture.completedFuture(otp)); - final AuthResponseBO actual = verificationService.sendPhoneNumberVerification(101); + final AuthResponseBO actual = verificationService.sendPhoneNumberVerification(101, "main"); assertThat(actual).isEqualTo(otp); } @@ -225,14 +225,14 @@ void verifyPhoneNumber() { .build()) .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(otpVerifier.verifyAccountTokenAsync("1:123456")) .thenReturn(CompletableFuture.completedFuture(101L));; // TODO account argument captor - verificationService.verifyPhoneNumber(1, "123456", "33334444"); + verificationService.verifyPhoneNumber(1, "main", "123456", "33334444"); } @Test @@ -245,13 +245,13 @@ void verifyPhoneNumberWrongNumber() { .build()) .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(otpVerifier.verifyAccountTokenAsync("1:123456")) .thenReturn(CompletableFuture.completedFuture(101L));; - assertThatThrownBy(() -> verificationService.verifyPhoneNumber(1, "123456", "9999999")) + assertThatThrownBy(() -> verificationService.verifyPhoneNumber(1, "main", "123456", "9999999")) .isInstanceOf(ServiceException.class); } @@ -261,25 +261,25 @@ void verifyPhoneNumberNullNumber() { .id(101) .build(); - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(account))); Mockito.when(otpVerifier.verifyAccountTokenAsync("1:123456")) .thenReturn(CompletableFuture.completedFuture(101L)); - assertThatThrownBy(() -> verificationService.verifyPhoneNumber(1, "123456", "9999999")) + assertThatThrownBy(() -> verificationService.verifyPhoneNumber(1, "main", "123456", "9999999")) .isInstanceOf(ServiceException.class); } @Test void verifyPhoneNumberNonExistingAccount() { - Mockito.when(accountsService.getById(101)) + Mockito.when(accountsService.getById(101, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); Mockito.when(otpVerifier.verifyAccountTokenAsync("1:123456")) .thenReturn(CompletableFuture.completedFuture(101L));; - assertThatThrownBy(() -> verificationService.verifyPhoneNumber(1, "123456", "9999999")) + assertThatThrownBy(() -> verificationService.verifyPhoneNumber(1, "main", "123456", "9999999")) .isInstanceOf(ServiceException.class); } } \ No newline at end of file diff --git a/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/OtpToSession.java b/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/OtpToSession.java index 2e26e850..a3b6ac40 100644 --- a/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/OtpToSession.java +++ b/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/OtpToSession.java @@ -35,7 +35,7 @@ public CompletableFuture exchange(final AuthRequestBO request) { .build(); return otpVerifier.verifyAccountTokenAsync(request.getToken()) - .thenCompose(accountsService::getById) + .thenCompose(id -> accountsService.getById(id, request.getDomain())) .thenCompose(opt -> { if (opt.isEmpty()) { throw new ServiceAuthorizationException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, diff --git a/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/PasswordlessToSession.java b/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/PasswordlessToSession.java index f549880e..2c0be292 100644 --- a/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/PasswordlessToSession.java +++ b/sessions/src/main/java/com/nexblocks/authguard/sessions/exchange/PasswordlessToSession.java @@ -36,7 +36,7 @@ public CompletableFuture exchange(final AuthRequestBO request) { .build(); return passwordlessVerifier.verifyAccountTokenAsync(request.getToken()) - .thenCompose(accountsService::getById) + .thenCompose(id -> accountsService.getById(id, request.getDomain())) .thenCompose(opt -> { if (opt.isEmpty()) { throw new ServiceAuthorizationException(ErrorCode.ACCOUNT_DOES_NOT_EXIST, "Account does not exist"); From 5ff486f91167bc0becf47b3c5073d43ff3665028 Mon Sep 17 00:00:00 2001 From: "Khaled Y.M" Date: Sat, 27 Jan 2024 19:34:00 +0000 Subject: [PATCH 5/5] Add name field to API keys --- .../authguard/api/dto/entities/ApiKey.java | 1 + .../api/dto/requests/ApiKeyRequest.java | 1 + api/src/main/resources/openapi.yml | 231 +++++++++++++----- .../PasswordResetRequestValidatorTest.java | 17 +- .../authguard/dal/model/ApiKeyDO.java | 1 + .../authguard/rest/routes/ApiKeysRoute.java | 6 +- .../authguard/service/ApiKeysService.java | 8 +- .../authguard/service/model/ApiKey.java | 1 + .../service/impl/ApiKeysServiceImpl.java | 21 +- .../service/impl/ApiKeysServiceImplTest.java | 16 +- 10 files changed, 198 insertions(+), 105 deletions(-) diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java index 84ce4036..376e1326 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/entities/ApiKey.java @@ -18,6 +18,7 @@ public interface ApiKey { String getAppId(); String getKey(); String getType(); + String getName(); boolean isForClient(); Instant getExpiresAt(); } diff --git a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java index 9cde487d..ee55bc50 100644 --- a/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java +++ b/api/src/main/java/com/nexblocks/authguard/api/dto/requests/ApiKeyRequest.java @@ -15,6 +15,7 @@ public interface ApiKeyRequest { boolean isForClient(); String getKeyType(); String getAppId(); + String getName(); Instant getExpiresAt(); DurationRequestDTO getValidFor(); } diff --git a/api/src/main/resources/openapi.yml b/api/src/main/resources/openapi.yml index cb96974b..decb3de8 100644 --- a/api/src/main/resources/openapi.yml +++ b/api/src/main/resources/openapi.yml @@ -2,20 +2,21 @@ openapi: 3.0.3 info: title: AuthGuard API - version: 0.20.0 + version: 0.22.0 security: - apiKey: [] paths: # ----------------- accounts ----------------- - /accounts: + /domains/{domain}/accounts: post: operationId: createAccount description: Create an account tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdempotentKeyHeader" requestBody: content: @@ -33,13 +34,14 @@ paths: description: Conflict - if the idempotent key header was used to create an account before $ref: "#/components/responses/ErrorResponse" - /accounts/{id}: + /domains/{domain}/accounts/{id}: get: operationId: getAccountById description: Get an account by ID tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -55,6 +57,7 @@ paths: tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" requestBody: content: @@ -78,6 +81,7 @@ paths: tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -87,13 +91,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/identifier/{id}: + /domains/{domain}/accounts/identifier/{id}: get: operationId: getAccountByCredentialsIdentifier description: Get an account by credentials identifier tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdentifierParameter" responses: 200: @@ -103,13 +108,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/externalId/{id}: + /domains/{domain}/accounts/externalId/{id}: get: operationId: getAccountByExternalId description: Get an account by external ID tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -119,13 +125,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/email/{email}: + /domains/{domain}/accounts/email/{email}: get: operationId: getAccountByEmail description: Get an account by email, regardless of whether it's the primary email or not tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/EmailParameter" responses: 200: @@ -135,13 +142,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/email/{email}/exists: + /domains/{domain}/accounts/email/{email}/exists: get: operationId: emailExists description: Checks whether an account with that email exists tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/EmailParameter" responses: 200: @@ -149,19 +157,20 @@ paths: 404: description: Not found - /accounts/{id}/permissions: + /domains/{domain}/accounts/{id}/permissions: patch: operationId: updateAccountPermissions description: Update the permissions of an account tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" requestBody: content: application/json: schema: - $ref: "#/components/schemas/PermissionsRequest" + $ref: "#/components/schemas/PermissionsRequest" responses: 200: description: Success @@ -170,13 +179,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/{id}/roles: + /domains/{domain}/accounts/{id}/roles: patch: operationId: updateAccountRoles description: Update the roles of an account tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" requestBody: content: @@ -191,13 +201,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/{id}/activate: + /domains/{domain}/accounts/{id}/activate: patch: operationId: activateAnAccount description: Activate an account if not already active tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -207,13 +218,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/{id}/deactivate: + /domains/{domain}/accounts/{id}/deactivate: patch: operationId: deactivateAnAccount description: Deactivate an account if it's active tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -223,13 +235,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/{id}/apps: + /domains/{domain}/accounts/{id}/apps: get: operationId: getApplicationsByAccountId description: Get a list of applications associated with an account tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -239,13 +252,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/{id}/locks: + /domains/{domain}/accounts/{id}/locks: get: operationId: getLocksByAccountId description: Get a list of active locks placed on an account tags: - Accounts parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -255,7 +269,7 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/domain/{domain}/identifier/{identifier}: + /domains/{domain}/accounts/identifier/{identifier}: get: operationId: getAccountByIdentifier description: Get account by identifier @@ -272,7 +286,7 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /accounts/domain/{domain}/identifier/{identifier}/exists: + /domains/{domain}/accounts/identifier/{identifier}/exists: get: operationId: identifierExists description: Check whether an identifier exists or not @@ -288,7 +302,7 @@ paths: description: Not found # ----------------- credentials ----------------- - /credentials/{id}/password: + /domains/{domain}/credentials/{id}/password: patch: operationId: updatePassword description: Update the password in a credentials entity @@ -300,6 +314,7 @@ paths: schema: $ref: "#/components/schemas/UpdateCredentialsPasswordRequest" parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -309,13 +324,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /credentials/{id}/identifiers: + /domains/{domain}/credentials/{id}/identifiers: patch: operationId: addIdentifier description: Add new identifiers to credentials tags: - Credentials parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" requestBody: content: @@ -339,6 +355,7 @@ paths: tags: - Credentials parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" requestBody: content: @@ -356,12 +373,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /credentials/reset_token: + /domains/{domain}/credentials/reset_token: post: operationId: generateResetToken description: Generates a new reset token which can be used by the user to set a new password tags: - Credentials + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -378,12 +397,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /credentials/reset: + /domains/{domain}/credentials/reset: post: operationId: resetPassword description: Set a new password using a reset token or by using an identifier and its current password tags: - Credentials + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -401,17 +422,19 @@ paths: $ref: "#/components/responses/ErrorResponse" # ----------------- applications ----------------- - /apps: + /domains/{domain}/apps: post: operationId: createApp description: Create an application tags: - Applications + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: schema: - $ref: "#/components/schemas/CreateApplicationRequest" + $ref: "#/components/schemas/CreateApplicationRequest" responses: 201: description: Success @@ -420,13 +443,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /apps/{id}: + /domains/{domain}/apps/{id}: get: operationId: getAppById description: Get an application by ID tags: - Applications parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -438,10 +462,11 @@ paths: delete: operationId: deleteApp - description: Delet an application + description: Delete an application tags: - Applications parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -451,7 +476,7 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /apps/{id}/keys: + /domains/{domain}/apps/{id}/keys: get: operationId: getApiKeysByAppId description: Get all API keys associated with an application @@ -459,6 +484,7 @@ paths: - Applications - API Keys parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -468,13 +494,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /apps/externalId/{id}: + /domains/{domain}/apps/externalId/{id}: get: operationId: getAppsByExternalId description: Get an application by an external ID tags: - Applications parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -484,13 +511,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /apps/{id}/activate: + /domains/{domain}/apps/{id}/activate: patch: operationId: activateAnApp description: Activate an application if not already active tags: - Applications parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -500,13 +528,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /apps/{id}/deactivate: + /domains/{domain}/apps/{id}/deactivate: patch: operationId: deactivateAnApp description: Deactivate an application if it's active tags: - Applications parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -517,17 +546,19 @@ paths: $ref: "#/components/responses/ErrorResponse" # ----------------- clients ----------------- - /clients: + /domains/{domain}/clients: post: operationId: createClient description: Create an AuthGuard client tags: - Clients + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: schema: - $ref: "#/components/schemas/CreateClientRequest" + $ref: "#/components/schemas/CreateClientRequest" responses: 201: description: Success @@ -536,13 +567,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /clients/{id}: + /domains/{domain}/clients/{id}: get: operationId: getClientById description: Get an client by ID tags: - Clients parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -558,6 +590,7 @@ paths: tags: - Clients parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -567,7 +600,7 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /clients/{id}/keys: + /domains/{domain}/clients/{id}/keys: get: operationId: getApiKeysByClientId description: Get all API keys associated with a client @@ -575,6 +608,7 @@ paths: - Clients - API Keys parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -584,13 +618,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /clients/externalId/{id}: + /domains/{domain}/clients/externalId/{id}: get: operationId: getClientsByExternalId description: Get an application by an external ID tags: - Clients parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -600,13 +635,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /clients/{id}/activate: + /domains/{domain}/clients/{id}/activate: patch: operationId: activateClient description: Activate a client if not already active tags: - Clients parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -616,13 +652,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /clients/{id}/deactivate: + /domains/{domain}/clients/{id}/deactivate: patch: operationId: deactivateClient description: Deactivate a client if it's active tags: - Clients parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -633,17 +670,19 @@ paths: $ref: "#/components/responses/ErrorResponse" # ---------------- API keys --------------- - /keys: + /domains/{domain}/keys: post: operationId: generateApiKey description: Generate a new API key for an application tags: - API Keys + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: schema: - $ref: "#/components/schemas/ApiKeyRequest" + $ref: "#/components/schemas/ApiKeyRequest" responses: 200: description: Success @@ -652,12 +691,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /keys/verify: + /domains/{domain}/keys/verify: post: operationId: verifyKey description: Verify an API key tags: - API Keys + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -671,13 +712,14 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /keys/{id}: + /domains/{domain}/keys/{id}: get: operationId: getApiKeyById description: Get an API key by ID tags: - API Keys parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" requestBody: content: @@ -697,6 +739,7 @@ paths: tags: - API Keys parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" requestBody: content: @@ -712,17 +755,19 @@ paths: $ref: "#/components/responses/ErrorResponse" # ----------------- roles ----------------- - /roles: + /domains/{domain}/roles: post: operationId: createRole description: Create a role tags: - Roles + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: schema: - $ref: "#/components/schemas/CreateRoleRequest" + $ref: "#/components/schemas/CreateRoleRequest" responses: 201: description: Success @@ -731,13 +776,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /roles/{id}: + /domains/{domain}/roles/{id}: get: operationId: getRoleById description: Get role by ID tags: - Roles parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -753,6 +799,7 @@ paths: tags: - Roles parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -762,7 +809,7 @@ paths: description: Not found $ref: "#/components/responses/ErrorResponse" - /roles/domain/{domain}/: + /domains/{domain}/roles/: get: operationId: getAllRoles description: Get all roles @@ -773,12 +820,12 @@ paths: responses: 201: description: Success - $ref: "#/components/responses/RolesArrayResponse" + $ref: "#/components/responses/domains/{domain}/rolesArrayResponse" 400: description: Bad request $ref: "#/components/responses/ErrorResponse" - /roles/domain/{domain}/roles/name/{name}: + /domains/{domain}/roles/domain/name/{name}: get: operationId: getRoleByName description: Get a role by its name @@ -796,17 +843,19 @@ paths: $ref: "#/components/responses/ErrorResponse" # ----------------- permissions ----------------- - /permissions: + /domains/{domain}/permissions: post: operationId: createPermission description: Create a permission tags: - Permissions + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: schema: - $ref: "#/components/schemas/CreatePermissionRequest" + $ref: "#/components/schemas/CreatePermissionRequest" responses: 201: description: Success @@ -815,13 +864,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /permissions/{id}: + /domains/{domain}/permissions/{id}: get: operationId: getPermissionById description: Get a permission by ID tags: - Permissions parameters: + - $ref: "#/components/parameters/DomainParameter" - $ref: "#/components/parameters/IdParameter" responses: 200: @@ -844,8 +894,8 @@ paths: 404: description: Not found $ref: "#/components/responses/ErrorResponse" - - /permissions/domain/{domain}/: + + /domains/{domain}/permissions/: get: operationId: getAllPermissions description: Get all permissions @@ -856,9 +906,9 @@ paths: responses: 200: description: Success - $ref: "#/components/responses/PermissionsArrayResponse" + $ref: "#/components/responses/domains/{domain}/permissionsArrayResponse" - /permissions/domain/{domain}/group/{group}: + /domains/{domain}/permissions/group/{group}: get: operationId: getPermissionsByGroupName description: Get a permissions by group name @@ -870,16 +920,17 @@ paths: responses: 200: description: Success - $ref: "#/components/responses/PermissionsArrayResponse" + $ref: "#/components/responses/domains/{domain}/permissionsArrayResponse" # ----------------- auth ----------------- - /auth/exchange: + /domains/{domain}/auth/exchange: post: operationId: exchange description: Perform an auth exchange tags: - Auth parameters: + - $ref: "#/components/parameters/DomainParameter" - name: from in: query required: true @@ -903,13 +954,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /auth/exchange/clear: + /domains/{domain}/auth/exchange/clear: post: operationId: clear description: Clear (delete) a token tags: - Auth parameters: + - $ref: "#/components/parameters/DomainParameter" - name: tokenType in: query required: true @@ -928,12 +980,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /auth/authenticate: + /domains/{domain}/auth/authenticate: post: operationId: authenticate description: Perform an auth exchange from 'basic' to what is specified in the configuration tags: - Auth + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -947,12 +1001,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /auth/refresh: + /domains/{domain}/auth/refresh: post: operationId: refresh description: Perform an auth exchange from 'refresh' to what is specified in the configuration tags: - Auth + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -966,12 +1022,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /auth/logout: + /domains/{domain}/auth/logout: post: operationId: logout description: Clear (delete) a token based on whatever is specified in the configuration tags: - Auth + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -985,13 +1043,14 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /auth/exchange/attempts: + /domains/{domain}/auth/exchange/attempts: get: operationId: getExchangeAttempts description: Get a list of attempts performed for an entity tags: - Auth parameters: + - $ref: "#/components/parameters/DomainParameter" - name: entityId in: query required: true @@ -1014,11 +1073,13 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /passwordless/verify: + /domains/{domain}/passwordless/verify: post: operationId: Verify a passwordless token tags: - Auth + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -1032,11 +1093,13 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /otp/verify: + /domains/{domain}/otp/verify: post: operationId: Verify a one-time password tags: - Auth + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -1056,6 +1119,7 @@ paths: tags: - Verification parameters: + - $ref: "#/components/parameters/DomainParameter" - name: token in: query required: true @@ -1068,12 +1132,13 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /actions/otp: + /domains/{domain}/actions/otp: post: operationId: Generate a one-time password for an action tags: - Action tokens parameters: + - $ref: "#/components/parameters/DomainParameter" - name: accountId in: query required: true @@ -1087,11 +1152,13 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /actions/token: + /domains/{domain}/actions/token: post: - operationId: Generate an action token either from an OTP generated by /actions/otp or by a user identifier and password + operationId: Generate an action token either from an OTP generated by /actions/domains/{domain}/otp or by a user identifier and password tags: - Action tokens + parameters: + - $ref: "#/components/parameters/DomainParameter" requestBody: content: application/json: @@ -1105,12 +1172,13 @@ paths: description: Bad request $ref: "#/components/responses/ErrorResponse" - /actions/verify: + /domains/{domain}/actions/verify: post: operationId: Verify that an action token is valid for the specified action tags: - Action tokens parameters: + - $ref: "#/components/parameters/DomainParameter" - name: token in: query required: true @@ -1142,7 +1210,7 @@ components: in: path required: true schema: - type: number + type: string DomainParameter: name: domain @@ -1487,10 +1555,20 @@ components: type: string key: type: string + type: + type: string + forClient: + type: boolean + createdAt: + type: date-time + expiresAt: + type: date-time ExchangeAttempt: type: object properties: + createdAt: + type: date-time entityId: type: string fromExchange: @@ -1507,6 +1585,8 @@ components: type: string sourceIp: type: string + userAgent: + type: string AuthRequest: type: object @@ -1685,11 +1765,20 @@ components: required: - appId - keyType + - name + - forClient properties: appId: type: string keyType: type: string + name: + type: string + validFor: + type: object + $ref: "#/components/schemas/DurationRequest" + forClient: + type: boolean ApiKeyVerificationRequest: type: object @@ -1877,6 +1966,16 @@ components: action: type: string + DurationRequest: + type: object + properties: + days: + type: number + hours: + type: number + minutes: + type: number + Error: type: object properties: diff --git a/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidatorTest.java b/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidatorTest.java index 10e95771..65f9b13f 100644 --- a/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidatorTest.java +++ b/api/src/test/java/com/nexblocks/authguard/api/dto/validation/validators/PasswordResetRequestValidatorTest.java @@ -18,6 +18,7 @@ void validateValidByToken() { .byToken(true) .resetToken("token") .newPassword("newPassword") + .domain("main") .build(); final Validator validator = Validators.getForClass(PasswordResetRequestDTO.class); @@ -58,22 +59,6 @@ void validateValidByPassword() { assertThat(violations).isEmpty(); } - @Test - void validateValidByPasswordWithoutDomain() { - final PasswordResetRequestDTO request = PasswordResetRequestDTO.builder() - .byToken(false) - .identifier("identifier") - .oldPassword("oldPassword") - .newPassword("newPassword") - .build(); - - final Validator validator = Validators.getForClass(PasswordResetRequestDTO.class); - - final List violations = validator.validate(request); - - assertThat(violations).containsExactly(new Violation("domain", ViolationType.MISSING_REQUIRED_VALUE)); - } - @Test void validateMissingIdentifierAndOldPassword() { final PasswordResetRequestDTO request = PasswordResetRequestDTO.builder() diff --git a/dal/dal-common/src/main/java/com/nexblocks/authguard/dal/model/ApiKeyDO.java b/dal/dal-common/src/main/java/com/nexblocks/authguard/dal/model/ApiKeyDO.java index c2e6169f..8e37d8de 100644 --- a/dal/dal-common/src/main/java/com/nexblocks/authguard/dal/model/ApiKeyDO.java +++ b/dal/dal-common/src/main/java/com/nexblocks/authguard/dal/model/ApiKeyDO.java @@ -41,6 +41,7 @@ public class ApiKeyDO extends AbstractDO { private String key; private long appId; private String type; + private String name; private boolean forClient; private Instant expiresAt; } diff --git a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java index 5d9bfcfa..04055b68 100644 --- a/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java +++ b/rest/src/main/java/com/nexblocks/authguard/rest/routes/ApiKeysRoute.java @@ -49,8 +49,10 @@ public void generate(final Context context) { String domain = Domain.fromContext(context); CompletableFuture key = request.isForClient() ? - apiKeysService.generateClientApiKey(IdParser.from(request.getAppId()), domain, request.getKeyType(), validFor) : - apiKeysService.generateApiKey(IdParser.from(request.getAppId()), domain, request.getKeyType(), validFor); + apiKeysService.generateClientApiKey(IdParser.from(request.getAppId()), domain, request.getKeyType(), + request.getName(), validFor) : + apiKeysService.generateApiKey(IdParser.from(request.getAppId()), domain, request.getKeyType(), + request.getName(), validFor); context.status(201).json(key.thenApply(restMapper::toDTO)); } diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java b/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java index 8225fcc9..9fb43ae3 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/ApiKeysService.java @@ -9,11 +9,11 @@ import java.util.concurrent.CompletableFuture; public interface ApiKeysService extends CrudService { - CompletableFuture generateApiKey(long appId, String domain, String type, Duration duration); - CompletableFuture generateClientApiKey(long clientId, String domain, String type, Duration duration); + CompletableFuture generateApiKey(long appId, String domain, String type, String name, Duration duration); + CompletableFuture generateClientApiKey(long clientId, String domain, String type, String name, Duration duration); - CompletableFuture generateApiKey(AppBO app, String type, Duration duration); - CompletableFuture generateClientApiKey(ClientBO client, String type, Duration duration); + CompletableFuture generateApiKey(AppBO app, String type, String name, Duration duration); + CompletableFuture generateClientApiKey(ClientBO client, String type, String name, Duration duration); CompletableFuture> getByAppId(long appId, String domain); diff --git a/service-api/src/main/java/com/nexblocks/authguard/service/model/ApiKey.java b/service-api/src/main/java/com/nexblocks/authguard/service/model/ApiKey.java index 5ac8c62d..2ecd35e0 100644 --- a/service-api/src/main/java/com/nexblocks/authguard/service/model/ApiKey.java +++ b/service-api/src/main/java/com/nexblocks/authguard/service/model/ApiKey.java @@ -10,6 +10,7 @@ public interface ApiKey extends Entity { long getAppId(); String getKey(); String getType(); + String getName(); boolean isForClient(); Instant getExpiresAt(); diff --git a/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java b/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java index f0343471..912a682c 100644 --- a/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java +++ b/service/src/main/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImpl.java @@ -87,22 +87,23 @@ public CompletableFuture> delete(final long id, final String @Override public CompletableFuture generateApiKey(final long appId, final String domain, final String type, - final Duration duration) { + final String name, final Duration duration) { return applicationsService.getById(appId, domain) .thenCompose(AsyncUtils::fromAppOptional) - .thenCompose(app -> generateApiKey(app, type, duration)); + .thenCompose(app -> generateApiKey(app, type, name, duration)); } @Override public CompletableFuture generateClientApiKey(final long clientId, final String domain, final String type, - final Duration duration) { + final String name, final Duration duration) { return clientsService.getById(clientId, domain) .thenCompose(AsyncUtils::fromClientOptional) - .thenCompose(client -> generateClientApiKey(client, type, duration)); + .thenCompose(client -> generateClientApiKey(client, type, name, duration)); } @Override - public CompletableFuture generateApiKey(final AppBO app, final String type, final Duration duration) { + public CompletableFuture generateApiKey(final AppBO app, final String type, + final String name, final Duration duration) { ApiKeyExchange apiKeyExchange = getExchangeOrFail(type); LOG.info("API key request. appId={}, domain={}, type={}, duration={}", @@ -112,7 +113,7 @@ public CompletableFuture generateApiKey(final AppBO app, final String AuthResponseBO token = apiKeyExchange.generateKey(app, expirationInstant); String generatedKey = (String) token.getToken(); String hashedKey = apiKeyHash.hash(generatedKey); - ApiKeyBO toCreate = mapApiKey(app.getId(), hashedKey, type, false, expirationInstant); + ApiKeyBO toCreate = mapApiKey(app.getId(), hashedKey, type, false, name, expirationInstant); return create(toCreate) .thenApply(persisted -> { @@ -124,7 +125,8 @@ public CompletableFuture generateApiKey(final AppBO app, final String } @Override - public CompletableFuture generateClientApiKey(ClientBO client, String type, Duration duration) { + public CompletableFuture generateClientApiKey(final ClientBO client, final String type, + final String name, final Duration duration) { ApiKeyExchange apiKeyExchange = getExchangeOrFail(type); LOG.info("API key request. clientId={}, domain={}, type={}, duration={}", @@ -134,7 +136,7 @@ public CompletableFuture generateClientApiKey(ClientBO client, String AuthResponseBO token = apiKeyExchange.generateKey(client, expirationInstant); String generatedKey = (String) token.getToken(); String hashedKey = apiKeyHash.hash(generatedKey); - ApiKeyBO toCreate = mapApiKey(client.getId(), hashedKey, type, true, expirationInstant); + ApiKeyBO toCreate = mapApiKey(client.getId(), hashedKey, type, true, name, expirationInstant); return create(toCreate) .thenApply(persisted -> { @@ -184,11 +186,12 @@ public CompletableFuture validateClientApiKey(final String key, final } private ApiKeyBO mapApiKey(final long appId, final String key, final String type, boolean forClient, - final Instant expiresAt) { + final String name, final Instant expiresAt) { ApiKeyBO.Builder builder = ApiKeyBO.builder() .appId(appId) .key(key) .type(type) + .name(name) .forClient(forClient); if (expiresAt != null) { diff --git a/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java b/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java index c51e6cdd..903e2aeb 100644 --- a/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java +++ b/service/src/test/java/com/nexblocks/authguard/service/impl/ApiKeysServiceImplTest.java @@ -117,7 +117,7 @@ void generateApiKeyWithoutExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateApiKey(appId, "main", "test", Duration.ZERO).join(); + ApiKeyBO actual = apiKeysService.generateApiKey(appId, "main", "test", "key-name", Duration.ZERO).join(); assertThat(actual.getAppId()).isEqualTo(appId); assertThat(actual.getKey()).isEqualTo(key); @@ -158,7 +158,7 @@ void generateApiKeyWithExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateApiKey(appId, "main", "test", duration).join(); + ApiKeyBO actual = apiKeysService.generateApiKey(appId, "main", "test", "key-name", duration).join(); assertThat(actual.getAppId()).isEqualTo(appId); assertThat(actual.getKey()).isEqualTo(key); @@ -188,7 +188,7 @@ void generateApiKeyNonExistingApp() { Mockito.when(applicationsService.getById(appId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "main", "test", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "main", "test", "key-name", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceNotFoundException.class); } @@ -202,7 +202,7 @@ void generateApiKeyInvalidType() { Mockito.when(applicationsService.getById(appId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(app))); - assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "main", "none", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateApiKey(appId, "main", "none", "key-name", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceException.class); } @@ -262,7 +262,7 @@ void generateClientApiKeyWithoutExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "main", "test", Duration.ZERO).join(); + ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "main", "test", "key-name", Duration.ZERO).join(); assertThat(actual.getAppId()).isEqualTo(clientId); assertThat(actual.getKey()).isEqualTo(key); @@ -303,7 +303,7 @@ void generateClientApiKeyWithExpiration() { .token(key) .build()); - ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "main", "test", duration).join(); + ApiKeyBO actual = apiKeysService.generateClientApiKey(clientId, "main", "test", "key-name", duration).join(); assertThat(actual.getAppId()).isEqualTo(clientId); assertThat(actual.getKey()).isEqualTo(key); @@ -333,7 +333,7 @@ void generateClientApiKeyNonExistingApp() { Mockito.when(clientsService.getById(clientId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "main", "test", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "main", "test", "key-name", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceNotFoundException.class); } @@ -347,7 +347,7 @@ void generateClientApiKeyInvalidType() { Mockito.when(clientsService.getById(clientId, "main")) .thenReturn(CompletableFuture.completedFuture(Optional.of(client))); - assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "main", "none", Duration.ZERO).join()) + assertThatThrownBy(() -> apiKeysService.generateClientApiKey(clientId, "main", "none", "key-name", Duration.ZERO).join()) .hasCauseInstanceOf(ServiceException.class); }