diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/CritController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/CritController.kt new file mode 100644 index 00000000..90e0c720 --- /dev/null +++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/CritController.kt @@ -0,0 +1,41 @@ +package com.sphereon.oid.fed.server.admin.controllers + +import com.sphereon.oid.fed.openapi.models.CreateCritDTO +import com.sphereon.oid.fed.persistence.models.Crit +import com.sphereon.oid.fed.services.CritService +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/{accountUsername}/crits") +class CritController { + private val critService = CritService() + + @PostMapping + fun createCrit( + @PathVariable accountUsername: String, + @RequestBody body: CreateCritDTO + ): Crit { + return critService.create(accountUsername, body.claim) + } + + @GetMapping + fun getCrits( + @PathVariable accountUsername: String + ): Array { + return critService.findByAccountUsername(accountUsername) + } + + @DeleteMapping("/{id}") + fun deleteCrit( + @PathVariable accountUsername: String, + @PathVariable id: Int + ): Crit { + return critService.delete(accountUsername, 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 5b50cef8..ed9aa27e 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 @@ -1959,6 +1959,16 @@ components: example: 2024-08-06T12:34:56Z nullable: true + CreateCritDTO: + type: object + x-tags: + - federation + properties: + claim: + type: string + description: A critical claims that must be understood and processed. + required: + - claim SubordinateStatement: allOf: 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 f24a7e25..c07eb52d 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 @@ -16,6 +16,7 @@ class EntityConfigurationStatementBuilder { private lateinit var jwks: Array private var metadata: MutableMap = mutableMapOf() private val authorityHints: MutableList = mutableListOf() + private val crit: MutableList = mutableListOf() fun iss(iss: String) = apply { this.iss = iss } fun exp(exp: Int) = apply { this.exp = exp } @@ -30,6 +31,10 @@ class EntityConfigurationStatementBuilder { this.authorityHints.add(hint) } + fun crit(claim: String) = apply { + this.crit.add(claim) + } + @OptIn(ExperimentalSerializationApi::class) private fun createJwks(jwks: Array): JsonObject { val jsonArray: JsonArray = Json.encodeToJsonElement(ArraySerializer(JwkDTO.serializer()), jwks) as JsonArray @@ -47,7 +52,8 @@ class EntityConfigurationStatementBuilder { iat = iat ?: throw IllegalArgumentException("iat must be provided"), jwks = createJwks(jwks), metadata = JsonObject(metadata), - authorityHints = if (authorityHints.isNotEmpty()) authorityHints.toTypedArray() else null + authorityHints = if (authorityHints.isNotEmpty()) authorityHints.toTypedArray() else null, + crit = if (crit.isNotEmpty()) crit.toTypedArray() else null ) } } 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 46a335b2..092580d0 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 @@ -2,6 +2,7 @@ package com.sphereon.oid.fed.persistence import com.sphereon.oid.fed.persistence.models.AccountQueries import com.sphereon.oid.fed.persistence.models.AuthorityHintQueries +import com.sphereon.oid.fed.persistence.models.CritQueries import com.sphereon.oid.fed.persistence.models.EntityConfigurationMetadataQueries import com.sphereon.oid.fed.persistence.models.EntityConfigurationStatementQueries import com.sphereon.oid.fed.persistence.models.KeyQueries @@ -14,4 +15,5 @@ expect object Persistence { val subordinateQueries: SubordinateQueries val entityConfigurationMetadataQueries: EntityConfigurationMetadataQueries val authorityHintQueries: AuthorityHintQueries + val critQueries: CritQueries } diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/7.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/7.sqm new file mode 100644 index 00000000..3fb530e3 --- /dev/null +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/7.sqm @@ -0,0 +1,10 @@ +CREATE TABLE Crit ( + id SERIAL PRIMARY KEY, + account_id INT NOT NULL, + claim TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP, + CONSTRAINT FK_ParentCrit FOREIGN KEY (account_id) REFERENCES Account (id) +); + +CREATE INDEX crit_account_id_index ON Crit (account_id); diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Crit.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Crit.sq new file mode 100644 index 00000000..f9151f3f --- /dev/null +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Crit.sq @@ -0,0 +1,17 @@ +findByAccountId: +SELECT * FROM Crit WHERE account_id = ? AND deleted_at IS NULL; + +findByAccountIdAndClaim: +SELECT * FROM Crit WHERE account_id = ? AND claim = ? AND deleted_at IS NULL; + +deleteByAccountIdAndId: +UPDATE Crit SET deleted_at = CURRENT_TIMESTAMP WHERE account_id = ? AND id = ? AND deleted_at IS NULL RETURNING *; + +deleteById: +UPDATE Crit SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING *; + +create: +INSERT INTO Crit (account_id, claim) VALUES (?, ?) RETURNING *; + +findByAccountIdAndId: +SELECT * FROM Crit WHERE account_id = ? AND id = ? 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 b0079c18..b646fd6f 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 @@ -6,6 +6,7 @@ import app.cash.sqldelight.db.SqlDriver import com.sphereon.oid.fed.persistence.database.PlatformSqlDriver import com.sphereon.oid.fed.persistence.models.AccountQueries import com.sphereon.oid.fed.persistence.models.AuthorityHintQueries +import com.sphereon.oid.fed.persistence.models.CritQueries import com.sphereon.oid.fed.persistence.models.EntityConfigurationMetadataQueries import com.sphereon.oid.fed.persistence.models.EntityConfigurationStatementQueries import com.sphereon.oid.fed.persistence.models.KeyQueries @@ -18,6 +19,7 @@ actual object Persistence { actual val subordinateQueries: SubordinateQueries actual val entityConfigurationMetadataQueries: EntityConfigurationMetadataQueries actual val authorityHintQueries: AuthorityHintQueries + actual val critQueries: CritQueries init { val driver = getDriver() @@ -30,6 +32,7 @@ actual object Persistence { subordinateQueries = database.subordinateQueries entityConfigurationMetadataQueries = database.entityConfigurationMetadataQueries authorityHintQueries = database.authorityHintQueries + critQueries = database.critQueries } private fun getDriver(): SqlDriver { diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt index d4c04fe3..562f8116 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt @@ -14,5 +14,9 @@ class Constants { const val AUTHORITY_HINT_NOT_FOUND = "Authority hint not found" const val FAILED_TO_DELETE_AUTHORITY_HINT = "Failed to delete authority hint" const val AUTHORITY_HINT_ALREADY_EXISTS = "Authority hint already exists" + const val CRIT_ALREADY_EXISTS = "Crit already exists" + const val FAILED_TO_CREATE_CRIT = "Failed to create crit" + const val CRIT_NOT_FOUND = "Crit not found" + const val FAILED_TO_DELETE_CRIT = "Failed to delete crit" } } diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/CritService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/CritService.kt new file mode 100644 index 00000000..9adff89d --- /dev/null +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/CritService.kt @@ -0,0 +1,41 @@ +package com.sphereon.oid.fed.services + +import com.sphereon.oid.fed.persistence.Persistence +import com.sphereon.oid.fed.persistence.models.Crit + +class CritService { + + fun create(accountUsername: String, claim: String): Crit { + val account = Persistence.accountQueries.findByUsername(accountUsername).executeAsOneOrNull() + ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND) + + val critAlreadyExists = + Persistence.critQueries.findByAccountIdAndClaim(account.id, claim).executeAsOneOrNull() + + if (critAlreadyExists != null) { + throw IllegalArgumentException(Constants.CRIT_ALREADY_EXISTS) + } + + return Persistence.critQueries.create(account.id, claim).executeAsOneOrNull() + ?: throw IllegalStateException(Constants.FAILED_TO_CREATE_CRIT) + } + + fun delete(accountUsername: String, id: Int): Crit { + val account = Persistence.accountQueries.findByUsername(accountUsername).executeAsOneOrNull() + ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND) + + return Persistence.critQueries.deleteByAccountIdAndId(account.id, id).executeAsOneOrNull() + ?: throw IllegalStateException(Constants.FAILED_TO_DELETE_CRIT) + } + + fun findByAccountId(accountId: Int): Array { + return Persistence.critQueries.findByAccountId(accountId).executeAsList().toTypedArray() + } + + fun findByAccountUsername(accountUsername: String): Array { + val account = Persistence.accountQueries.findByUsername(accountUsername).executeAsOneOrNull() + ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND) + + return findByAccountId(account.id) + } +} 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 e88afe93..fee6eb9c 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 @@ -25,6 +25,7 @@ class EntityConfigurationStatementService { val hasSubordinates = subordinateQueries.findByAccountId(account.id).executeAsList().isNotEmpty() val authorityHints = 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 entityConfigurationStatement = EntityConfigurationStatementBuilder() @@ -56,6 +57,10 @@ class EntityConfigurationStatementService { ) } + crits.forEach { + entityConfigurationStatement.crit(it) + } + return entityConfigurationStatement.build() }