From 4d66861dab5dc9f6d8ff3e2cd09615c4fb672418 Mon Sep 17 00:00:00 2001 From: John Melati Date: Mon, 23 Dec 2024 11:09:12 +0100 Subject: [PATCH] feat: implement Trust Mark Types and Issuers --- .../controllers/TrustMarkIssuerController.kt | 57 ++++++ ...ntroller.kt => TrustMarkTypeController.kt} | 42 +++-- .../com/sphereon/oid/fed/openapi/openapi.yaml | 71 +++++--- .../EntityConfigurationStatementBuilder.kt | 10 +- .../oid/fed/persistence/Persistence.kt | 6 +- .../oid/fed/persistence/models/11.sqm | 10 +- .../oid/fed/persistence/models/12.sqm | 16 ++ .../fed/persistence/models/TrustMarkIssuer.sq | 18 ++ ...rustMarkDefinition.sq => TrustMarkType.sq} | 16 +- .../Persistence.jvm.kt | 9 +- .../EntityConfigurationStatementService.kt | 14 ++ .../oid/fed/services/TrustMarkService.kt | 172 ++++++++++++++---- .../extensions/TrustMarkExtensions.kt | 8 +- 13 files changed, 344 insertions(+), 105 deletions(-) create mode 100644 modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkIssuerController.kt rename modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/{TrustMarkDefinitionController.kt => TrustMarkTypeController.kt} (54%) create mode 100644 modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/12.sqm create mode 100644 modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkIssuer.sq rename modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/{TrustMarkDefinition.sq => TrustMarkType.sq} (62%) diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkIssuerController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkIssuerController.kt new file mode 100644 index 00000000..ec928917 --- /dev/null +++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkIssuerController.kt @@ -0,0 +1,57 @@ +package com.sphereon.oid.fed.server.admin.controllers + +import com.sphereon.oid.fed.openapi.models.CreateTrustMarkTypeIssuerDTO +import com.sphereon.oid.fed.persistence.models.TrustMarkIssuer +import com.sphereon.oid.fed.services.AccountService +import com.sphereon.oid.fed.services.TrustMarkService +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/accounts/{username}/trust-mark-types/{id}/issuers") +class TrustMarkIssuerController { + private val accountService = AccountService() + private val trustMarkService = TrustMarkService() + + @GetMapping + fun getIssuersForTrustMarkType( + @PathVariable username: String, + @PathVariable id: Int + ): List { + return trustMarkService.getIssuersForTrustMarkType( + accountId = accountService.usernameToAccountId(username), + trustMarkTypeId = id + ) + } + + @PostMapping + fun addIssuerToTrustMarkType( + @PathVariable username: String, + @PathVariable id: Int, + @RequestBody body: CreateTrustMarkTypeIssuerDTO + ): TrustMarkIssuer { + return trustMarkService.addIssuerToTrustMarkType( + accountId = accountService.usernameToAccountId(username), + trustMarkTypeId = id, + issuerIdentifier = body.identifier + ) + } + + @DeleteMapping("/{issuerIdentifier}") + fun removeIssuerFromTrustMarkType( + @PathVariable username: String, + @PathVariable id: Int, + @PathVariable issuerIdentifier: String + ): TrustMarkIssuer { + return trustMarkService.removeIssuerFromTrustMarkType( + accountId = accountService.usernameToAccountId(username), + trustMarkTypeId = id, + issuerIdentifier = issuerIdentifier + ) + } +} diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkDefinitionController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkTypeController.kt similarity index 54% rename from modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkDefinitionController.kt rename to modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkTypeController.kt index 2e23809a..cd788234 100644 --- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkDefinitionController.kt +++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/TrustMarkTypeController.kt @@ -1,8 +1,8 @@ package com.sphereon.oid.fed.server.admin.controllers -import com.sphereon.oid.fed.openapi.models.CreateTrustMarkDefinitionDTO -import com.sphereon.oid.fed.openapi.models.TrustMarkDefinitionDTO -import com.sphereon.oid.fed.openapi.models.UpdateTrustMarkDefinitionDTO +import com.sphereon.oid.fed.openapi.models.CreateTrustMarkTypeDTO +import com.sphereon.oid.fed.openapi.models.TrustMarkTypeDTO +import com.sphereon.oid.fed.openapi.models.UpdateTrustMarkTypeDTO import com.sphereon.oid.fed.services.AccountService import com.sphereon.oid.fed.services.TrustMarkService import org.springframework.web.bind.annotation.DeleteMapping @@ -15,46 +15,50 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping("/accounts/{username}/trust-mark-definitions") -class TrustMarkDefinitionController { +@RequestMapping("/accounts/{username}/trust-mark-types") +class TrustMarkTypeController { private val accountService = AccountService() private val trustMarkService = TrustMarkService() @GetMapping - fun getTrustMarkDefinitions(@PathVariable username: String): List { + fun getTrustMarkTypes(@PathVariable username: String): List { return trustMarkService.findAllByAccount(accountService.usernameToAccountId(username)) } @PostMapping - fun createTrustMarkDefinition( + fun createTrustMarkType( @PathVariable username: String, - @RequestBody createDto: CreateTrustMarkDefinitionDTO - ): TrustMarkDefinitionDTO { - return trustMarkService.createTrustMarkDefinition(accountService.usernameToAccountId(username), createDto) + @RequestBody createDto: CreateTrustMarkTypeDTO + ): TrustMarkTypeDTO { + return trustMarkService.createTrustMarkType( + username, + createDto, + accountService + ) } @GetMapping("/{id}") - fun getTrustMarkDefinitionById( + fun getTrustMarkTypeById( @PathVariable username: String, @PathVariable id: Int - ): TrustMarkDefinitionDTO { + ): TrustMarkTypeDTO { return trustMarkService.findById(accountService.usernameToAccountId(username), id) } @PutMapping("/{id}") - fun updateTrustMarkDefinition( + fun updateTrustMarkType( @PathVariable username: String, @PathVariable id: Int, - @RequestBody updateDto: UpdateTrustMarkDefinitionDTO - ): TrustMarkDefinitionDTO { - return trustMarkService.updateTrustMarkDefinition(accountService.usernameToAccountId(username), id, updateDto) + @RequestBody updateDto: UpdateTrustMarkTypeDTO + ): TrustMarkTypeDTO { + return trustMarkService.updateTrustMarkType(accountService.usernameToAccountId(username), id, updateDto) } @DeleteMapping("/{id}") - fun deleteTrustMarkDefinition( + fun deleteTrustMarkType( @PathVariable username: String, @PathVariable id: Int - ): TrustMarkDefinitionDTO { - return trustMarkService.deleteTrustMarkDefinition(accountService.usernameToAccountId(username), id) + ): TrustMarkTypeDTO { + return trustMarkService.deleteTrustMarkType(accountService.usernameToAccountId(username), id) } } diff --git a/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml b/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml index f6f8b8c5..c9ddc5b0 100644 --- a/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml +++ b/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml @@ -1441,7 +1441,7 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '/accounts/{username}/trust-mark-definitions': + '/accounts/{username}/trust-mark-types': get: summary: Get all Trust Mark Definitions tags: @@ -1455,13 +1455,13 @@ paths: description: The username of the tenant account. responses: '200': - description: List of trust mark definitions + description: List of trust mark types content: application/json: schema: type: array items: - $ref: '#/components/schemas/TrustMarkDefinitionDTO' + $ref: '#/components/schemas/TrustMarkTypeDTO' post: summary: Create a Trust Mark Definition @@ -1478,16 +1478,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateTrustMarkDefinitionDTO' + $ref: '#/components/schemas/CreateTrustMarkTypeDTO' responses: '201': description: Trust mark definition created content: application/json: schema: - $ref: '#/components/schemas/TrustMarkDefinitionDTO' + $ref: '#/components/schemas/TrustMarkTypeDTO' - '/accounts/{username}/trust-mark-definitions/{id}': + '/accounts/{username}/trust-mark-types/{id}': get: summary: Get a Trust Mark Definition by ID tags: @@ -1509,7 +1509,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TrustMarkDefinitionDTO' + $ref: '#/components/schemas/TrustMarkTypeDTO' put: summary: Update a Trust Mark Definition tags: @@ -1530,14 +1530,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateTrustMarkDefinitionDTO' + $ref: '#/components/schemas/CreateTrustMarkTypeDTO' responses: '200': description: Trust mark definition updated content: application/json: schema: - $ref: '#/components/schemas/TrustMarkDefinitionDTO' + $ref: '#/components/schemas/TrustMarkTypeDTO' delete: summary: Delete a Trust Mark Definition @@ -2232,11 +2232,18 @@ components: type: object x-tags: - federation - properties: - a: + description: A mapping of trust mark identifiers to their associated issuers. + additionalProperties: + type: array + description: A list of issuers for the trust mark. + items: type: string + format: uri + description: The URI of an issuer for the trust mark. example: - 'https://openid.net/certification/op': [ ] + 'https://openid.net/certification/op': + - 'https://example-issuer1.com' + - 'https://example-issuer2.com' 'https://refeds.org/wp-content/uploads/2016/01/Sirtfi-1.0.pdf': - 'https://swamid.se' TrustMarkOwners: @@ -3830,69 +3837,68 @@ components: description: The identifier of the authority hint. required: - identifier - CreateTrustMarkDefinitionDTO: + CreateTrustMarkTypeDTO: type: object x-tags: - federation properties: identifier: type: string - description: The unique identifier for the Trust Mark Definition. + description: The unique identifier for the Trust Mark Type. example: "example-identifier" name: type: string - description: A human-readable name for the Trust Mark Definition. + description: A human-readable name for the Trust Mark Type. example: "Example Trust Mark" description: type: string - description: A detailed description of the Trust Mark Definition. + description: A detailed description of the Trust Mark Type. example: "This is a trust mark for demonstrating compliance with XYZ standards." required: - - identifier - name - UpdateTrustMarkDefinitionDTO: + UpdateTrustMarkTypeDTO: type: object x-tags: - federation properties: name: type: string - description: A human-readable name for the Trust Mark Definition. + description: A human-readable name for the Trust Mark Type. example: "Example Trust Mark" description: type: string - description: A detailed description of the Trust Mark Definition. + description: A detailed description of the Trust Mark Type. example: "This is a trust mark for demonstrating compliance with XYZ standards." - TrustMarkDefinitionDTO: + TrustMarkTypeDTO: type: object x-tags: - federation properties: id: type: integer - description: The unique identifier of the Trust Mark Definition. + description: The unique identifier of the Trust Mark Type. example: 123 identifier: type: string - description: The unique identifier for the Trust Mark Definition. + description: The unique identifier for the Trust Mark Type. example: "https://www.example.com/oidf/trustmark/underageSafetyVerified" name: type: string - description: A human-readable name for the Trust Mark Definition. + description: A human-readable name for the Trust Mark Type. example: "Example Trust Mark" description: type: string - description: A detailed description of the Trust Mark Definition. + description: A detailed description of the Trust Mark Type. example: "This is a trust mark for demonstrating compliance with XYZ standards." createdAt: type: string format: date-time - description: The timestamp when the Trust Mark Definition was created. + description: The timestamp when the Trust Mark Type was created. example: "2024-12-01T12:00:00Z" updatedAt: type: string format: date-time - description: The timestamp when the Trust Mark Definition was last updated. + description: The timestamp when the Trust Mark Type was last updated. example: "2024-12-15T15:30:00Z" nullable: true required: @@ -3901,3 +3907,14 @@ components: - name - issuerPolicy - createdAt + CreateTrustMarkTypeIssuerDTO: + type: object + x-tags: + - federation + properties: + identifier: + type: string + description: The entity identifier for the Trust Mark Type Issuer. + example: "https://www.example.com/oidf" + required: + - identifier diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt index 5bf267f2..b8d2e975 100644 --- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt +++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt @@ -3,7 +3,6 @@ package com.sphereon.oid.fed.common.builder import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement import com.sphereon.oid.fed.openapi.models.EntityJwks import com.sphereon.oid.fed.openapi.models.Jwk -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.JsonObject class EntityConfigurationStatementBuilder { @@ -13,6 +12,7 @@ class EntityConfigurationStatementBuilder { private lateinit var jwks: List private var metadata: MutableMap = mutableMapOf() private val authorityHints: MutableList = mutableListOf() + private val trustMarkIssuers: MutableMap> = mutableMapOf() private val crit: MutableList = mutableListOf() fun iss(iss: String) = apply { this.iss = iss } @@ -32,7 +32,10 @@ class EntityConfigurationStatementBuilder { this.crit.add(claim) } - @OptIn(ExperimentalSerializationApi::class) + fun trustMarkIssuer(trustMark: String, issuers: List) = apply { + this.trustMarkIssuers[trustMark] = issuers + } + private fun createJwks(jwks: List): EntityJwks { return EntityJwks(jwks.toTypedArray()) } @@ -46,7 +49,8 @@ class EntityConfigurationStatementBuilder { jwks = createJwks(jwks), metadata = JsonObject(metadata), authorityHints = if (authorityHints.isNotEmpty()) authorityHints.toTypedArray() else null, - crit = if (crit.isNotEmpty()) crit.toTypedArray() else null + crit = if (crit.isNotEmpty()) crit.toTypedArray() else null, + trustMarkIssuers = this.trustMarkIssuers.map { (k, v) -> k to v.toTypedArray() }.toMap() ) } } diff --git a/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Persistence.kt b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Persistence.kt index 0066440b..57a0fe62 100644 --- a/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Persistence.kt +++ b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Persistence.kt @@ -10,7 +10,8 @@ import com.sphereon.oid.fed.persistence.models.SubordinateJwkQueries import com.sphereon.oid.fed.persistence.models.SubordinateMetadataQueries import com.sphereon.oid.fed.persistence.models.SubordinateQueries import com.sphereon.oid.fed.persistence.models.SubordinateStatementQueries -import com.sphereon.oid.fed.persistence.models.TrustMarkDefinitionQueries +import com.sphereon.oid.fed.persistence.models.TrustMarkIssuerQueries +import com.sphereon.oid.fed.persistence.models.TrustMarkTypeQueries expect object Persistence { val entityConfigurationStatementQueries: EntityConfigurationStatementQueries @@ -23,5 +24,6 @@ expect object Persistence { val subordinateStatementQueries: SubordinateStatementQueries val subordinateJwkQueries: SubordinateJwkQueries val subordinateMetadataQueries: SubordinateMetadataQueries - val trustMarkDefinitionQueries: TrustMarkDefinitionQueries + val trustMarkTypeQueries: TrustMarkTypeQueries + val trustMarkIssuerQueries: TrustMarkIssuerQueries } diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/11.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/11.sqm index b5284c0e..df24d465 100644 --- a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/11.sqm +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/11.sqm @@ -1,4 +1,4 @@ -CREATE TABLE TrustMarkDefinition ( +CREATE TABLE TrustMarkType ( id SERIAL PRIMARY KEY, account_id INT NOT NULL, identifier TEXT NOT NULL, @@ -7,13 +7,13 @@ CREATE TABLE TrustMarkDefinition ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP, - CONSTRAINT FK_AccountTrustMarkDefinition FOREIGN KEY (account_id) REFERENCES Account (id) ON DELETE CASCADE + CONSTRAINT FK_AccountTrustMarkType FOREIGN KEY (account_id) REFERENCES Account (id) ON DELETE CASCADE ); CREATE UNIQUE INDEX unique_account_identifier_active -ON TrustMarkDefinition (account_id, identifier) +ON TrustMarkType (account_id, identifier) WHERE deleted_at IS NULL; -CREATE INDEX idx_trustmarkdefinitions_account_id ON TrustMarkDefinition (account_id); +CREATE INDEX idx_trustmarkdefinitions_account_id ON TrustMarkType (account_id); -CREATE INDEX idx_trustmarkdefinitions_deleted_at ON TrustMarkDefinition (deleted_at); +CREATE INDEX idx_trustmarkdefinitions_deleted_at ON TrustMarkType (deleted_at); diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/12.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/12.sqm new file mode 100644 index 00000000..87dc8ba8 --- /dev/null +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/12.sqm @@ -0,0 +1,16 @@ +CREATE TABLE TrustMarkIssuer ( + id SERIAL PRIMARY KEY, + trust_mark_type_id INT NOT NULL, + issuer_identifier TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP, + CONSTRAINT FK_TrustMarkType FOREIGN KEY (trust_mark_type_id) REFERENCES TrustMarkType (id) ON DELETE CASCADE +); + +CREATE UNIQUE INDEX unique_trustmarkissuer_type_identifier_active +ON TrustMarkIssuer (trust_mark_type_id, issuer_identifier) +WHERE deleted_at IS NULL; + +CREATE INDEX idx_trustmarkissuer_deleted_at ON TrustMarkIssuer (deleted_at); +CREATE INDEX idx_trustmarkissuer_type ON TrustMarkIssuer (trust_mark_type_id); +CREATE INDEX idx_trustmarkissuer_identifier ON TrustMarkIssuer (issuer_identifier); diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkIssuer.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkIssuer.sq new file mode 100644 index 00000000..86c54892 --- /dev/null +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkIssuer.sq @@ -0,0 +1,18 @@ +create: +INSERT INTO TrustMarkIssuer (trust_mark_type_id, issuer_identifier) +VALUES (:trust_mark_type_id, :issuer_identifier) +RETURNING *; + +delete: +UPDATE TrustMarkIssuer +SET deleted_at = CURRENT_TIMESTAMP +WHERE trust_mark_type_id = :trust_mark_type_id AND issuer_identifier = :issuer_identifier AND deleted_at IS NULL +RETURNING *; + +findByTrustMarkTypeId: +SELECT * FROM TrustMarkIssuer +WHERE trust_mark_type_id = :trust_mark_type_id AND deleted_at IS NULL; + +findByIssuerIdentifier: +SELECT * FROM TrustMarkIssuer +WHERE issuer_identifier = :issuer_identifier AND deleted_at IS NULL; diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkDefinition.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkType.sq similarity index 62% rename from modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkDefinition.sq rename to modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkType.sq index de4c52d7..7ab7515f 100644 --- a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkDefinition.sq +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/TrustMarkType.sq @@ -1,21 +1,21 @@ create: -INSERT INTO TrustMarkDefinition (identifier, name, description, account_id) +INSERT INTO TrustMarkType (identifier, name, description, account_id) VALUES (:identifier, :name, :description, :account_id) RETURNING *; findByAccountId: -SELECT * FROM TrustMarkDefinition WHERE account_id = :account_id AND deleted_at IS NULL; +SELECT * FROM TrustMarkType WHERE account_id = :account_id AND deleted_at IS NULL; findByAccountIdAndId: -SELECT * FROM TrustMarkDefinition +SELECT * FROM TrustMarkType WHERE account_id = :account_id AND id = :id AND deleted_at IS NULL; findByAccountIdAndIdentifier: -SELECT * FROM TrustMarkDefinition +SELECT * FROM TrustMarkType WHERE account_id = :account_id AND identifier = :identifier AND deleted_at IS NULL; update: -UPDATE TrustMarkDefinition +UPDATE TrustMarkType SET name = COALESCE(:name, name), description = COALESCE(:description, description), @@ -24,7 +24,11 @@ WHERE id = :id AND deleted_at IS NULL RETURNING *; delete: -UPDATE TrustMarkDefinition +UPDATE TrustMarkType SET deleted_at = CURRENT_TIMESTAMP WHERE id = :id AND deleted_at IS NULL RETURNING *; + +findByIdentifier: +SELECT * FROM TrustMarkType +WHERE identifier = :identifier AND deleted_at IS NULL; diff --git a/modules/persistence/src/jvmMain/kotlin/com.sphereon.oid.fed.persistence/Persistence.jvm.kt b/modules/persistence/src/jvmMain/kotlin/com.sphereon.oid.fed.persistence/Persistence.jvm.kt index 1c20ed22..8b2c0365 100644 --- a/modules/persistence/src/jvmMain/kotlin/com.sphereon.oid.fed.persistence/Persistence.jvm.kt +++ b/modules/persistence/src/jvmMain/kotlin/com.sphereon.oid.fed.persistence/Persistence.jvm.kt @@ -14,7 +14,8 @@ import com.sphereon.oid.fed.persistence.models.SubordinateJwkQueries import com.sphereon.oid.fed.persistence.models.SubordinateMetadataQueries import com.sphereon.oid.fed.persistence.models.SubordinateQueries import com.sphereon.oid.fed.persistence.models.SubordinateStatementQueries -import com.sphereon.oid.fed.persistence.models.TrustMarkDefinitionQueries +import com.sphereon.oid.fed.persistence.models.TrustMarkIssuerQueries +import com.sphereon.oid.fed.persistence.models.TrustMarkTypeQueries actual object Persistence { actual val entityConfigurationStatementQueries: EntityConfigurationStatementQueries @@ -27,7 +28,8 @@ actual object Persistence { actual val subordinateStatementQueries: SubordinateStatementQueries actual val subordinateJwkQueries: SubordinateJwkQueries actual val subordinateMetadataQueries: SubordinateMetadataQueries - actual val trustMarkDefinitionQueries: TrustMarkDefinitionQueries + actual val trustMarkTypeQueries: TrustMarkTypeQueries + actual val trustMarkIssuerQueries: TrustMarkIssuerQueries init { val driver = getDriver() @@ -44,7 +46,8 @@ actual object Persistence { subordinateStatementQueries = database.subordinateStatementQueries subordinateJwkQueries = database.subordinateJwkQueries subordinateMetadataQueries = database.subordinateMetadataQueries - trustMarkDefinitionQueries = database.trustMarkDefinitionQueries + trustMarkTypeQueries = database.trustMarkTypeQueries + trustMarkIssuerQueries = database.trustMarkIssuerQueries } private fun getDriver(): SqlDriver { diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt index d42b1c2a..ff338a36 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt @@ -31,6 +31,8 @@ class EntityConfigurationStatementService { authorityHintQueries.findByAccountId(account.id).executeAsList().map { it.identifier }.toTypedArray() val crits = Persistence.critQueries.findByAccountId(account.id).executeAsList().map { it.claim }.toTypedArray() val metadata = Persistence.entityConfigurationMetadataQueries.findByAccountId(account.id).executeAsList() + val trustMarkTypes = + Persistence.trustMarkTypeQueries.findByAccountId(account.id).executeAsList() val entityConfigurationStatement = EntityConfigurationStatementBuilder() .iss(identifier) @@ -65,9 +67,21 @@ class EntityConfigurationStatementService { entityConfigurationStatement.crit(it) } + trustMarkTypes.forEach { trustMarkType -> + + val trustMarkIssuers = + Persistence.trustMarkIssuerQueries.findByTrustMarkTypeId(trustMarkType.id).executeAsList() + + entityConfigurationStatement.trustMarkIssuer( + trustMarkType.identifier, + trustMarkIssuers.map { it.issuer_identifier } + ) + } + return entityConfigurationStatement.build() } + fun publishByUsername(accountUsername: String, dryRun: Boolean? = false): String { val account = accountService.getAccountByUsername(accountUsername) diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/TrustMarkService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/TrustMarkService.kt index e4a471c2..70dc563c 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/TrustMarkService.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/TrustMarkService.kt @@ -2,70 +2,170 @@ package com.sphereon.oid.fed.services import com.sphereon.oid.fed.common.exceptions.EntityAlreadyExistsException import com.sphereon.oid.fed.common.exceptions.NotFoundException -import com.sphereon.oid.fed.openapi.models.CreateTrustMarkDefinitionDTO -import com.sphereon.oid.fed.openapi.models.TrustMarkDefinitionDTO -import com.sphereon.oid.fed.openapi.models.UpdateTrustMarkDefinitionDTO +import com.sphereon.oid.fed.openapi.models.CreateTrustMarkTypeDTO +import com.sphereon.oid.fed.openapi.models.TrustMarkTypeDTO +import com.sphereon.oid.fed.openapi.models.UpdateTrustMarkTypeDTO import com.sphereon.oid.fed.persistence.Persistence -import com.sphereon.oid.fed.services.extensions.toTrustMarkDefinitionDTO +import com.sphereon.oid.fed.persistence.models.TrustMarkIssuer +import com.sphereon.oid.fed.services.extensions.toTrustMarkTypeDTO class TrustMarkService { - private val trustMarkQueries = Persistence.trustMarkDefinitionQueries + private val trustMarkTypeQueries = Persistence.trustMarkTypeQueries + private val trustMarkIssuerQueries = Persistence.trustMarkIssuerQueries - fun createTrustMarkDefinition(accountId: Int, createDto: CreateTrustMarkDefinitionDTO): TrustMarkDefinitionDTO { - val existingDefinition = - trustMarkQueries.findByAccountIdAndIdentifier(accountId, createDto.identifier).executeAsOneOrNull() - if (existingDefinition != null) { - throw EntityAlreadyExistsException("A trust mark definition with the given identifier already exists for this account.") - } + fun createTrustMarkType( + username: String, + createDto: CreateTrustMarkTypeDTO, + accountService: AccountService + ): TrustMarkTypeDTO { + val account = accountService.getAccountByUsername(username) + + this.validateTrustMarkTypeIdentifierDoesNotExist(account.id, createDto.identifier) - val createdDefinition = trustMarkQueries.create( - account_id = accountId, - identifier = createDto.identifier, + val createdType = trustMarkTypeQueries.create( + account_id = account.id, + identifier = createDto.identifier + ?: this.generateUniqueTrustMarkTypeIdentifier( + accountService.getAccountIdentifier(username), + createDto.name + ), name = createDto.name, description = createDto.description ).executeAsOne() - return createdDefinition.toTrustMarkDefinitionDTO() + return createdType.toTrustMarkTypeDTO() } - fun findAllByAccount(accountId: Int): List { - return trustMarkQueries.findByAccountId(accountId).executeAsList().map { it.toTrustMarkDefinitionDTO() } + private fun validateTrustMarkTypeIdentifierDoesNotExist(accountId: Int, identifier: String?) { + if (identifier != null) { + val trustMarkAlreadyExists = trustMarkTypeQueries.findByAccountIdAndIdentifier(accountId, identifier) + .executeAsOneOrNull() + + if (trustMarkAlreadyExists != null) { + throw EntityAlreadyExistsException("A trust mark type with the given identifier already exists for this account.") + } + } } - fun findById(accountId: Int, id: Int): TrustMarkDefinitionDTO { - val definition = trustMarkQueries.findByAccountIdAndId(accountId, id).executeAsOneOrNull() + fun findAllByAccount(accountId: Int): List { + return trustMarkTypeQueries.findByAccountId(accountId).executeAsList() + .map { it.toTrustMarkTypeDTO() } + } + + fun findById(accountId: Int, id: Int): TrustMarkTypeDTO { + val definition = trustMarkTypeQueries.findByAccountIdAndId(accountId, id).executeAsOneOrNull() ?: throw NotFoundException("Trust mark definition with ID $id not found for account $accountId.") - return definition.toTrustMarkDefinitionDTO() + return definition.toTrustMarkTypeDTO() } - fun findByIdentifier(accountId: Int, identifier: String): TrustMarkDefinitionDTO { - val definition = trustMarkQueries.findByAccountIdAndIdentifier(accountId, identifier).executeAsOneOrNull() - ?: throw NotFoundException("Trust mark definition with identifier $identifier not found for account $accountId.") - return definition.toTrustMarkDefinitionDTO() + fun findByIdentifier(accountId: Int, identifier: String): TrustMarkTypeDTO { + val definition = + trustMarkTypeQueries.findByAccountIdAndIdentifier(accountId, identifier).executeAsOneOrNull() + ?: throw NotFoundException("Trust mark definition with identifier $identifier not found for account $accountId.") + return definition.toTrustMarkTypeDTO() } - fun updateTrustMarkDefinition( + fun updateTrustMarkType( accountId: Int, id: Int, - updateDto: UpdateTrustMarkDefinitionDTO - ): TrustMarkDefinitionDTO { - val existingDefinition = trustMarkQueries.findByAccountIdAndId(accountId, id).executeAsOneOrNull() + updateDto: UpdateTrustMarkTypeDTO + ): TrustMarkTypeDTO { + val existingType = trustMarkTypeQueries.findByAccountIdAndId(accountId, id).executeAsOneOrNull() ?: throw NotFoundException("Trust mark definition with ID $id not found for account $accountId.") - val updatedDefinition = trustMarkQueries.update( - name = updateDto.name ?: existingDefinition.name, - description = updateDto.description ?: existingDefinition.description, + val updatedType = trustMarkTypeQueries.update( + name = updateDto.name ?: existingType.name, + description = updateDto.description ?: existingType.description, id = id ).executeAsOne() - return updatedDefinition.toTrustMarkDefinitionDTO() + return updatedType.toTrustMarkTypeDTO() } - fun deleteTrustMarkDefinition(accountId: Int, id: Int): TrustMarkDefinitionDTO { - val definition = trustMarkQueries.findByAccountIdAndId(accountId, id).executeAsOneOrNull() + fun deleteTrustMarkType(accountId: Int, id: Int): TrustMarkTypeDTO { + trustMarkTypeQueries.findByAccountIdAndId(accountId, id).executeAsOneOrNull() ?: throw NotFoundException("Trust mark definition with ID $id not found for account $accountId.") - val deletedDefinition = trustMarkQueries.delete(id).executeAsOne() - return deletedDefinition.toTrustMarkDefinitionDTO() + val deletedType = trustMarkTypeQueries.delete(id).executeAsOne() + return deletedType.toTrustMarkTypeDTO() + } + + fun getIssuersForTrustMarkType(accountId: Int, trustMarkTypeId: Int): List { + // Validate that the trust mark definition belongs to the account + val definitionExists = trustMarkTypeQueries.findByAccountIdAndId(accountId, trustMarkTypeId) + .executeAsOneOrNull() + + if (definitionExists == null) { + throw NotFoundException("Trust mark definition with ID $trustMarkTypeId not found for account $accountId.") + } + + // Fetch and return issuers + return trustMarkIssuerQueries.findByTrustMarkTypeId(trustMarkTypeId) + .executeAsList() + .map { it.issuer_identifier } + } + + fun addIssuerToTrustMarkType(accountId: Int, trustMarkTypeId: Int, issuerIdentifier: String): TrustMarkIssuer { + // Validate that the trust mark definition belongs to the account + val definitionExists = trustMarkTypeQueries.findByAccountIdAndId(accountId, trustMarkTypeId) + .executeAsOneOrNull() + + if (definitionExists == null) { + throw NotFoundException("Trust mark definition with ID $trustMarkTypeId not found for account $accountId.") + } + + // Check if the issuer already exists + val existingIssuer = trustMarkIssuerQueries.findByTrustMarkTypeId(trustMarkTypeId) + .executeAsList() + .any { it.issuer_identifier == issuerIdentifier } + + if (existingIssuer) { + throw EntityAlreadyExistsException("Issuer $issuerIdentifier is already associated with the trust mark definition.") + } + + // Add the issuer + return trustMarkIssuerQueries.create( + trust_mark_type_id = trustMarkTypeId, + issuer_identifier = issuerIdentifier + ).executeAsOne() + } + + fun removeIssuerFromTrustMarkType(accountId: Int, trustMarkTypeId: Int, issuerIdentifier: String): TrustMarkIssuer { + // Validate that the trust mark definition belongs to the account + val definitionExists = trustMarkTypeQueries.findByAccountIdAndId(accountId, trustMarkTypeId) + .executeAsOneOrNull() + + if (definitionExists == null) { + throw NotFoundException("Trust mark definition with ID $trustMarkTypeId not found for account $accountId.") + } + + // Check if the issuer exists + trustMarkIssuerQueries.findByTrustMarkTypeId(trustMarkTypeId) + .executeAsList() + .find { it.issuer_identifier == issuerIdentifier } + ?: throw NotFoundException("Issuer $issuerIdentifier is not associated with the trust mark definition.") + + // Soft-delete the issuer + return trustMarkIssuerQueries.delete( + trust_mark_type_id = trustMarkTypeId, + issuer_identifier = issuerIdentifier + ).executeAsOne() + } + + private fun generateUniqueTrustMarkTypeIdentifier(identifier: String, name: String): String { + val trustMarkTypeBaseIdentifier = "$identifier/trust-mark-types/$name" + + var counter = 2 + + var trustMarkTypeIdentifier = trustMarkTypeBaseIdentifier + + while (trustMarkTypeQueries.findByIdentifier(trustMarkTypeIdentifier) + .executeAsOneOrNull() != null + ) { + trustMarkTypeIdentifier = "$trustMarkTypeBaseIdentifier-$counter" + counter++ + } + + return trustMarkTypeIdentifier } } diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/TrustMarkExtensions.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/TrustMarkExtensions.kt index d695652a..cefd46b6 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/TrustMarkExtensions.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/TrustMarkExtensions.kt @@ -1,10 +1,10 @@ package com.sphereon.oid.fed.services.extensions -import com.sphereon.oid.fed.openapi.models.TrustMarkDefinitionDTO -import com.sphereon.oid.fed.persistence.models.TrustMarkDefinition +import com.sphereon.oid.fed.openapi.models.TrustMarkTypeDTO +import com.sphereon.oid.fed.persistence.models.TrustMarkType -fun TrustMarkDefinition.toTrustMarkDefinitionDTO(): TrustMarkDefinitionDTO { - return TrustMarkDefinitionDTO( +fun TrustMarkType.toTrustMarkTypeDTO(): TrustMarkTypeDTO { + return TrustMarkTypeDTO( id = this.id, identifier = this.identifier, name = this.name,