diff --git a/.env b/.env
index 34ab61a8..b5c1af1c 100644
--- a/.env
+++ b/.env
@@ -1,4 +1,5 @@
-DATASOURCE_URL=jdbc:postgresql://localhost:5432/openid-federation-db
-DATASOURCE_USER=openid-federation-db-user
-DATASOURCE_PASSWORD=openid-federation-db-password
-DATASOURCE_DB=openid-federation-db
\ No newline at end of file
+DATASOURCE_URL=jdbc:postgresql://localhost:5432/openid-federation-db
+DATASOURCE_USER=openid-federation-db-user
+DATASOURCE_PASSWORD=openid-federation-db-password
+DATASOURCE_DB=openid-federation-db
+APP_KEY=Nit5tWts42QeCynT1Q476LyStDeSd4xb
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 161fec79..0a8ce54c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ captures
/.temp/
/docker/.env
/.run/*
+kotlin-js-store/
\ No newline at end of file
diff --git a/README.md b/README.md
index 8b1fe329..feb4eedb 100644
--- a/README.md
+++ b/README.md
@@ -1,71 +1,94 @@
-
-
-
-
OpenID Federation Monorepo
-
-
-
-# Background
-
-OpenID Federation is a framework designed to facilitate the secure and interoperable interaction of entities within a federation. This involves the use of JSON Web Tokens (JWTs) to represent and convey necessary information for entities to participate in federations, ensuring trust and security across different organizations and systems.
-
-In the context of OpenID Federation, Entity Statements play a crucial role. These are signed JWTs that contain details about the entity, such as its public keys and metadata. This framework allows entities to assert their identity and capabilities in a standardized manner, enabling seamless integration and interoperability within federations.
-
-## Key Concepts
-
-- **Federation**: A group of organizations that agree to interoperate under a set of common rules defined in a federation policy.
-- **Entity Statements**: JSON objects that contain metadata about entities (IdPs, RPs) and their federation relationships.
-- **Trust Chains**: Mechanisms by which parties in a federation verify each other’s trustworthiness through a chain of entity statements, leading back to a trusted authority.
-- **Federation API**: Interfaces defined for entities to exchange information and perform operations necessary for federation management.
-
-## Core Components
-
-- **Federation Operator**: The central authority in a federation that manages policy and trust chain verification.
-- **Identity Providers (IdPs)**: Entities that authenticate users and provide identity assertions to relying parties.
-- **Relying Parties (RPs)**: Entities that rely on identity assertions provided by IdPs to offer services to users.
-
-## Technical Features
-
-- **JSON Web Tokens (JWT)**: Used for creating verifiable entity statements and security assertions.
-- **JSON Object Signing and Encryption (JOSE)**: Standards for signing and encrypting JSON-based objects to ensure their integrity and confidentiality.
-
-## Operational Model
-
-- **Dynamic Federation**: Allows entities to join or adjust their federation relationships dynamically, based on real-time verification of entity statements.
-- **Trust Model**: Establishes a model where trust is derived from known and verifiable sources and can be dynamically adjusted according to real-time interactions and policy evaluations.
-- **Conflict Resolution**: Defines how disputes or mismatches in federation policies among entities are resolved.
-
-# Data Structure
-
-## Entity Statement Overview
-
-### 1. Definition
-- An Entity Statement is a signed JWT containing information necessary for the Entity to participate in federations.
-- **Entity Configuration**: An Entity Statement about itself.
-- **Subordinate Statement**: An Entity Statement about an Immediate Subordinate Entity by a Superior Entity.
-
-### 2. Requirements and Structure
-- **Type**: JWT must be explicitly typed as `entity-statement+jwt`.
-- **Signature**: Signed using the issuer’s private key, preferably using ECDSA with P-256 and SHA-256 (ES256).
-- **Key ID (kid)**: The header must include the Key ID of the signing key.
-
-### 3. Claims in an Entity Statement
-- **iss (Issuer)**: Entity Identifier of the issuer.
-- **sub (Subject)**: Entity Identifier of the subject.
-- **iat (Issued At)**: Time the statement was issued.
-- **exp (Expiration Time)**: Time after which the statement is no longer valid.
-- **jwks (JSON Web Key Set)**: Public keys for verifying signatures. Required except in specific cases like Explicit Registration.
-- **authority_hints** (Optional): Identifiers of Intermediate Entities or Trust Anchors that may issue Subordinate Statements.
-- **metadata** (Optional): Represents the Entity’s Types and metadata.
-- **metadata_policy** (Optional): Defines a metadata policy, applicable to the subject and its Subordinates.
-- **constraints** (Optional): Defines Trust Chain constraints.
-- **crit** (Optional): Specifies critical claims that must be understood and processed.
-- **metadata_policy_crit** (Optional): Specifies critical metadata policy operators that must be understood and processed.
-- **trust_marks** (Optional): Array of JSON objects, each representing a Trust Mark.
-- **trust_mark_issuers** (Optional): Specifies trusted issuers of Trust Marks.
-- **trust_mark_owners** (Optional): Specifies ownership of Trust Marks by different Entities.
-- **source_endpoint** (Optional): URL to fetch the Entity Statement from the issuer.
-
-### 4. Usage and Flexibility
-- Entity Statements can include additional claims as required by applications and protocols.
-- Metadata in Subordinate Statements overrides that in the Entity’s own configuration.
+
+
+
+
OpenID Federation Monorepo
+
+
+
+# Background
+
+OpenID Federation is a framework designed to facilitate the secure and interoperable interaction of entities within a
+federation. This involves the use of JSON Web Tokens (JWTs) to represent and convey necessary information for entities
+to participate in federations, ensuring trust and security across different organizations and systems.
+
+In the context of OpenID Federation, Entity Statements play a crucial role. These are signed JWTs that contain details
+about the entity, such as its public keys and metadata. This framework allows entities to assert their identity and
+capabilities in a standardized manner, enabling seamless integration and interoperability within federations.
+
+## Key Concepts
+
+- **Federation**: A group of organizations that agree to interoperate under a set of common rules defined in a
+ federation policy.
+- **Entity Statements**: JSON objects that contain metadata about entities (IdPs, RPs) and their federation
+ relationships.
+- **Trust Chains**: Mechanisms by which parties in a federation verify each other’s trustworthiness through a chain of
+ entity statements, leading back to a trusted authority.
+- **Federation API**: Interfaces defined for entities to exchange information and perform operations necessary for
+ federation management.
+
+## Core Components
+
+- **Federation Operator**: The central authority in a federation that manages policy and trust chain verification.
+- **Identity Providers (IdPs)**: Entities that authenticate users and provide identity assertions to relying parties.
+- **Relying Parties (RPs)**: Entities that rely on identity assertions provided by IdPs to offer services to users.
+
+## Technical Features
+
+- **JSON Web Tokens (JWT)**: Used for creating verifiable entity statements and security assertions.
+- **JSON Object Signing and Encryption (JOSE)**: Standards for signing and encrypting JSON-based objects to ensure their
+ integrity and confidentiality.
+
+## Operational Model
+
+- **Dynamic Federation**: Allows entities to join or adjust their federation relationships dynamically, based on
+ real-time verification of entity statements.
+- **Trust Model**: Establishes a model where trust is derived from known and verifiable sources and can be dynamically
+ adjusted according to real-time interactions and policy evaluations.
+- **Conflict Resolution**: Defines how disputes or mismatches in federation policies among entities are resolved.
+
+# Local Key Management System - Important Notice
+
+Local Key Management Service is designed primarily for testing, development, and local experimentation
+purposes. **It is not intended for use in production environments** due to significant security and compliance risks.
+
+# Data Structure
+
+## Entity Statement Overview
+
+### 1. Definition
+
+- An Entity Statement is a signed JWT containing information necessary for the Entity to participate in federations.
+- **Entity Configuration**: An Entity Statement about itself.
+- **Subordinate Statement**: An Entity Statement about an Immediate Subordinate Entity by a Superior Entity.
+
+### 2. Requirements and Structure
+
+- **Type**: JWT must be explicitly typed as `entity-statement+jwt`.
+- **Signature**: Signed using the issuer’s private key, preferably using ECDSA with P-256 and SHA-256 (ES256).
+- **Key ID (kid)**: The header must include the Key ID of the signing key.
+
+### 3. Claims in an Entity Statement
+
+- **iss (Issuer)**: Entity Identifier of the issuer.
+- **sub (Subject)**: Entity Identifier of the subject.
+- **iat (Issued At)**: Time the statement was issued.
+- **exp (Expiration Time)**: Time after which the statement is no longer valid.
+- **jwks (JSON Web Key Set)**: Public keys for verifying signatures. Required except in specific cases like Explicit
+ Registration.
+- **authority_hints** (Optional): Identifiers of Intermediate Entities or Trust Anchors that may issue Subordinate
+ Statements.
+- **metadata** (Optional): Represents the Entity’s Types and metadata.
+- **metadata_policy** (Optional): Defines a metadata policy, applicable to the subject and its Subordinates.
+- **constraints** (Optional): Defines Trust Chain constraints.
+- **crit** (Optional): Specifies critical claims that must be understood and processed.
+- **metadata_policy_crit** (Optional): Specifies critical metadata policy operators that must be understood and
+ processed.
+- **trust_marks** (Optional): Array of JSON objects, each representing a Trust Mark.
+- **trust_mark_issuers** (Optional): Specifies trusted issuers of Trust Marks.
+- **trust_mark_owners** (Optional): Specifies ownership of Trust Marks by different Entities.
+- **source_endpoint** (Optional): URL to fetch the Entity Statement from the issuer.
+
+### 4. Usage and Flexibility
+
+- Entity Statements can include additional claims as required by applications and protocols.
+- Metadata in Subordinate Statements overrides that in the Entity’s own configuration.
diff --git a/docker-compose.yaml b/docker-compose.yaml
index af8db708..3a726859 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,5 +1,3 @@
-version: '3.9'
-
services:
db:
image: postgres:latest
diff --git a/modules/admin-server/build.gradle.kts b/modules/admin-server/build.gradle.kts
index 06152f33..b512a212 100644
--- a/modules/admin-server/build.gradle.kts
+++ b/modules/admin-server/build.gradle.kts
@@ -16,7 +16,10 @@ java {
}
dependencies {
+ api(projects.modules.openapi)
api(projects.modules.openidFederationCommon)
+ api(projects.modules.persistence)
+ api(projects.modules.services)
implementation(libs.springboot.actuator)
implementation(libs.springboot.web)
implementation(libs.springboot.data.jdbc)
@@ -42,4 +45,4 @@ tasks.withType {
events("started", "skipped", "passed", "failed")
showStandardStreams = true
}
-}
\ No newline at end of file
+}
diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt
index 654f00f7..019fd9c0 100644
--- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt
+++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt
@@ -7,5 +7,5 @@ import org.springframework.boot.runApplication
class Application
fun main(args: Array) {
- runApplication(*args)
-}
\ No newline at end of file
+ runApplication(*args)
+}
diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/AccountController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/AccountController.kt
new file mode 100644
index 00000000..5b5b6a9c
--- /dev/null
+++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/AccountController.kt
@@ -0,0 +1,22 @@
+package com.sphereon.oid.fed.server.admin.controllers
+
+import com.sphereon.oid.fed.openapi.models.AccountDTO
+import com.sphereon.oid.fed.openapi.models.CreateAccountDTO
+import com.sphereon.oid.fed.services.AccountService
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/accounts")
+class AccountController {
+ private val accountService = AccountService()
+
+ @GetMapping
+ fun getAccounts(): List {
+ return accountService.findAll()
+ }
+
+ @PostMapping
+ fun createAccount(@RequestBody account: CreateAccountDTO): AccountDTO {
+ return accountService.create(account)
+ }
+}
diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/KeyController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/KeyController.kt
new file mode 100644
index 00000000..f8e0e0f8
--- /dev/null
+++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/KeyController.kt
@@ -0,0 +1,33 @@
+package com.sphereon.oid.fed.server.admin.controllers
+
+import com.sphereon.oid.fed.openapi.models.JwkAdminDTO
+import com.sphereon.oid.fed.services.KeyService
+import com.sphereon.oid.fed.services.extensions.toJwkAdminDTO
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/accounts/{accountUsername}/keys")
+class KeyController {
+ private val keyService = KeyService()
+
+ @PostMapping
+ fun create(@PathVariable accountUsername: String): JwkAdminDTO {
+ val key = keyService.create(accountUsername)
+ return key.toJwkAdminDTO()
+ }
+
+ @GetMapping
+ fun getKeys(@PathVariable accountUsername: String): List {
+ val keys = keyService.getKeys(accountUsername)
+ return keys
+ }
+
+ @DeleteMapping("/{keyId}")
+ fun revokeKey(
+ @PathVariable accountUsername: String,
+ @PathVariable keyId: Int,
+ @RequestParam reason: String?
+ ): JwkAdminDTO {
+ return keyService.revokeKey(accountUsername, keyId, reason)
+ }
+}
\ No newline at end of file
diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/SubordinateController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/SubordinateController.kt
new file mode 100644
index 00000000..f11bbdff
--- /dev/null
+++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/SubordinateController.kt
@@ -0,0 +1,19 @@
+package com.sphereon.oid.fed.server.admin.controllers
+
+import com.sphereon.oid.fed.persistence.models.Subordinate
+import com.sphereon.oid.fed.services.SubordinateService
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/accounts/{accountUsername}/subordinates")
+class SubordinateController {
+ private val subordinateService = SubordinateService()
+
+ @GetMapping
+ fun getSubordinates(@PathVariable accountUsername: String): List {
+ return subordinateService.findSubordinatesByAccount(accountUsername)
+ }
+}
\ No newline at end of file
diff --git a/modules/admin-server/src/main/resources/application.properties b/modules/admin-server/src/main/resources/application.properties
index 683495f2..49841a4e 100644
--- a/modules/admin-server/src/main/resources/application.properties
+++ b/modules/admin-server/src/main/resources/application.properties
@@ -1,12 +1,9 @@
spring.config.import=optional:file:../../.env[.properties]
-
spring.application.name=OpenID Federation
-
spring.datasource.url=${DATASOURCE_URL}
spring.datasource.username=${DATASOURCE_USER}
spring.datasource.password=${DATASOURCE_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
-
# Mapping /actuator/health to /status
management.endpoints.web.base-path=/
management.endpoints.web.path-mapping.health=status
\ No newline at end of file
diff --git a/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/ApplicationTests.kt b/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/ApplicationTests.kt
index 3f3e34e3..811206b1 100644
--- a/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/ApplicationTests.kt
+++ b/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/ApplicationTests.kt
@@ -6,8 +6,8 @@ import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class ApplicationTests {
- @Test
- fun contextLoads() {
- }
+ @Test
+ fun contextLoads() {
+ }
}
diff --git a/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/DatabaseTest.kt b/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/DatabaseTest.kt
deleted file mode 100644
index 2c8b2b94..00000000
--- a/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/DatabaseTest.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.sphereon.oid.fed.server.admin
-
-import org.junit.jupiter.api.Test
-import org.testcontainers.containers.PostgreSQLContainer
-import org.testcontainers.junit.jupiter.Container
-import org.testcontainers.junit.jupiter.Testcontainers
-
-@Testcontainers
-class DatabaseTest {
-
- @Container
- val postgres: PostgreSQLContainer<*> = PostgreSQLContainer("postgres:14")
-
- @Test
- fun `test database connection`() {
- assert(postgres.isRunning)
- }
-}
\ No newline at end of file
diff --git a/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/StatusEndpointTest.kt b/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/StatusEndpointTest.kt
index e1014b98..290b50d5 100644
--- a/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/StatusEndpointTest.kt
+++ b/modules/admin-server/src/test/kotlin/com/sphereon/oid/fed/server/admin/StatusEndpointTest.kt
@@ -1,12 +1,13 @@
package com.sphereon.oid.fed.server.admin
import org.junit.jupiter.api.Test
+import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.servlet.MockMvc
-import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
diff --git a/modules/federation-server/README.md b/modules/federation-server/README.md
new file mode 100644
index 00000000..66db3d08
--- /dev/null
+++ b/modules/federation-server/README.md
@@ -0,0 +1,27 @@
+# Federation Server
+
+API
+
+```/status``` - To check health status
+
+
+
+Add environment file (.env) with following properties
+
+```
+DATASOURCE_USER=
+DATASOURCE_PASSWORD=
+DATASOURCE_URL=
+```
+
+To build
+
+```./gradlew :modules:federation-server:build```
+
+To run
+
+```./gradlew :modules:federation-server:bootRun```
+
+To run tests
+
+```./gradlew :modules:federation-server:test```
\ No newline at end of file
diff --git a/modules/federation-server/build.gradle.kts b/modules/federation-server/build.gradle.kts
new file mode 100644
index 00000000..f94127e2
--- /dev/null
+++ b/modules/federation-server/build.gradle.kts
@@ -0,0 +1,46 @@
+plugins {
+ alias(libs.plugins.springboot)
+ alias(libs.plugins.springDependencyManagement)
+ alias(libs.plugins.kotlinJvm)
+ alias(libs.plugins.kotlinPluginSpring)
+ application
+}
+
+group = "com.sphereon.oid.fed.server.federation"
+version = "0.0.1"
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+dependencies {
+ api(projects.modules.openapi)
+ api(projects.modules.openidFederationCommon)
+ api(projects.modules.persistence)
+ api(projects.modules.services)
+ implementation(libs.springboot.actuator)
+ implementation(libs.springboot.web)
+ implementation(libs.springboot.data.jdbc)
+ implementation(libs.kotlin.reflect)
+ testImplementation(libs.springboot.test)
+ testImplementation(libs.testcontainer.junit)
+ testImplementation(libs.springboot.testcontainer)
+ runtimeOnly(libs.springboot.devtools)
+}
+
+kotlin {
+ compilerOptions {
+ freeCompilerArgs.addAll("-Xjsr305=strict")
+ }
+}
+
+tasks.withType {
+ useJUnitPlatform()
+ testLogging {
+ setExceptionFormat("full")
+ events("started", "skipped", "passed", "failed")
+ showStandardStreams = true
+ }
+}
diff --git a/modules/federation-server/src/main/kotlin/com/sphereon/oid/fed/server/federation/Application.kt b/modules/federation-server/src/main/kotlin/com/sphereon/oid/fed/server/federation/Application.kt
new file mode 100644
index 00000000..c5ba0f8a
--- /dev/null
+++ b/modules/federation-server/src/main/kotlin/com/sphereon/oid/fed/server/federation/Application.kt
@@ -0,0 +1,11 @@
+package com.sphereon.oid.fed.server.federation
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+
+@SpringBootApplication
+class Application
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/modules/federation-server/src/main/kotlin/com/sphereon/oid/fed/server/federation/controllers/FederationController.kt b/modules/federation-server/src/main/kotlin/com/sphereon/oid/fed/server/federation/controllers/FederationController.kt
new file mode 100644
index 00000000..53943c2d
--- /dev/null
+++ b/modules/federation-server/src/main/kotlin/com/sphereon/oid/fed/server/federation/controllers/FederationController.kt
@@ -0,0 +1,38 @@
+package com.sphereon.oid.fed.server.federation.controllers
+
+import com.sphereon.oid.fed.services.SubordinateService
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping()
+class FederationController {
+ private val subordinateService = SubordinateService()
+
+ @GetMapping("/.well-known/openid-federation")
+ fun getRootEntityConfigurationStatement(): String {
+ throw NotImplementedError()
+ }
+
+ @GetMapping("/{username}/.well-known/openid-federation")
+ fun getAccountEntityConfigurationStatement(@PathVariable username: String): String {
+ throw NotImplementedError()
+ }
+
+ @GetMapping("/list")
+ fun getRootSubordinatesList(): List {
+ return subordinateService.findSubordinatesByAccountAsList("root")
+ }
+
+ @GetMapping("/{username}/list")
+ fun getSubordinatesList(@PathVariable username: String): List {
+ return subordinateService.findSubordinatesByAccountAsList(username)
+ }
+
+ @GetMapping("/fetch")
+ fun getSubordinateStatement(): List {
+ throw NotImplementedError()
+ }
+}
diff --git a/modules/federation-server/src/main/resources/application.properties b/modules/federation-server/src/main/resources/application.properties
new file mode 100644
index 00000000..523035b3
--- /dev/null
+++ b/modules/federation-server/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+spring.config.import=optional:file:../../.env[.properties]
+spring.application.name=OpenID Federation Server
+spring.datasource.url=${DATASOURCE_URL}
+spring.datasource.username=${DATASOURCE_USER}
+spring.datasource.password=${DATASOURCE_PASSWORD}
+# Mapping /actuator/health to /status
+management.endpoints.web.base-path=/
+management.endpoints.web.path-mapping.health=status
\ No newline at end of file
diff --git a/modules/federation-server/src/test/kotlin/com/sphereon/oid/fed/server/federation/ApplicationTests.kt b/modules/federation-server/src/test/kotlin/com/sphereon/oid/fed/server/federation/ApplicationTests.kt
new file mode 100644
index 00000000..25835bbb
--- /dev/null
+++ b/modules/federation-server/src/test/kotlin/com/sphereon/oid/fed/server/federation/ApplicationTests.kt
@@ -0,0 +1,12 @@
+package com.sphereon.oid.fed.server.federation
+
+import org.junit.jupiter.api.Test
+import org.springframework.boot.test.context.SpringBootTest
+
+@SpringBootTest
+class ApplicationTests {
+
+ @Test
+ fun contextLoads() {
+ }
+}
diff --git a/modules/federation-server/src/test/kotlin/com/sphereon/oid/fed/server/federation/StatusEndpointTest.kt b/modules/federation-server/src/test/kotlin/com/sphereon/oid/fed/server/federation/StatusEndpointTest.kt
new file mode 100644
index 00000000..8d79bb24
--- /dev/null
+++ b/modules/federation-server/src/test/kotlin/com/sphereon/oid/fed/server/federation/StatusEndpointTest.kt
@@ -0,0 +1,26 @@
+package com.sphereon.oid.fed.server.federation
+
+import org.junit.jupiter.api.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+class StatusEndpointTest {
+
+ @Autowired
+ private lateinit var mockMvc: MockMvc
+
+ @Test
+ fun testStatusEndpoint() {
+ mockMvc.perform(get("/status"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.status").value("UP"))
+ }
+}
\ No newline at end of file
diff --git a/modules/openapi/build.gradle.kts b/modules/openapi/build.gradle.kts
index 71a13855..cc7ca19b 100644
--- a/modules/openapi/build.gradle.kts
+++ b/modules/openapi/build.gradle.kts
@@ -35,7 +35,8 @@ kotlin {
filter { line: String ->
line.replace(
"kotlin.collections.Map",
- "kotlinx.serialization.json.JsonObject")
+ "kotlinx.serialization.json.JsonObject"
+ )
}
}
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 02476887..7e4ac1a8 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
@@ -28,116 +28,2112 @@ servers:
url: https://virtserver.swaggerhub.com/SphereonInt/OpenIDFederationAPI/1.0.0-d36
paths:
+ /status:
+ get:
+ tags:
+ - api
+ summary: Check node status
+ description: Check the status of the Federated Node.
+ responses:
+ '200':
+ description: Successful status check
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StatusResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
/entity-statement:
get:
tags:
- - federation
- summary: Fetch Entity Statement
- description: Fetch an Entity Statement for a specified issuer and optional subject.
+ - federation
+ summary: Fetch Entity Statement
+ description: Fetch an Entity Statement for a specified issuer and optional subject.
+ parameters:
+ - name: iss
+ in: query
+ description: The Entity Identifier of the issuer from which the Entity Statement is issued. Because of the normalization of the URL, multiple issuers MAY resolve to a shared fetch endpoint. This parameter makes it explicit exactly which issuer the Entity Statement must come from.
+ required: true
+ schema:
+ type: string
+ - name: sub
+ in: query
+ description: The Entity Identifier of the subject for which the Entity Statement is being requested. If this parameter is omitted, it is considered to be the same as the issuer and indicates a request for a self-signed Entity Configuration.
+ required: false
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful fetch of Entity Statement
+ content:
+ application/entity-statement+jwt:
+ schema:
+ $ref: '#/components/schemas/EntityConfigurationStatement'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Entity Statement not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Entity Statement not found example
+ value:
+ error: not_found
+ error_description: The requested Entity Statement could not be found for the provided issuer and subject.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /subordinates:
+ get:
+ tags:
+ - federation
+ summary: List Immediate Subordinates
+ description: List the Immediate Subordinates for the specified criteria.
+ parameters:
+ - name: entity_type
+ in: query
+ description: The value of this parameter is an Entity Type Identifier. If the responder knows the Entity Types of its Immediate Subordinates, the result MUST be filtered to include only those that include the specified Entity Type.
+ required: false
+ schema:
+ type: string
+ - name: trust_marked
+ in: query
+ description: If the parameter trust_marked is present and set to true, the result contains only the Immediate Subordinates for which at least one Trust Mark have been issued and is still valid.
+ required: false
+ schema:
+ type: boolean
+ - name: trust_mark_id
+ in: query
+ description: The value of this parameter is a Trust Mark identifier. If the responder has issued Trust Marks with the specified Trust Mark identifier, the list in the response is filtered to include only the Immediate Subordinates for which that Trust Mark identifier has been issued and is still valid.
+ required: false
+ schema:
+ type: string
+ - name: intermediate
+ in: query
+ description: If the parameter intermediate is present and set to true, then if the responder knows whether its Immediate Subordinates are Intermediates or not, the result MUST be filtered accordingly.
+ required: false
+ schema:
+ type: boolean
+ responses:
+ '200':
+ description: Successful fetch of Immediate Subordinates
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ format: uri
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /resolve-statement:
+ get:
+ tags:
+ - federation
+ summary: Resolve Entity Statement
+ description: Resolve metadata and Trust Marks for an Entity.
+ parameters:
+ - name: sub
+ in: query
+ description: The Entity Identifier of the Entity whose resolved data is requested.
+ required: true
+ schema:
+ type: string
+ - name: anchor
+ in: query
+ description: The Trust Anchor that the resolve endpoint MUST use when resolving the metadata. The value is an Entity identifier.
+ required: true
+ schema:
+ type: string
+ - name: type
+ in: query
+ description: A specific Entity Type to resolve. Its value is an Entity Type Identifier. If this parameter is not present, then all Entity Types are returned.
+ required: false
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful resolve of Entity metadata
+ content:
+ application/resolve-response+jwt:
+ schema:
+ $ref: '#/components/schemas/ResolveResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Entity not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Entity not found example
+ value:
+ error: not_found
+ error_description: The requested Entity could not be found for the provided parameters.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /trust-mark:
+ get:
+ tags:
+ - federation
+ summary: Get Trust Mark
+ description: Retrieve a specific Trust Mark.
+ parameters:
+ - name: trust_mark_id
+ in: query
+ description: Trust Mark identifier.
+ required: true
+ schema:
+ type: string
+ - name: sub
+ in: query
+ description: The Entity Identifier of the Entity to which the Trust Mark is issued.
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful retrieval of Trust Mark
+ content:
+ application/trust-mark+jwt:
+ schema:
+ type: string
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Trust Mark not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Trust Mark not found example
+ value:
+ error: not_found
+ error_description: The requested Trust Mark could not be found for the provided parameters.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /trust-mark/status:
+ post:
+ tags:
+ - federation
+ summary: Check Trust Mark Status
+ description: Check if a Trust Mark is still active.
+ requestBody:
+ content:
+ application/x-www-form-urlencoded:
+ schema:
+ type: object
+ properties:
+ sub:
+ type: string
+ description: The Entity Identifier of the Entity to which the Trust Mark was issued.
+ trust_mark_id:
+ type: string
+ description: Identifier of the Trust Mark.
+ iat:
+ type: integer
+ description: Time when the Trust Mark was issued. If iat is not specified and the Trust Mark issuer has issued several Trust Marks with the identifier specified in the request to the Entity identified by sub, the most recent one is assumed.
+ trust_mark:
+ type: string
+ description: The whole Trust Mark.
+ responses:
+ '200':
+ description: Trust Mark status
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ active:
+ type: boolean
+ description: Whether the Trust Mark is active or not.
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Trust Mark not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Trust Mark not found example
+ value:
+ error: not_found
+ error_description: The requested Trust Mark could not be found for the provided parameters.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /trust-marked-entities:
+ get:
+ tags:
+ - federation
+ summary: List Trust Marked Entities
+ description: List all Entities for which Trust Marks have been issued and are still valid.
+ parameters:
+ - name: trust_mark_id
+ in: query
+ description: Trust Mark identifier to filter by. If the responder has issued Trust Marks with the specified Trust Mark identifier, the list in the response is filtered to include only the Entities for which that Trust Mark identifier has been issued and is still valid.
+ required: true
+ schema:
+ type: string
+ - name: sub
+ in: query
+ description: The Entity Identifier of the Entity to which the Trust Mark was issued. The list obtained in the response MUST be filtered to only the Entity matching this value.
+ required: false
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful fetch of Trust Marked Entities
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ format: uri
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /historical-keys:
+ get:
+ tags:
+ - federation
+ summary: Get Historical Keys
+ description: Retrieve previously used keys for non-repudiation of statements.
+ responses:
+ '200':
+ description: Successful retrieval of historical keys
+ content:
+ application/jwk-set+jwt:
+ schema:
+ $ref: '#/components/schemas/FederationHistoricalKeysResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /{accountUsername}/entity-statement:
+ get:
+ tags:
+ - federation
+ summary: Fetch an Tenant Entity Statement
+ description: Fetch an Entity Statement for a specified issuer and optional subject.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ - name: iss
+ in: query
+ description: The Entity Identifier of the issuer from which the Entity Statement is issued. Because of the normalization of the URL, multiple issuers MAY resolve to a shared fetch endpoint. This parameter makes it explicit exactly which issuer the Entity Statement must come from.
+ required: true
+ schema:
+ type: string
+ - name: sub
+ in: query
+ description: The Entity Identifier of the subject for which the Entity Statement is being requested. If this parameter is omitted, it is considered to be the same as the issuer and indicates a request for a self-signed Entity Configuration.
+ required: false
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful fetch of Entity Statement
+ content:
+ application/entity-statement+jwt:
+ schema:
+ $ref: '#/components/schemas/EntityConfigurationStatement'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Entity Statement not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Entity Statement not found example
+ value:
+ error: not_found
+ error_description: The requested Entity Statement could not be found for the provided issuer and subject.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /{accountUsername}/subordinates:
+ get:
+ tags:
+ - federation
+ summary: List Tenant Immediate Subordinates
+ description: List the Immediate Subordinates for the specified criteria.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+
+ - name: entity_type
+ in: query
+ description: The value of this parameter is an Entity Type Identifier. If the responder knows the Entity Types of its Immediate Subordinates, the result MUST be filtered to include only those that include the specified Entity Type.
+ required: false
+ schema:
+ type: string
+ - name: trust_marked
+ in: query
+ description: If the parameter trust_marked is present and set to true, the result contains only the Immediate Subordinates for which at least one Trust Mark have been issued and is still valid.
+ required: false
+ schema:
+ type: boolean
+ - name: trust_mark_id
+ in: query
+ description: The value of this parameter is a Trust Mark identifier. If the responder has issued Trust Marks with the specified Trust Mark identifier, the list in the response is filtered to include only the Immediate Subordinates for which that Trust Mark identifier has been issued and is still valid.
+ required: false
+ schema:
+ type: string
+ - name: intermediate
+ in: query
+ description: If the parameter intermediate is present and set to true, then if the responder knows whether its Immediate Subordinates are Intermediates or not, the result MUST be filtered accordingly.
+ required: false
+ schema:
+ type: boolean
+ responses:
+ '200':
+ description: Successful fetch of Immediate Subordinates
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ format: uri
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /{accountUsername}/resolve-statement:
+ get:
+ tags:
+ - federation
+ summary: Resolve Tenant Entity Statement
+ description: Resolve metadata and Trust Marks for an Entity.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+
+ - name: sub
+ in: query
+ description: The Entity Identifier of the Entity whose resolved data is requested.
+ required: true
+ schema:
+ type: string
+ - name: anchor
+ in: query
+ description: The Trust Anchor that the resolve endpoint MUST use when resolving the metadata. The value is an Entity identifier.
+ required: true
+ schema:
+ type: string
+ - name: type
+ in: query
+ description: A specific Entity Type to resolve. Its value is an Entity Type Identifier. If this parameter is not present, then all Entity Types are returned.
+ required: false
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful resolve of Entity metadata
+ content:
+ application/resolve-statement-response+jwt:
+ schema:
+ $ref: '#/components/schemas/ResolveResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Entity not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Entity not found example
+ value:
+ error: not_found
+ error_description: The requested Entity could not be found for the provided parameters.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /{accountUsername}/trust-mark:
+ get:
+ tags:
+ - federation
+ summary: Get Tenant Trust Mark
+ description: Retrieve a specific Trust Mark.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+
+ - name: trust_mark_id
+ in: query
+ description: Trust Mark identifier.
+ required: true
+ schema:
+ type: string
+ - name: sub
+ in: query
+ description: The Entity Identifier of the Entity to which the Trust Mark is issued.
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful retrieval of Trust Mark
+ content:
+ application/trust-mark+jwt:
+ schema:
+ type: string
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Trust Mark not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Trust Mark not found example
+ value:
+ error: not_found
+ error_description: The requested Trust Mark could not be found for the provided parameters.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /{accountUsername}/trust-mark/status:
+ post:
+ tags:
+ - federation
+ summary: Check Tenant Trust Mark Status
+ description: Check if a Trust Mark is still active.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ requestBody:
+ content:
+ application/x-www-form-urlencoded:
+ schema:
+ type: object
+ properties:
+ sub:
+ type: string
+ description: The Entity Identifier of the Entity to which the Trust Mark was issued.
+ trust_mark_id:
+ type: string
+ description: Identifier of the Trust Mark.
+ iat:
+ type: integer
+ description: Time when the Trust Mark was issued. If iat is not specified and the Trust Mark issuer has issued several Trust Marks with the identifier specified in the request to the Entity identified by sub, the most recent one is assumed.
+ trust_mark:
+ type: string
+ description: The whole Trust Mark.
+ responses:
+ '200':
+ description: Trust Mark status
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ active:
+ type: boolean
+ description: Whether the Trust Mark is active or not.
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '404':
+ description: Trust Mark not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ notFound:
+ summary: Trust Mark not found example
+ value:
+ error: not_found
+ error_description: The requested Trust Mark could not be found for the provided parameters.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /{accountUsername}/trust-marked-entities:
+ get:
+ tags:
+ - federation
+ summary: List Tenant Trust Marked Entities
+ description: List all Entities for which Trust Marks have been issued and are still valid.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+
+ - name: trust_mark_id
+ in: query
+ description: Trust Mark identifier to filter by. If the responder has issued Trust Marks with the specified Trust Mark identifier, the list in the response is filtered to include only the Entities for which that Trust Mark identifier has been issued and is still valid.
+ required: true
+ schema:
+ type: string
+ - name: sub
+ in: query
+ description: The Entity Identifier of the Entity to which the Trust Mark was issued. The list obtained in the response MUST be filtered to only the Entity matching this value.
+ required: false
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Successful fetch of Trust Marked Entities
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ format: uri
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalidRequest:
+ summary: Invalid request example
+ value:
+ error: invalid_request
+ error_description: The request is incomplete or does not comply with current specifications.
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /{accountUsername}/historical-keys:
+ get:
+ tags:
+ - federation
+ summary: Get Tenant Historical Keys
+ description: Retrieve previously used keys for non-repudiation of statements.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ responses:
+ '200':
+ description: Successful retrieval of historical keys
+ content:
+ application/jwk-set+jwt:
+ schema:
+ $ref: '#/components/schemas/FederationHistoricalKeysResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /stats:
+ get:
+ tags:
+ - Superadmin
+ summary: Get system statistics
+ description: Retrieve system statistics including uptime, CPU usage, memory usage, and disk usage.
+ responses:
+ '200':
+ description: Successful retrieval of system statistics
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SystemStatsResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ serverError:
+ summary: Server error example
+ value:
+ error: server_error
+ error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
+ /audit:
+ get:
+ tags:
+ - Superadmin
+ summary: Get audit logs
+ description: Retrieve audit logs with optional filtering by start and end dates.
+ parameters:
+ - name: startDate
+ in: query
+ description: The start date for filtering audit logs.
+ required: false
+ schema:
+ type: string
+ format: date-time
+ - name: endDate
+ in: query
+ description: The end date for filtering audit logs.
+ required: false
+ schema:
+ type: string
+ format: date-time
+ responses:
+ '200':
+ description: Successful retrieval of audit logs
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/AuditLog'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /role:
+ get:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Retrieve all available roles
+ description: Retrieve a list of all available roles and their descriptions.
+ responses:
+ '200':
+ description: Successful retrieval of roles
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Role'
+
+ /scope:
+ get:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Retrieve all available scopes
+ description: Retrieve a list of all available scopes and their descriptions.
+ responses:
+ '200':
+ description: Successful retrieval of scopes
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Scope'
+
+ /me:
+ get:
+ tags:
+ - auth
+ summary: Get logged-in user details
+ description: Retrieve information about the logged-in user, including linked accounts, roles, and scopes per account.
+ responses:
+ '200':
+ description: Successful retrieval of logged-in user details
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserDetailsResponse'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account:
+ get:
+ tags:
+ - Superadmin
+ summary: List all accounts
+ description: Retrieve a list of all accounts.
+ responses:
+ '200':
+ description: Accounts retrieved successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AccountDTO'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ post:
+ tags:
+ - Superadmin
+ summary: Register a new tenant account
+ description: Endpoint for a superadmin to create a new account.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateAccountDTO'
+ responses:
+ '201':
+ description: Account created successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AccountDTO'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '409':
+ description: Conflict (e.g., slug already exists)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}:
+ delete:
+ tags:
+ - Superadmin
+ summary: Delete an account
+ description: Endpoint for a superadmin to delete an account.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account to be deleted.
+ responses:
+ '200':
+ description: Account deleted successfully
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: Account deleted successfully
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: Account not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/user:
+ post:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Add an user to an account
+ description: Endpoint to add an user to a specific account with a defined role.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AddUserToAccountRequest'
+ responses:
+ '201':
+ description: User added to account successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AddUserToAccountResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: Account or user not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '409':
+ description: Conflict (e.g., user already in account)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ get:
+ tags:
+ - Superadmin
+ - Account Admin
+ - Account User
+ summary: List users in an account
+ description: Endpoint to list all users in a specific account.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ responses:
+ '200':
+ description: Users retrieved successfully
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/User'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: Account not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/user/{userId}:
+ delete:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Remove an user from an account
+ description: Endpoint to remove an user from a specific account.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the user to be removed.
+ responses:
+ '200':
+ description: User removed from account successfully
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: User removed from account successfully
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: Account or user not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/user/{userId}/role:
+ post:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Add a role to an user
+ description: Endpoint to add a role to an user.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the user.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserRoleRequest'
+ responses:
+ '200':
+ description: Role added successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserRoleResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/user/{userId}/role/{roleId}:
+ delete:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Remove a role from an user
+ description: Endpoint to remove a role from an user.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the user.
+ - name: roleId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the role to be removed from the user.
+ responses:
+ '200':
+ description: Role removed successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserRoleResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/user/{userId}/scope:
+ post:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Add a scope to an user
+ description: Endpoint to add a scope to an user.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the user.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserScopeRequest'
+ responses:
+ '200':
+ description: Scope added successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserScopeResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/user/{userId}/scope/{scopeId}:
+ delete:
+ tags:
+ - Superadmin
+ - Account Admin
+ summary: Remove a scope from an user
+ description: Endpoint to remove a scope from an user.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ - name: userId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the user.
+ - name: scopeId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the scope to be removed from the user.
+ responses:
+ '200':
+ description: Scope removed successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateUserScopeResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '403':
+ description: Forbidden
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/statement:
+ post:
+ tags:
+ - Account Admin
+ - Account User
+ summary: Create an Entity Configuration Statement for the specified account
+ description: Create an Entity Configuration Statement for the specified account. If `dry-run` is true, it will return the generated entity statement without persisting it.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the account.
+ requestBody:
+ description: Entity Statement data
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateEntityStatementRequest'
+ responses:
+ '200':
+ description: Entity Statement generated successfully (dry-run)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EntityConfigurationStatement'
+ '201':
+ description: Entity Statement created successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EntityConfigurationStatement'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/subordinate-statement:
+ post:
+ tags:
+ - Account Admin
+ - Account User
+ summary: Create a new Subordinate Statement
+ description: Create a new Subordinate Statement. If `dry-run` is true, it will return the generated entity statement without persisting it.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ - name: dry-run
+ in: query
+ required: false
+ schema:
+ type: boolean
+ description: If true, the statement will be generated but not persisted.
+ requestBody:
+ description: Entity Statement data
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateEntityStatementRequest'
+ responses:
+ '200':
+ description: Subordinate Statement dry-run successful
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SubordinateStatement'
+ '201':
+ description: Subordinate Statement created successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SubordinateStatement'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ get:
+ tags:
+ - Account Admin
+ - Account User
+ summary: List Subordinate Statements
+ description: List all active Subordinate Statements for the specified account.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ responses:
+ '200':
+ description: Successful fetch of Subordinate Statements
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/SubordinateStatement'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/subordinate-statement/{statementId}:
+ delete:
+ tags:
+ - Account Admin
+ summary: Delete a Subordinate Statement
+ description: Delete an existing Subordinate Statement and move it to historical data.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ - name: statementId
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The ID of the Subordinate Statement to be deleted.
+ responses:
+ '200':
+ description: Subordinate Statement deleted successfully
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: Subordinate Statement deleted successfully
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '404':
+ description: Subordinate Statement not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/trust-mark:
+ post:
+ tags:
+ - Account Admin
+ summary: Create or Update a Trust Mark
+ description: Create or update a Trust Mark for the specified account.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ dry_run: # TO-DO Add correct required attributes
+ type: boolean
+ description: If true, the entity statement will be generated but not persisted.
+ default: false
+ responses:
+ '200':
+ description: Trust Mark dry-run successful
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ trustMarkId:
+ type: string
+ description: The identifier of the created or updated Trust Mark.
+ '201':
+ description: Trust Mark created or updated successfully
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ trustMarkId:
+ type: string
+ description: The identifier of the created or updated Trust Mark.
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ get:
+ tags:
+ - Account Admin
+ - Account User
+ summary: List Trust Marks
+ description: List all Trust Marks for the specified account.
parameters:
- - name: iss
- in: query
- description: The Entity Identifier of the issuer from which the Entity Statement is issued. Because of the normalization of the URL, multiple issuers MAY resolve to a shared fetch endpoint. This parameter makes it explicit exactly which issuer the Entity Statement must come from.
+ - name: accountUsername
+ in: path
required: true
schema:
type: string
- - name: sub
- in: query
- description: The Entity Identifier of the subject for which the Entity Statement is being requested. If this parameter is omitted, it is considered to be the same as the issuer and indicates a request for a self-signed Entity Configuration.
- required: false
+ description: The username of the tenant account.
+ responses:
+ '200':
+ description: Successful fetch of Trust Marks
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ trustMarkId:
+ type: string
+ description: The identifier of the Trust Mark.
+ trustMark:
+ type: string
+ description: The JWT of the Trust Mark.
+ entityId:
+ type: string
+ description: The Entity Identifier of the entity to which the Trust Mark is issued.
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ '500':
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+
+ /account/{accountUsername}/trust-mark/{trustMarkId}:
+ delete:
+ tags:
+ - Account Admin
+ summary: Delete a Trust Mark
+ description: Delete an existing Trust Mark for the specified account.
+ parameters:
+ - name: accountUsername
+ in: path
+ required: true
+ schema:
+ type: string
+ description: The username of the tenant account.
+ - name: trustMarkId
+ in: path
+ required: true
schema:
type: string
+ description: The identifier of the Trust Mark to be deleted.
responses:
'200':
- description: Successful fetch of Entity Statement
+ description: Trust Mark deleted successfully
content:
- application/entity-statement+jwt:
+ application/json:
schema:
- $ref: '#/components/schemas/EntityStatement'
+ type: object
+ properties:
+ message:
+ type: string
+ example: Trust Mark deleted successfully
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
- examples:
- invalidRequest:
- summary: Invalid request example
- value:
- error: invalid_request
- error_description: The request is incomplete or does not comply with current specifications.
'404':
- description: Entity Statement not found
+ description: Trust Mark not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
- examples:
- notFound:
- summary: Entity Statement not found example
- value:
- error: not_found
- error_description: The requested Entity Statement could not be found for the provided issuer and subject.
'500':
description: Server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
- examples:
- serverError:
- summary: Server error example
- value:
- error: server_error
- error_description: The server encountered an unexpected condition that prevented it from fulfilling the request.
+
components:
schemas:
- JWK:
+ JwkDTO:
type: object
x-tags:
- federation
properties:
kty:
type: string
- description: The "kty" (key type) parameter identifies the cryptographic algorithm family used with the key, such as "RSA" or "EC".
+ description: The key type (e.g., EC, RSA).
example: RSA
+ crv:
+ type: string
+ description: The elliptic curve used (only for EC keys).
+ example: P-256
+ nullable: true
+ kid:
+ type: string
+ description: The key ID (optional).
+ example: 12345
+ nullable: true
+ x:
+ type: string
+ description: The X coordinate for EC keys (optional).
+ example: o-7zraXKDaoBte2PsuTXo-MSLzsyWdAElNptGgI4aH8
+ nullable: true
+ y:
+ type: string
+ description: The Y coordinate for EC keys (optional).
+ example: Xr_wCzJ1XnsgAIV5qHruzSwaNnwy87UjmevVklTpIv8
+ nullable: true
+ n:
+ type: string
+ description: The modulus for RSA keys.
+ example: modulus_value
+ nullable: true
+ e:
+ type: string
+ description: The exponent for RSA keys.
+ example: AQAB
+ nullable: true
+ alg:
+ type: string
+ description: The algorithm associated with the key.
+ example: ES256
+ nullable: true
use:
type: string
- description: The "use" (public key use) parameter identifies the intended use of the public key.
+ description: The intended use of the key (e.g., sig, enc).
example: sig
- key_ops:
+ nullable: true
+ x5u:
type: string
- description: The "key_ops" (key operations) parameter identifies the operation(s) for which the key is intended to be used.
- example: encrypt
- alg:
+ format: uri
+ description: A URL that points to an X.509 public key certificate or certificate chain.
+ example: https://example.com/cert.pem
+ nullable: true
+ x5c:
+ type: array
+ items:
+ type: string
+ description: A base64-encoded string representing an X.509 certificate.
+ example: MIICoTCCAYkCAQ...
+ description: The X.509 certificate chain.
+ nullable: true
+ x5t:
+ type: string
+ description: The SHA-1 thumbprint of the X.509 certificate.
+ example: dGhpcyBpcyBqdXN0IGEgdGh1bWJwcmludA
+ nullable: true
+ x5tS256: # Renamed to comply with OpenAPI restrictions
+ type: string
+ description: The SHA-256 thumbprint of the X.509 certificate.
+ example: sM4KhEI1Y2Sb6-EVr6tJabmJuoP-ZE...
+ nullable: true
+ revoked:
+ $ref: '#/components/schemas/JWTRevoked'
+
+ Jwk:
+ type: object
+ x-tags:
+ - federation
+ required:
+ - kty
+ properties:
+ kty:
+ type: string
+ description: The key type (e.g., EC, RSA).
+ example: RSA
+ crv:
type: string
- description: The "alg" (algorithm) parameter identifies the algorithm intended for use with the key.
- example: RS256
+ description: The elliptic curve used (only for EC keys).
+ example: P-256
+ nullable: true
kid:
type: string
- description: The "kid" (key ID) parameter is used to match a specific key.
+ description: The key ID (optional).
+ example: 12345
+ nullable: true
+ x:
+ type: string
+ description: The X coordinate for EC keys (optional).
+ example: o-7zraXKDaoBte2PsuTXo-MSLzsyWdAElNptGgI4aH8
+ nullable: true
+ y:
+ type: string
+ description: The Y coordinate for EC keys (optional).
+ example: Xr_wCzJ1XnsgAIV5qHruzSwaNnwy87UjmevVklTpIv8
+ nullable: true
+ n:
+ type: string
+ description: The modulus for RSA keys.
+ example: modulus_value
+ nullable: true
+ e:
+ type: string
+ description: The exponent for RSA keys.
+ example: AQAB
+ nullable: true
+ alg:
+ type: string
+ description: The algorithm associated with the key.
+ example: ES256
+ use:
+ type: string
+ description: The intended use of the key (e.g., sig, enc).
+ example: sig
+ nullable: true
+ x5u:
+ type: string
+ format: uri
+ description: A URL that points to an X.509 public key certificate or certificate chain.
+ example: https://example.com/cert.pem
+ nullable: true
+ x5c:
+ type: array
+ items:
+ type: string
+ description: A base64-encoded string representing an X.509 certificate.
+ example: MIICoTCCAYkCAQ...
+ description: The X.509 certificate chain.
+ nullable: true
+ x5t:
+ type: string
+ description: The SHA-1 thumbprint of the X.509 certificate.
+ example: dGhpcyBpcyBqdXN0IGEgdGh1bWJwcmludA
+ nullable: true
+ x5tS256:
+ type: string
+ description: The SHA-256 thumbprint of the X.509 certificate.
+ example: sM4KhEI1Y2Sb6-EVr6tJabmJuoP-ZE...
+ nullable: true
+ d:
+ type: string
+ description: The private key value (for RSA and EC keys).
+ example: base64url_encoded_private_key
+ nullable: true
+ p:
+ type: string
+ description: The first prime factor (for RSA private key).
+ example: base64url_encoded_p
+ nullable: true
+ q:
+ type: string
+ description: The second prime factor (for RSA private key).
+ example: base64url_encoded_q
+ nullable: true
+ dp:
+ type: string
+ description: The first factor CRT exponent (for RSA private key).
+ example: base64url_encoded_dp
+ nullable: true
+ dq:
+ type: string
+ description: The second factor CRT exponent (for RSA private key).
+ example: base64url_encoded_dq
+ nullable: true
+ qi:
+ type: string
+ description: The first CRT coefficient (for RSA private key).
+ example: base64url_encoded_qi
+ nullable: true
+
+ JwkAdminDTO:
+ type: object
+ x-tags:
+ - federation
+ properties:
+ id:
+ type: integer
+ description: The unique identifier for the JWK record.
example: 1
+ uuid:
+ type: string
+ format: uuid
+ description: The universally unique identifier for the JWK record.
+ example: 123e4567-e89b-12d3-a456-426614174000
+ account_id:
+ type: integer
+ description: The ID of the account associated with this JWK.
+ example: 100
+ kty:
+ type: string
+ description: The key type (e.g., EC, RSA).
+ example: RSA
+ crv:
+ type: string
+ description: The elliptic curve used (only for EC keys).
+ example: P-256
+ nullable: true
+ kid:
+ type: string
+ description: The key ID (optional).
+ example: 12345
+ nullable: true
+ x:
+ type: string
+ description: The X coordinate for EC keys (optional).
+ example: o-7zraXKDaoBte2PsuTXo-MSLzsyWdAElNptGgI4aH8
+ nullable: true
+ y:
+ type: string
+ description: The Y coordinate for EC keys (optional).
+ example: Xr_wCzJ1XnsgAIV5qHruzSwaNnwy87UjmevVklTpIv8
+ nullable: true
+ n:
+ type: string
+ description: The modulus for RSA keys.
+ example: modulus_value
+ nullable: true
+ e:
+ type: string
+ description: The exponent for RSA keys.
+ example: AQAB
+ nullable: true
+ alg:
+ type: string
+ description: The algorithm associated with the key.
+ example: ES256
+ nullable: true
+ use:
+ type: string
+ description: The intended use of the key (e.g., sig, enc).
+ example: sig
+ nullable: true
x5u:
type: string
- description: The "x5u" (X.509 URL) parameter is a URI that refers to a resource for an X.509 public key certificate or certificate chain.
+ format: uri
+ description: A URL that points to an X.509 public key certificate or certificate chain.
example: https://example.com/cert.pem
+ nullable: true
x5c:
type: array
- description: The "x5c" (X.509 certificate chain) parameter contains a chain of one or more PKIX certificates.
items:
type: string
- example:
- - MIIDQzCCA...+3whvMF1XEt0K2bA8wpPmSTPgQ==
+ description: A base64-encoded string representing an X.509 certificate.
+ example: MIICoTCCAYkCAQ...
+ description: The X.509 certificate chain.
+ nullable: true
x5t:
type: string
- description: The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a base64url-encoded SHA-1 thumbprint of the DER encoding of an X.509 certificate.
- example: 0fVuYF8jJ3onI+9Zk2/Iy+Oh5ZpE
+ description: The SHA-1 thumbprint of the X.509 certificate.
+ example: dGhpcyBpcyBqdXN0IGEgdGh1bWJwcmludA
+ nullable: true
x5t#S256:
type: string
- description: The "x5t#S256" (X.509 certificate SHA-256 thumbprint) parameter is a base64url-encoded SHA-256 thumbprint of the DER encoding of an X.509 certificate.
- example: 1MvI4/VhnEzTz7Jo/0Q/d/jI3rE7IMoMT34wvAjyLvs
- revoked:
- $ref: '#/components/schemas/JWTRevoked'
+ description: The SHA-256 thumbprint of the X.509 certificate.
+ example: sM4KhEI1Y2Sb6-EVr6tJabmJuoP-ZE...
+ nullable: true
+ revoked_at:
+ type: string
+ format: date-time
+ description: The timestamp when the JWK was revoked, if applicable.
+ example: 2024-09-01T12:34:56Z
+ nullable: true
+ revoked_reason:
+ type: string
+ description: The reason for revoking the JWK, if applicable.
+ example: Key compromise
+ nullable: true
+ created_at:
+ type: string
+ format: date-time
+ description: The timestamp when the JWK was created.
+ example: 2024-08-06T12:34:56Z
+ nullable: true
+
JWTRevoked:
type: object
@@ -148,6 +2144,7 @@ components:
properties:
revoked_at:
type: string
+ format: date-time
reason:
type: string
@@ -159,7 +2156,7 @@ components:
keys:
type: array
items:
- $ref: '#/components/schemas/JWK'
+ $ref: '#/components/schemas/JwkDTO'
JWTHeader:
type: object
@@ -192,10 +2189,16 @@ components:
type: string
description: The encoded JWT signature value.
- EntityStatement:
+ BaseEntityStatement:
type: object
x-tags:
- federation
+ required:
+ - iss
+ - sub
+ - iat
+ - exp
+ - jwks
properties:
iss:
type: string
@@ -208,33 +2211,69 @@ components:
description: Expiration time after which the statement MUST NOT be accepted for processing.
iat:
type: integer
+ format: date-time
description: The time the statement was issued.
jwks:
$ref: '#/components/schemas/JWKS'
- authority_hints:
- type: array
- items:
- type: string
- description: An array of strings representing the Entity Identifiers of Intermediate Entities or Trust Anchors
metadata:
$ref: '#/components/schemas/Metadata'
- constraints:
- $ref: '#/components/schemas/Constraint'
crit:
type: array
- description: Extension of the JOSE header parameters that MUST be understood and processed.
items:
type: string
- description: Claim names present in the JWT that use those extensions
- source_endpoint:
- type: string
- format: uri
- description: String containing the fetch endpoint URL from which the Entity Statement was issued.
- additionalProperties:
- type: object
- additionalProperties: true
- example:
- "jti": "7l2lncFdY6SlhNia"
+
+ EntityConfigurationStatement:
+ allOf:
+ - $ref: '#/components/schemas/BaseEntityStatement'
+ - type: object
+ properties:
+ authority_hints:
+ type: array
+ items:
+ type: string
+ metadata:
+ $ref: '#/components/schemas/Metadata'
+ crit:
+ type: array
+ items:
+ type: string
+ trust_marks:
+ type: array
+ description: An array of JSON objects, each representing a Trust Mark.
+ items:
+ $ref: '#/components/schemas/TrustMark'
+ trust_mark_issuers:
+ $ref: '#/components/schemas/TrustMarkIssuers'
+ trust_mark_owners:
+ $ref: '#/components/schemas/TrustMarkOwners'
+
+ SubordinateStatement:
+ allOf:
+ - $ref: '#/components/schemas/BaseEntityStatement'
+ - type: object
+ required:
+ - iss
+ - sub
+ - iat
+ - exp
+ - jwks
+ properties:
+ metadata_policy:
+ $ref: '#/components/schemas/MetadataPolicy'
+ constraints:
+ $ref: '#/components/schemas/Constraint'
+ crit:
+ type: array
+ items:
+ type: string
+ metadata_policy_crit:
+ type: array
+ items:
+ type: string
+ source_endpoint:
+ type: string
+ format: uri
+ description: String containing the fetch endpoint URL from which the Entity Subordinate Statement was issued.
Metadata:
type: object
@@ -254,6 +2293,91 @@ components:
oauth_resource:
$ref: '#/components/schemas/OAuthProtectedResourceMetadata'
+ MetadataPolicy:
+ type: object
+ x-tags:
+ - federation
+ properties:
+ federation_entity:
+ $ref: '#/components/schemas/MetadataParameterPolicy'
+ openid_relying_party:
+ $ref: '#/components/schemas/MetadataParameterPolicy'
+ openid_provider:
+ $ref: '#/components/schemas/MetadataParameterPolicy'
+ oauth_authorization_server:
+ $ref: '#/components/schemas/MetadataParameterPolicy'
+ oauth_client:
+ $ref: '#/components/schemas/MetadataParameterPolicy'
+ oauth_resource:
+ $ref: '#/components/schemas/MetadataParameterPolicy'
+
+ MetadataParameterPolicy:
+ type: object
+ x-tags:
+ - federation
+ properties:
+ additionalProperties:
+ type: object
+ additionalProperties: true
+
+ TrustMark:
+ type: object
+ x-tags:
+ - federation
+ properties:
+ id:
+ type: string
+ description: The Trust Mark identifier. It MUST be the same value as the id claim contained in the Trust Mark JWT.
+ example: "example-trust-mark-id"
+ trust_mark:
+ type: string
+ description: A signed JSON Web Token that represents a Trust Mark.
+ example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
+
+ TrustMarkIssuers:
+ type: object
+ x-tags:
+ - federation
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ example:
+ "https://openid.net/certification/op": [ ]
+ "https://refeds.org/wp-content/uploads/2016/01/Sirtfi-1.0.pdf":
+ - "https://swamid.se"
+
+ TrustMarkOwners:
+ type: object
+ x-tags:
+ - federation
+ additionalProperties:
+ $ref: '#/components/schemas/TrustMarkOwner'
+ example:
+ "https://refeds.org/wp-content/uploads/2016/01/Sirtfi-1.0.pdf":
+ sub: "https://refeds.org/sirtfi"
+ jwks:
+ keys:
+ - alg: "RS256"
+ e: "AQAB"
+ kid: "key1"
+ kty: "RSA"
+ n: "pnXBOusEANuug6ewezb9J_..."
+ use: "sig"
+
+ TrustMarkOwner:
+ type: object
+ x-tags:
+ - federation
+ properties:
+ sub:
+ type: string
+ description: Identifier of the Trust Mark owner
+ jwks:
+ $ref: '#/components/schemas/JWKS'
+ additionalProperties:
+ type: string
+
NamingConstraints:
type: object
x-tags:
@@ -440,37 +2564,37 @@ components:
items:
type: string
description: JSON array containing a list of the OAuth 2.0 "scope" values that this authorization server supports.
- example: ["openid", "profile", "email"]
+ example: [ "openid", "profile", "email" ]
response_types_supported:
type: array
items:
type: string
description: JSON array containing a list of the OAuth 2.0 "response_type" values that this authorization server supports.
- example: ["code", "token", "id_token"]
+ example: [ "code", "token", "id_token" ]
response_modes_supported:
type: array
items:
type: string
description: JSON array containing a list of the OAuth 2.0 "response_mode" values that this authorization server supports.
- example: ["query", "fragment", "form_post"]
+ example: [ "query", "fragment", "form_post" ]
grant_types_supported:
type: array
items:
type: string
description: JSON array containing a list of the OAuth 2.0 grant type values that this authorization server supports.
- example: ["authorization_code", "implicit", "client_credentials", "refresh_token"]
+ example: [ "authorization_code", "implicit", "client_credentials", "refresh_token" ]
token_endpoint_auth_methods_supported:
type: array
items:
type: string
description: JSON array containing a list of client authentication methods supported by this token endpoint.
- example: ["client_secret_basic", "private_key_jwt"]
+ example: [ "client_secret_basic", "private_key_jwt" ]
token_endpoint_auth_signing_alg_values_supported:
type: array
items:
type: string
description: JSON array containing a list of the JWS signing algorithms supported by the token endpoint for the signature on the JWT used to authenticate the client.
- example: ["RS256", "ES256"]
+ example: [ "RS256", "ES256" ]
service_documentation:
type: string
description: URL of a page containing human-readable information that developers might want or need to know when using the authorization server.
@@ -480,7 +2604,7 @@ components:
items:
type: string
description: Languages and scripts supported for the user interface, represented as a JSON array of language tag values from BCP 47.
- example: ["en-US", "fr-FR"]
+ example: [ "en-US", "fr-FR" ]
op_policy_uri:
type: string
description: URL that the authorization server provides to the person registering the client to read about the authorization server's requirements on how the client can use the data provided by the authorization server.
@@ -498,13 +2622,13 @@ components:
items:
type: string
description: JSON array containing a list of client authentication methods supported by this revocation endpoint.
- example: ["client_secret_basic", "private_key_jwt"]
+ example: [ "client_secret_basic", "private_key_jwt" ]
revocation_endpoint_auth_signing_alg_values_supported:
type: array
items:
type: string
description: JSON array containing a list of the JWS signing algorithms supported by the revocation endpoint for the signature on the JWT used to authenticate the client.
- example: ["RS256", "ES256"]
+ example: [ "RS256", "ES256" ]
introspection_endpoint:
type: string
description: URL of the authorization server's OAuth 2.0 introspection endpoint.
@@ -514,19 +2638,19 @@ components:
items:
type: string
description: JSON array containing a list of client authentication methods supported by this introspection endpoint.
- example: ["client_secret_basic", "private_key_jwt"]
+ example: [ "client_secret_basic", "private_key_jwt" ]
introspection_endpoint_auth_signing_alg_values_supported:
type: array
items:
type: string
description: JSON array containing a list of the JWS signing algorithms supported by the introspection endpoint for the signature on the JWT used to authenticate the client.
- example: ["RS256", "ES256"]
+ example: [ "RS256", "ES256" ]
code_challenge_methods_supported:
type: array
items:
type: string
description: JSON array containing a list of Proof Key for Code Exchange (PKCE) code challenge methods supported by this authorization server.
- example: ["plain", "S256"]
+ example: [ "plain", "S256" ]
OAuthClientMetadata:
allOf:
@@ -537,67 +2661,140 @@ components:
x-tags:
- federation
- OAuthDynamicClientMetadata:
- type:
- object
+ OAuthProtectedResourceMetadata:
+ allOf:
+ - $ref: '#/components/schemas/CommonMetadata'
+ - $ref: '#/components/schemas/ProtectedResourceMetadata'
+ type: object
+ x-tags:
+ - federation
+
+ ProtectedResourceMetadata:
+ type: object
x-tags:
- federation
properties:
- redirect_uris:
+ resource:
+ type: string
+ format: uri
+ description: URL identifier of the protected resource using the https scheme.
+ authorization_servers:
type: array
items:
type: string
- format: uri
- description: Array of redirection URI strings for redirect-based flows.
- token_endpoint_auth_method:
- $ref: '#/components/schemas/OAuthDynamicClientTokenEndpointAuthMethod'
- grant_types:
+ description: JSON array of OAuth authorization server issuer identifiers for servers that can be used with this protected resource.
+ jwks_uri:
+ type: string
+ format: uri
+ description: URL of the protected resource's JWK Set document, containing its public keys.
+ scopes_supported:
type: array
items:
- $ref: '#/components/schemas/OAuthDynamicClientGrantTypes'
- response_types:
+ type: string
+ description: JSON array of OAuth 2.0 scope values used in authorization requests to access this protected resource.
+ bearer_methods_supported:
type: array
items:
- $ref: '#/components/schemas/OAuthDynamicClientResponseTypes'
- client_name:
+ type: string
+ description: JSON array of supported methods for sending an OAuth 2.0 Bearer Token to the protected resource. Values are ["header", "body", "query"].
+ resource_signing_alg_values_supported:
+ type: array
+ items:
+ type: string
+ description: JSON array of JWS signing algorithms supported by the protected resource for signing responses.
+ resource_documentation:
type: string
- description: Human-readable string name of the client to be presented to the end-user during authorization.
- client_uri:
+ format: uri
+ description: URL of a page with human-readable information for developers using the protected resource.
+ resource_policy_uri:
type: string
format: uri
- description: URL string of a web page providing information about the client.
- logo_uri:
+ description: URL to the protected resource's policy document.
+ resource_tos_uri:
type: string
format: uri
- description: URL string that references a logo for the client.
- scope:
+ description: URL to the protected resource's terms of service.
+
+ CommonMetadata:
+ type: object
+ x-tags:
+ - federation
+ properties:
+ organization_name:
type: string
- description: Space-separated list of scope values the client can use when requesting access tokens.
+ description: A human-readable name representing the organization owning this Entity. If the owner is a physical person, this MAY be, for example, the person's name. Note that this information will be publicly available.
contacts:
type: array
items:
type: string
- description: Array of strings representing ways to contact people responsible for this client, typically email addresses.
- tos_uri:
+ description: JSON array with one or more strings representing contact persons at the Entity. These MAY contain names, e-mail addresses, descriptions, phone numbers, etc.
+ logo_uri:
type: string
format: uri
- description: URL string that points to a human-readable terms of service document for the client.
+ description: A URL that points to the logo of this Entity. The file containing the logo SHOULD be published in a format that can be viewed via the web.
policy_uri:
type: string
format: uri
- description: URL string that points to a human-readable privacy policy document.
- jwks_uri:
+ description: URL of the documentation of conditions and policies relevant to this Entity.
+ homepage_uri:
type: string
format: uri
- description: URL string referencing the client’s JSON Web Key (JWK) Set document, which contains the client’s public keys.
- jwks:
- $ref: '#/components/schemas/JWKS'
- software_id:
- type: string
- description: Unique identifier string for the client software to be dynamically registered.
- software_version:
+ description: URL of a Web page for the organization owning this Entity.
+
+ ErrorType:
+ type: string
+ x-tags:
+ - federation
+ description: One of the predefined error codes.
+ example: invalid_request
+ enum:
+ - invalid_request
+ - invalid_client
+ - invalid_issuer
+ - not_found
+ - server_error
+ - temporary_unavailable
+ - unsupported_parameter
+ - invalid_token
+ - insufficient_scope
+ - unsupported_token_type
+ - interaction_required
+ - login_required
+ - account_selection_required
+ - consent_required
+ - invalid_request_uri
+ - invalid_request_object
+ - request_not_supported
+ - request_uri_not_supported
+ - registration_not_supported
+ - need_info
+ - request_denied
+ - request_submitted
+ - authorization_pending
+ - access_denied
+ - slow_down
+ - expired_token
+ - invalid_target
+ - unsupported_pop_key
+ - incompatible_ace_profiles
+ - invalid_authorization_details
+ - invalid_dpop_proof
+ - use_dpop_nonce
+ - insufficient_user_authentication
+
+ ErrorResponse:
+ type: object
+ x-tags:
+ - federation
+ required:
+ - error
+ - error_description
+ properties:
+ error:
+ $ref: '#/components/schemas/ErrorType'
+ error_description:
type: string
- description: Version identifier string for the client software identified by software_id.
+ description: A human-readable short text describing the error.
OAuthDynamicClientTokenEndpointAuthMethod:
type: string
@@ -632,85 +2829,89 @@ components:
- code
- token
- OAuthProtectedResourceMetadata:
- allOf:
- - $ref: '#/components/schemas/CommonMetadata'
- - $ref: '#/components/schemas/ProtectedResourceMetadata'
- type: object
- x-tags:
- - federation
-
- ProtectedResourceMetadata:
- type: object
+ OAuthDynamicClientMetadata:
+ type:
+ object
x-tags:
- federation
properties:
- resource:
- type: string
- format: uri
- description: URL identifier of the protected resource using the https scheme.
- authorization_servers:
- type: array
- items:
- type: string
- description: JSON array of OAuth authorization server issuer identifiers for servers that can be used with this protected resource.
- jwks_uri:
- type: string
- format: uri
- description: URL of the protected resource's JWK Set document, containing its public keys.
- scopes_supported:
+ redirect_uris:
type: array
items:
type: string
- description: JSON array of OAuth 2.0 scope values used in authorization requests to access this protected resource.
- bearer_methods_supported:
+ format: uri
+ description: Array of redirection URI strings for redirect-based flows.
+ token_endpoint_auth_method:
+ $ref: '#/components/schemas/OAuthDynamicClientTokenEndpointAuthMethod'
+ grant_types:
type: array
items:
- type: string
- description: JSON array of supported methods for sending an OAuth 2.0 Bearer Token to the protected resource. Values are ["header", "body", "query"].
- resource_signing_alg_values_supported:
+ $ref: '#/components/schemas/OAuthDynamicClientGrantTypes'
+ response_types:
type: array
items:
- type: string
- description: JSON array of JWS signing algorithms supported by the protected resource for signing responses.
- resource_documentation:
- type: string
- format: uri
- description: URL of a page with human-readable information for developers using the protected resource.
- resource_policy_uri:
+ $ref: '#/components/schemas/OAuthDynamicClientResponseTypes'
+ client_name:
type: string
- format: uri
- description: URL to the protected resource's policy document.
- resource_tos_uri:
+ description: Human-readable string name of the client to be presented to the end-user during authorization.
+ client_uri:
type: string
format: uri
- description: URL to the protected resource's terms of service.
-
- CommonMetadata:
- type: object
- x-tags:
- - federation
- properties:
- organization_name:
+ description: URL string of a web page providing information about the client.
+ logo_uri:
type: string
- description: A human-readable name representing the organization owning this Entity. If the owner is a physical person, this MAY be, for example, the person's name. Note that this information will be publicly available.
+ format: uri
+ description: URL string that references a logo for the client.
+ scope:
+ type: string
+ description: Space-separated list of scope values the client can use when requesting access tokens.
contacts:
type: array
items:
type: string
- description: JSON array with one or more strings representing contact persons at the Entity. These MAY contain names, e-mail addresses, descriptions, phone numbers, etc.
- logo_uri:
+ description: Array of strings representing ways to contact people responsible for this client, typically email addresses.
+ tos_uri:
type: string
format: uri
- description: A URL that points to the logo of this Entity. The file containing the logo SHOULD be published in a format that can be viewed via the web.
+ description: URL string that points to a human-readable terms of service document for the client.
policy_uri:
type: string
format: uri
- description: URL of the documentation of conditions and policies relevant to this Entity.
- homepage_uri:
+ description: URL string that points to a human-readable privacy policy document.
+ jwks_uri:
type: string
format: uri
- description: URL of a Web page for the organization owning this Entity.
+ description: URL string referencing the client’s JSON Web Key (JWK) Set document, which contains the client’s public keys.
+ jwks:
+ $ref: '#/components/schemas/JWKS'
+ software_id:
+ type: string
+ description: Unique identifier string for the client software to be dynamically registered.
+ software_version:
+ type: string
+ description: Version identifier string for the client software identified by software_id.
+
+ OpenIDConnectDynamicClientRegistrationGrantTypes:
+ type: string
+ x-tags:
+ - federation
+ description: JSON array containing a list of the OAuth 2.0 Grant Types that the Client is declaring that it will restrict itself to using.
+ enum:
+ - authorization_code
+ - implicit
+ - refresh_token
+ example: [ "authorization_code", "implicit" ]
+
+ OpenIDConnectDynamicClientRegistrationApplicationType:
+ type: string
+ x-tags:
+ - federation
+ description: Kind of the application. The default, if omitted, is web.
+ enum:
+ - native
+ - web
+ example: native
+ default: web
OpenIDConnectDynamicClientRegistrationMetadata:
type: object
@@ -828,28 +3029,6 @@ components:
required:
- redirect_uris
- OpenIDConnectDynamicClientRegistrationGrantTypes:
- type: string
- x-tags:
- - federation
- description: JSON array containing a list of the OAuth 2.0 Grant Types that the Client is declaring that it will restrict itself to using.
- enum:
- - authorization_code
- - implicit
- - refresh_token
- example: [ "authorization_code", "implicit" ]
-
- OpenIDConnectDynamicClientRegistrationApplicationType:
- type: string
- x-tags:
- - federation
- description: Kind of the application. The default, if omitted, is web.
- enum:
- - native
- - web
- example: native
- default: web
-
OpenIDConnectDiscoveryProviderMetadata:
type: object
x-tags:
@@ -1509,57 +3688,328 @@ components:
type: boolean
description: Specifies whether the client always uses DPoP for token requests.
- ErrorResponse:
+ FederationHistoricalKeysResponse:
type: object
x-tags:
- federation
required:
- - error
- - error_description
+ - iss
+ - iat
+ - keys
properties:
- error:
- $ref: '#/components/schemas/ErrorType'
- error_description:
+ iss:
type: string
- description: A human-readable short text describing the error.
+ format: date-time
+ description: The Entity's Entity Identifier.
+ iat:
+ type: string
+ format: date-time
+ description: Time when the signed JWT was issued, using the time format defined for the iat claim in RFC7519.
+ keys:
+ type: array
+ items:
+ $ref: '#/components/schemas/JwkDTO'
- ErrorType:
- type: string
+ ResolveResponse:
+ type: object
x-tags:
- federation
- description: One of the predefined error codes.
- example: invalid_request
+ required:
+ - iss
+ - sub
+ - iat
+ - exp
+ - metadata
+ properties:
+ iss:
+ type: string
+ format: date-time
+ description: Entity Identifier of the issuer of the resolve response.
+ sub:
+ type: string
+ format: date-time
+ description: Entity Identifier of the subject of the resolve response.
+ iat:
+ type: string
+ format: date-time
+ description: Time when this resolution was issued. This is expressed as Seconds Since the Epoch.
+ exp:
+ type: string
+ format: date-time
+ description: Time when this resolution is no longer valid. This is expressed as Seconds Since the Epoch.
+ metadata:
+ $ref: '#/components/schemas/Metadata'
+ trust_marks:
+ type: array
+ items:
+ $ref: '#/components/schemas/TrustMark'
+ trust_chain:
+ type: array
+ items:
+ type: string
+
+ StatusResponse:
+ type: object
+ properties:
+ status:
+ type: string
+ description: The current status of the node.
+ example: "OK"
+ timestamp:
+ type: string
+ format: date-time
+ description: The time at which the status was checked.
+ example: "2024-06-05T12:34:56Z"
+
+ SystemStatsResponse:
+ type: object
+ properties:
+ uptime:
+ type: string
+ description: The system uptime.
+ example: "5 days, 4:03:27"
+
+ UpdateUserRoleRequest:
+ type: object
+ properties:
+ role:
+ type: string
+ description: The role to be added or removed from the user.
+ required:
+ - role
+
+ UpdateUserRoleResponse:
+ type: object
+ properties:
+ roles:
+ type: array
+ items:
+ type: string
+ description: The updated list of roles for the user.
+
+ UpdateUserScopeRequest:
+ type: object
+ properties:
+ scope:
+ type: string
+ description: The scope to be added or removed from the user.
+ required:
+ - scope
+
+ UpdateUserScopeResponse:
+ type: object
+ properties:
+ scopes:
+ type: array
+ items:
+ type: string
+ description: The updated list of scopes for the user.
+
+ CreateAccountDTO:
+ type: object
+ properties:
+ username:
+ type: string
+ description: The username of the account.
+ example: acmeco
+ required:
+ - username
+
+ AddUserToAccountRequest:
+ type: object
+ properties:
+ email:
+ type: string
+ format: email
+ description: The email of the user to be added to the account.
+ example: user@acme-corp.com
+ role:
+ type: string
+ description: The role of the user within the account (e.g., admin, user). The default, if omitted, is user.
+ example: admin
+ required:
+ - email
+
+ AddUserToAccountResponse:
+ type: object
+ properties:
+ accountId:
+ type: string
+ description: The unique identifier for the account.
+ example: account123
+ userId:
+ type: string
+ description: The ID of the user added to the account.
+ example: user123
+ role:
+ type: string
+ description: The role of the user within the account.
+ example: admin
+
+ AccountDTO:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The unique identifier for the account.
+ example: 12345
+ username:
+ type: string
+ description: The username of the account.
+ example: acmecorp
+
+ CreateEntityStatementRequest:
+ properties:
+ dry_run: # TO-DO Add correct required attributes
+ type: boolean
+ description: If true, the entity statement will be generated but not persisted.
+ default: false
+
+ Scope:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The unique identifier for the scope.
+ example: "1"
+ name:
+ type: string
+ description: The name of the scope.
+ example: "create:statement"
+ description:
+ type: string
+ description: A detailed description of what the scope allows.
+ example: "Permission to create Entity Statements"
+
+ Role:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The unique identifier for the role.
+ example: "1"
+ name:
+ type: string
+ description: The name of the role.
+ example: "admin"
+ description:
+ type: string
+ description: A detailed description of what the role allows.
+ example: "Administrator role with full access to account management"
+ scopes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Scope"
+
+ User:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The unique identifier for the user.
+ example: "user123"
+ role:
+ type: string
+ description: The role assigned to the user within the account.
+ example: "admin"
+ scopes:
+ type: array
+ items:
+ type: string
+ description: The list of scopes assigned to the user.
+ example:
+ - "read:config"
+ - "write:config"
+ email:
+ type: string
+ format: email
+ description: The email address of the user.
+ example: "johndoe@gmail.com"
+ required:
+ - email
+
+ UserAccount:
+ allOf:
+ - $ref: '#/components/schemas/AccountDTO'
+ type: object
+ properties:
+ roles:
+ type: array
+ items:
+ type: string
+ description: The roles assigned to the user within this account.
+ scopes:
+ type: array
+ items:
+ type: string
+ description: The scopes assigned to the user within this account.
+
+ UserDetailsResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The unique identifier for the user.
+ email:
+ type: string
+ format: email
+ description: The email address of the user.
+ accounts:
+ type: array
+ items:
+ $ref: '#/components/schemas/UserAccount'
+
+ AuditLog:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The unique identifier for the audit log entry.
+ accountId:
+ type: string
+ description: The account ID from where the log was generated
+ timestamp:
+ type: string
+ format: date-time
+ description: The timestamp of the audit log entry.
+ errorLevel:
+ $ref: '#/components/schemas/LogLevel'
+ errorCode:
+ type: string
+ description: The error code or type.
+ errorMessage:
+ type: string
+ description: A meaningful explanation of what happened.
+ componentName:
+ type: string
+ description: The name of the component logging the error.
+ operation:
+ type: string
+ description: The operation performed when the error occurred.
+ sourceLineNumber:
+ type: integer
+ description: The source code line number.
+ details:
+ type: object
+ additionalProperties: true
+ description: Additional details about the audit log entry.
+
+ LogLevel:
+ type: string
enum:
- - invalid_request
- - invalid_client
- - invalid_issuer
- - not_found
- - server_error
- - temporary_unavailable
- - unsupported_parameter
- - invalid_token
- - insufficient_scope
- - unsupported_token_type
- - interaction_required
- - login_required
- - account_selection_required
- - consent_required
- - invalid_request_uri
- - invalid_request_object
- - request_not_supported
- - request_uri_not_supported
- - registration_not_supported
- - need_info
- - request_denied
- - request_submitted
- - authorization_pending
- - access_denied
- - slow_down
- - expired_token
- - invalid_target
- - unsupported_pop_key
- - incompatible_ace_profiles
- - invalid_authorization_details
- - invalid_dpop_proof
- - use_dpop_nonce
- - insufficient_user_authentication
+ - TRACE
+ - DEBUG
+ - INFO
+ - NOTICE
+ - WARN
+ - ERROR
+ - FATAL
+ description: Enum for log levels.
+ example: ERROR
+
+ KMS:
+ type: string
+ enum:
+ - LOCAL
+ description: Enum for KMS integrations.
+ example: LOCAL
\ No newline at end of file
diff --git a/modules/openid-federation-common/build.gradle.kts b/modules/openid-federation-common/build.gradle.kts
index 4ac9c5c8..f4ffe611 100644
--- a/modules/openid-federation-common/build.gradle.kts
+++ b/modules/openid-federation-common/build.gradle.kts
@@ -1,9 +1,8 @@
-import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
plugins {
alias(libs.plugins.kotlinMultiplatform)
- alias(libs.plugins.androidLibrary)
+// alias(libs.plugins.androidLibrary)
kotlin("plugin.serialization") version "2.0.0"
}
@@ -11,14 +10,13 @@ val ktorVersion = "2.3.11"
repositories {
mavenCentral()
+ mavenLocal()
google()
}
kotlin {
jvm()
- // wasmJs is not available yet for ktor until v3.x is released which is still in alpha
- // @OptIn(ExperimentalWasmDsl::class)
js {
browser {
commonWebpackConfig {
@@ -36,18 +34,18 @@ kotlin {
}
}
- // TODO Should be placed back at a later point in time: https://sphereon.atlassian.net/browse/OIDF-50
- // androidTarget {
- // @OptIn(ExperimentalKotlinGradlePluginApi::class)
- // compilerOptions {
- // jvmTarget.set(JvmTarget.JVM_11)
- // }
- // }
+ // wasmJs is not available yet for ktor until v3.x is released which is still in alpha
+
+// androidTarget {
+// @OptIn(ExperimentalKotlinGradlePluginApi::class)
+// compilerOptions {
+// jvmTarget.set(JvmTarget.JVM_11)
+// }
+// }
- // iosX64()
- // iosArm64()
- // iosSimulatorArm64()
- // androidTarget()
+// iosX64()
+// iosArm64()
+// iosSimulatorArm64()
sourceSets {
val commonMain by getting {
@@ -98,9 +96,6 @@ kotlin {
// val iosMain by creating {
// dependsOn(commonMain)
-// dependencies {
-// implementation("io.ktor:ktor-client-core-ios:$ktorVersion")
-// }
// }
// val iosX64Main by getting {
// dependsOn(iosMain)
@@ -123,7 +118,7 @@ kotlin {
// implementation("io.ktor:ktor-client-cio-iossimulatorarm64:$ktorVersion")
// }
// }
-
+//
// val iosTest by creating {
// dependsOn(commonTest)
// dependencies {
@@ -143,6 +138,7 @@ kotlin {
}
val jsTest by getting {
+ dependsOn(commonTest)
dependencies {
implementation(kotlin("test-js"))
implementation(kotlin("test-annotations-common"))
@@ -151,21 +147,21 @@ kotlin {
}
}
-tasks.register("printSdkLocation") {
- doLast {
- println("Android SDK Location: ${android.sdkDirectory}")
- }
-}
-
-android {
- namespace = "com.sphereon.oid.fed.common"
- compileSdk = libs.versions.android.compileSdk.get().toInt()
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
- defaultConfig {
- minSdk = libs.versions.android.minSdk.get().toInt()
- }
-}
+//tasks.register("printSdkLocation") {
+// doLast {
+// println("Android SDK Location: ${android.sdkDirectory}")
+// }
+//}
+//
+//android {
+// namespace = "com.sphereon.oid.fed.common"
+// compileSdk = libs.versions.android.compileSdk.get().toInt()
+// compileOptions {
+// sourceCompatibility = JavaVersion.VERSION_11
+// targetCompatibility = JavaVersion.VERSION_11
+// }
+// defaultConfig {
+// minSdk = libs.versions.android.minSdk.get().toInt()
+// }
+//}
diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/httpclient/EntityStatementJwtConverter.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/httpclient/EntityStatementJwtConverter.kt
deleted file mode 100644
index ed7c83d9..00000000
--- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/httpclient/EntityStatementJwtConverter.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.sphereon.oid.fed.common.httpclient
-
-import com.sphereon.oid.fed.common.mapper.JsonMapper
-import com.sphereon.oid.fed.openapi.models.EntityStatement
-import io.ktor.http.*
-import io.ktor.http.content.*
-import io.ktor.serialization.*
-import io.ktor.util.reflect.*
-import io.ktor.utils.io.*
-import io.ktor.utils.io.charsets.*
-import io.ktor.utils.io.core.*
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-
-class EntityStatementJwtConverter: ContentConverter {
-
- override suspend fun serializeNullable(
- contentType: ContentType,
- charset: Charset,
- typeInfo: TypeInfo,
- value: Any?
- ): OutgoingContent? {
- if (value is EntityStatement) {
- return OutgoingEntityStatementContent(value)
- } else if (value is String) {
- JsonMapper().mapEntityStatement(value)?.let {
- return OutgoingEntityStatementContent(it)
- }
- }
- return null
- }
-
- override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
- val text = content.readRemaining().readText(charset)
- return Json.decodeFromString(EntityStatement.serializer(), text)
- }
-}
-
-class OutgoingEntityStatementContent(private val entityStatement: EntityStatement): OutgoingContent.ByteArrayContent() {
-
- override fun bytes(): ByteArray {
- val serializedData = Json.encodeToString(entityStatement)
- return serializedData.toByteArray(Charsets.UTF_8)
- }
-}
diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClient.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClient.kt
index 21b3c548..3b8f879e 100644
--- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClient.kt
+++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClient.kt
@@ -1,20 +1,17 @@
package com.sphereon.oid.fed.common.httpclient
-import com.sphereon.oid.fed.openapi.models.EntityStatement
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
-import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.http.HttpMethod.Companion.Get
import io.ktor.http.HttpMethod.Companion.Post
-import io.ktor.serialization.kotlinx.json.*
import io.ktor.utils.io.core.*
class OidFederationClient(
@@ -24,10 +21,6 @@ class OidFederationClient(
) {
private val client: HttpClient = HttpClient(engine) {
install(HttpCache)
- install(ContentNegotiation) {
- register(EntityStatementJwt, EntityStatementJwtConverter())
- json()
- }
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.INFO
@@ -47,7 +40,11 @@ class OidFederationClient(
}
}
- suspend fun fetchEntityStatement(url: String, httpMethod: HttpMethod = Get, parameters: Parameters = Parameters.Empty): EntityStatement {
+ suspend fun fetchEntityStatement(
+ url: String,
+ httpMethod: HttpMethod = Get,
+ parameters: Parameters = Parameters.Empty
+ ): String {
return when (httpMethod) {
Get -> getEntityStatement(url)
Post -> postEntityStatement(url, parameters)
@@ -55,15 +52,15 @@ class OidFederationClient(
}
}
- private suspend fun getEntityStatement(url: String): EntityStatement {
- return client.use { it.get(url).body() }
+ private suspend fun getEntityStatement(url: String): String {
+ return client.use { it.get(url).body() }
}
- private suspend fun postEntityStatement(url: String, parameters: Parameters): EntityStatement {
+ private suspend fun postEntityStatement(url: String, parameters: Parameters): String {
return client.use {
it.post(url) {
setBody(FormDataContent(parameters))
- }.body()
+ }.body()
}
}
}
diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwk/Jwk.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwk/Jwk.kt
new file mode 100644
index 00000000..03cbaee8
--- /dev/null
+++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwk/Jwk.kt
@@ -0,0 +1,6 @@
+package com.sphereon.oid.fed.common.jwk
+
+import com.sphereon.oid.fed.openapi.models.Jwk
+
+expect fun generateKeyPair(): Jwk
+
diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/logic/EntityLogic.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/logic/EntityLogic.kt
index 3ae15dd3..495680ca 100644
--- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/logic/EntityLogic.kt
+++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/logic/EntityLogic.kt
@@ -1,20 +1,20 @@
package com.sphereon.oid.fed.common.logic
-import com.sphereon.oid.fed.openapi.models.EntityStatement
+import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
class EntityLogic {
- fun getEntityType(entityStatement: EntityStatement): EntityType = when {
+ fun getEntityType(entityStatement: EntityConfigurationStatement): EntityType = when {
isFederationListEndpointPresent(entityStatement) && !isAuthorityHintPresent(entityStatement) -> EntityType.TRUST_ANCHOR
isFederationListEndpointPresent(entityStatement) && isAuthorityHintPresent(entityStatement) -> EntityType.INTERMEDIATE
!isFederationListEndpointPresent(entityStatement) && isAuthorityHintPresent(entityStatement) -> EntityType.LEAF
else -> EntityType.UNDEFINED
}
- private fun isAuthorityHintPresent(entityStatement: EntityStatement): Boolean =
+ private fun isAuthorityHintPresent(entityStatement: EntityConfigurationStatement): Boolean =
entityStatement.authorityHints?.isNotEmpty() ?: false
- private fun isFederationListEndpointPresent(entityStatement: EntityStatement): Boolean =
+ private fun isFederationListEndpointPresent(entityStatement: EntityConfigurationStatement): Boolean =
entityStatement.metadata?.federationEntity?.federationListEndpoint?.isNotEmpty() ?: false
}
diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt
index 29de6d62..3c566d5c 100644
--- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt
+++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt
@@ -1,6 +1,6 @@
package com.sphereon.oid.fed.common.mapper
-import com.sphereon.oid.fed.openapi.models.EntityStatement
+import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
import com.sphereon.oid.fed.openapi.models.JWTHeader
import com.sphereon.oid.fed.openapi.models.JWTSignature
import kotlinx.serialization.json.Json
@@ -14,13 +14,14 @@ class JsonMapper {
/*
* Used for mapping JWT token to EntityStatement object
*/
- fun mapEntityStatement(jwtToken: String): EntityStatement? =
+ fun mapEntityStatement(jwtToken: String): EntityConfigurationStatement? =
decodeJWTComponents(jwtToken)?.payload?.let { Json.decodeFromJsonElement(it) }
/*
* Used for mapping trust chain
*/
- fun mapTrustChain(jwtTokenList: List): List = jwtTokenList.map { mapEntityStatement(it) }
+ fun mapTrustChain(jwtTokenList: List): List =
+ jwtTokenList.map { mapEntityStatement(it) }
/*
* Used for decoding JWT to an object of JWT with Header, Payload and Signature
diff --git a/modules/openid-federation-common/src/commonTest/kotlin/com/sphereon/oid/fed/common/logic/EntityLogicTest.kt b/modules/openid-federation-common/src/commonTest/kotlin/com/sphereon/oid/fed/common/logic/EntityLogicTest.kt
index 5f8b3e23..2dd51aea 100644
--- a/modules/openid-federation-common/src/commonTest/kotlin/com/sphereon/oid/fed/common/logic/EntityLogicTest.kt
+++ b/modules/openid-federation-common/src/commonTest/kotlin/com/sphereon/oid/fed/common/logic/EntityLogicTest.kt
@@ -1,6 +1,7 @@
package com.sphereon.oid.fed.common.logic
-import com.sphereon.oid.fed.openapi.models.EntityStatement
+import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
+import com.sphereon.oid.fed.openapi.models.JWKS
import com.sphereon.oid.fed.openapi.models.Metadata
import kotlinx.serialization.json.Json
import kotlin.test.Test
@@ -16,29 +17,37 @@ class EntityLogicTest {
@Test
fun shouldReturnTrustAnchor() {
- val trustAnchorEntityStatement = json.decodeFromString(TRUST_ANCHOR_ENTITY_STATEMENT)
+ val trustAnchorEntityStatement =
+ json.decodeFromString(TRUST_ANCHOR_ENTITY_STATEMENT)
assertEquals(EntityType.TRUST_ANCHOR, entityLogic.getEntityType(trustAnchorEntityStatement))
}
@Test
fun shouldReturnIntermediate() {
- val intermediateEntityStatement = json.decodeFromString(INTERMEDIATE_ENTITY_STATEMENT)
+ val intermediateEntityStatement =
+ json.decodeFromString(INTERMEDIATE_ENTITY_STATEMENT)
assertEquals(EntityType.INTERMEDIATE, entityLogic.getEntityType(intermediateEntityStatement))
}
@Test
fun shouldReturnLeafEntity() {
- val leafEntityStatement = json.decodeFromString(LEAF_ENTITY_STATEMENT)
+ val leafEntityStatement = json.decodeFromString(LEAF_ENTITY_STATEMENT)
assertEquals(EntityType.LEAF, entityLogic.getEntityType(leafEntityStatement))
}
@Test
fun shouldReturnUndefined() {
- val entityStatement = EntityStatement(
- metadata = Metadata(federationEntity = null), authorityHints = emptyList()
+ val entityStatement = EntityConfigurationStatement(
+ metadata = Metadata(federationEntity = null),
+ authorityHints = emptyList(),
+ exp = 0,
+ iat = 0,
+ iss = "",
+ sub = "",
+ jwks = JWKS(),
)
assertEquals(EntityType.UNDEFINED, entityLogic.getEntityType(entityStatement))
diff --git a/modules/openid-federation-common/src/jsMain/kotlin/com.sphereon.oid.fed.common.jwk/Jwk.kt b/modules/openid-federation-common/src/jsMain/kotlin/com.sphereon.oid.fed.common.jwk/Jwk.kt
new file mode 100644
index 00000000..f9c5208c
--- /dev/null
+++ b/modules/openid-federation-common/src/jsMain/kotlin/com.sphereon.oid.fed.common.jwk/Jwk.kt
@@ -0,0 +1,20 @@
+package com.sphereon.oid.fed.common.jwk
+
+import com.sphereon.oid.fed.common.jwt.Jose
+import com.sphereon.oid.fed.openapi.models.Jwk
+
+@ExperimentalJsExport
+@JsExport
+actual fun generateKeyPair(): Jwk {
+ val key = Jose.generateKeyPair("EC")
+ return Jwk(
+ d = key.d,
+ alg = key.alg,
+ crv = key.crv,
+ x = key.x,
+ y = key.y,
+ kid = key.kid,
+ kty = key.kty,
+ use = key.use,
+ )
+}
diff --git a/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt b/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt
index 4286f44f..5429b9b5 100644
--- a/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt
+++ b/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt
@@ -1,6 +1,6 @@
package com.sphereon.oid.fed.common.jwt
-import com.sphereon.oid.fed.openapi.models.EntityStatement
+import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
import com.sphereon.oid.fed.openapi.models.JWTHeader
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@@ -12,18 +12,21 @@ external object Jose {
constructor(payload: dynamic) {
definedExternally
}
+
fun setProtectedHeader(protectedHeader: dynamic): SignJWT {
definedExternally
}
+
fun sign(key: Any?, signOptions: Any?): String {
definedExternally
}
}
+
fun generateKeyPair(alg: String, options: dynamic = definedExternally): dynamic
fun jwtVerify(jwt: String, key: Any, options: dynamic = definedExternally): dynamic
}
-actual typealias JwtPayload = EntityStatement
+actual typealias JwtPayload = EntityConfigurationStatement
actual typealias JwtHeader = JWTHeader
@ExperimentalJsExport
diff --git a/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt b/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt
index 3f4c3e63..34a58e2f 100644
--- a/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt
+++ b/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt
@@ -1,6 +1,7 @@
package com.sphereon.oid.fed.common.jwt
import com.sphereon.oid.fed.common.jwt.Jose.generateKeyPair
+import com.sphereon.oid.fed.openapi.models.JWKS
import kotlinx.coroutines.async
import kotlinx.coroutines.await
import kotlinx.coroutines.test.runTest
@@ -15,9 +16,11 @@ class JoseJwtTest {
val keyPair = (generateKeyPair("RS256") as Promise).await()
val result = async {
sign(
- JwtPayload(iss="test"),
- JwtHeader(typ="JWT",alg="RS256",kid="test"),
- mutableMapOf("privateKey" to keyPair.privateKey)) }
+ JwtPayload(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS()),
+ JwtHeader(typ = "JWT", alg = "RS256", kid = "test"),
+ mutableMapOf("privateKey" to keyPair.privateKey)
+ )
+ }
assertTrue((result.await() as Promise).await().startsWith("ey"))
}
@@ -26,9 +29,10 @@ class JoseJwtTest {
fun verifyTest() = runTest {
val keyPair = (generateKeyPair("RS256") as Promise).await()
val signed = (sign(
- JwtPayload(iss="test"),
- JwtHeader(typ="JWT",alg="RS256",kid="test"),
- mutableMapOf("privateKey" to keyPair.privateKey)) as Promise).await()
+ JwtPayload(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS()),
+ JwtHeader(typ = "JWT", alg = "RS256", kid = "test"),
+ mutableMapOf("privateKey" to keyPair.privateKey)
+ ) as Promise).await()
val result = async { verify(signed, keyPair.publicKey, emptyMap()) }
assertTrue((result.await()))
}
diff --git a/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwk/Jwk.jvm.kt b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwk/Jwk.jvm.kt
new file mode 100644
index 00000000..873ddaba
--- /dev/null
+++ b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwk/Jwk.jvm.kt
@@ -0,0 +1,32 @@
+package com.sphereon.oid.fed.common.jwk
+
+import com.nimbusds.jose.Algorithm
+import com.nimbusds.jose.jwk.Curve
+import com.nimbusds.jose.jwk.ECKey
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator
+import com.sphereon.oid.fed.openapi.models.Jwk
+import java.util.*
+
+actual fun generateKeyPair(): Jwk {
+ try {
+ val ecKey: ECKey = ECKeyGenerator(Curve.P_256)
+ .keyIDFromThumbprint(true)
+ .algorithm(Algorithm("EC"))
+ .issueTime(Date())
+ .generate()
+
+ return Jwk(
+ d = ecKey.d.toString(),
+ alg = ecKey.algorithm.name,
+ crv = ecKey.curve.name,
+ kid = ecKey.keyID,
+ kty = ecKey.keyType.value,
+ use = ecKey.keyUse?.value ?: "sig",
+ x = ecKey.x.toString(),
+ y = ecKey.y.toString()
+ )
+
+ } catch (e: Exception) {
+ throw Exception("Couldn't generate the EC Key Pair: ${e.message}", e)
+ }
+}
diff --git a/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt
index a0e9f17b..377697ad 100644
--- a/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt
+++ b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt
@@ -18,7 +18,7 @@ actual fun sign(
opts: Map
): String {
val rsaJWK = opts["key"] as RSAKey? ?: throw IllegalArgumentException("The RSA key pair is required")
-
+
val signer: JWSSigner = RSASSASigner(rsaJWK)
val signedJWT = SignedJWT(
@@ -44,4 +44,4 @@ actual fun verify(
} catch (e: Exception) {
throw Exception("Couldn't verify the JWT Signature: ${e.message}", e)
}
-}
+}
\ No newline at end of file
diff --git a/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClientTest.kt b/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClientTest.kt
index d95a7de8..8ce3813a 100644
--- a/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClientTest.kt
+++ b/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/httpclient/OidFederationClientTest.kt
@@ -7,34 +7,28 @@ import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.test.Test
+import kotlin.test.assertEquals
class OidFederationClientTest {
- private val entityStatement = EntityStatement(
- iss = "https://edugain.org/federation",
- sub = "https://openid.sunet.se",
- exp = 1568397247,
- iat = 1568310847,
- sourceEndpoint = "https://edugain.org/federation/federation_fetch_endpoint",
- jwks = JWKS(
- propertyKeys = listOf(
- JWK(
- // missing e and n ?
- kid = "dEEtRjlzY3djcENuT01wOGxrZlkxb3RIQVJlMTY0...",
- kty = "RSA"
- )
- )
- ),
- metadata = Metadata(
- federationEntity = FederationEntityMetadata(
- organizationName = "SUNET"
- )
- )
- )
+ private val jwt = """
+ eyJhbGciOiJSUzI1NiIsInR5cCI6ImVudGl0eS1zdGF0ZW1lbnQrand0In0.eyJpc3MiOiJodHRwczovL2VkdWdhaW4ub3JnL2ZlZGVyYXRpb24i
+ LCJzdWIiOiJodHRwczovL29wZW5pZC5zdW5ldC5zZSIsImV4cCI6MTU2ODM5NzI0NywiaWF0IjoxNTY4MzEwODQ3LCJzb3VyY2VfZW5kcG9pbnQi
+ OiJodHRwczovL2VkdWdhaW4ub3JnL2ZlZGVyYXRpb24vZmVkZXJhdGlvbl9mZXRjaF9lbmRwb2ludCIsImp3a3MiOnsia2V5cyI6W3siZSI6IkFR
+ QUIiLCJraWQiOiJkRUV0UmpselkzZGpjRU51VDAxd09HeHJabGt4YjNSSVFWSmxNVFkwLi4uIiwia3R5IjoiUlNBIiwibiI6Ing5N1lLcWM5Q3Mt
+ RE50RnJRN192aFhvSDlid2tEV1c2RW4yakowNDR5SC4uLiJ9XX0sIm1ldGFkYXRhIjp7ImZlZGVyYXRpb25fZW50aXR5Ijp7Im9yZ2FuaXphdGlv
+ bl9uYW1lIjoiU1VORVQifX0sIm1ldGFkYXRhX3BvbGljeSI6eyJvcGVuaWRfcHJvdmlkZXIiOnsic3ViamVjdF90eXBlc19zdXBwb3J0ZWQiOnsi
+ dmFsdWUiOlsicGFpcndpc2UiXX0sInRva2VuX2VuZHBvaW50X2F1dGhfbWV0aG9kc19zdXBwb3J0ZWQiOnsiZGVmYXVsdCI6WyJwcml2YXRlX2tl
+ eV9qd3QiXSwic3Vic2V0X29mIjpbInByaXZhdGVfa2V5X2p3dCIsImNsaWVudF9zZWNyZXRfand0Il0sInN1cGVyc2V0X29mIjpbInByaXZhdGVf
+ a2V5X2p3dCJdfX19fQ.Jdd45c8LKvdzUy3FXl66Dp_1MXCkcbkL_uO17kWP7bIeYHe-fKqPlV2stta3oUitxy3NB8U3abgmNWnSf60qEaF7YmiDr
+ j0u3WZE87QXYv6fAMW00TGvcPIC8qtoFcamK7OTrsi06eqKUJslCPSEXYl6couNkW70YSiJGUI0PUQ-TmD-vFFpQCFwtIfQeUUm47GxcCP0jBjjz
+ gg1D3rMCX49RhRdJWnH8yl6r1lZazcREVqNuuN6LBHhKA7asNNwtLkcJP1rCRioxIFQPn7g0POM6t50l4wNhDewXZ-NVENex4N7WeVTA1Jh9EcD_
+ swTuR9X1AbD7vW80OXe_RrGmw
+ """
private val mockEngine = MockEngine {
respond(
- content = Json.encodeToString(entityStatement),
+ content = jwt,
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/entity-statement+jwt")
)
@@ -45,7 +39,7 @@ class OidFederationClientTest {
runBlocking {
val client = OidFederationClient(mockEngine)
val response = client.fetchEntityStatement("https://www.example.com?iss=https://edugain.org/federation&sub=https://openid.sunet.se", HttpMethod.Get)
- assert(response == entityStatement)
+ assertEquals(jwt, response)
}
}
@@ -58,7 +52,7 @@ class OidFederationClientTest {
append("iss","https://edugain.org/federation")
append("sub","https://openid.sunet.se")
})
- assert(response == entityStatement)
+ assertEquals(jwt, response)
}
}
}
diff --git a/modules/persistence/build.gradle.kts b/modules/persistence/build.gradle.kts
new file mode 100644
index 00000000..4f0e7de8
--- /dev/null
+++ b/modules/persistence/build.gradle.kts
@@ -0,0 +1,49 @@
+plugins {
+ kotlin("multiplatform") version "2.0.0"
+ id("app.cash.sqldelight") version "2.0.2"
+}
+
+group = "com.sphereon.oid.fed.persistence"
+version = "0.1.0"
+
+repositories {
+ google()
+ mavenCentral()
+ mavenLocal()
+}
+
+sqldelight {
+ databases {
+ create("Database") {
+ packageName = "com.sphereon.oid.fed.persistence"
+ dialect("app.cash.sqldelight:postgresql-dialect:2.0.2")
+ schemaOutputDirectory = file("src/commonMain/resources/db/migration")
+ migrationOutputDirectory = file("src/commonMain/resources/db/migration")
+ deriveSchemaFromMigrations = true
+ migrationOutputFileFormat = ".sql"
+ srcDirs.from(
+ "src/commonMain/sqldelight"
+ )
+ }
+ }
+}
+
+kotlin {
+ jvm()
+
+ sourceSets {
+ commonMain {
+ dependencies {
+ implementation(projects.modules.openapi)
+ }
+ }
+
+ jvmMain {
+ dependencies {
+ implementation("app.cash.sqldelight:jdbc-driver:2.0.2")
+ implementation("com.zaxxer:HikariCP:5.1.0")
+ implementation("org.postgresql:postgresql:42.7.3")
+ }
+ }
+ }
+}
diff --git a/modules/persistence/gradle/wrapper/gradle-wrapper.jar b/modules/persistence/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..249e5832
Binary files /dev/null and b/modules/persistence/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/modules/persistence/gradle/wrapper/gradle-wrapper.properties b/modules/persistence/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..80f0757a
--- /dev/null
+++ b/modules/persistence/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Jul 25 12:58:25 CEST 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Constants.kt b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Constants.kt
new file mode 100644
index 00000000..ff0fc0b5
--- /dev/null
+++ b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Constants.kt
@@ -0,0 +1,10 @@
+package com.sphereon.oid.fed.persistence
+
+class Constants {
+ companion object {
+ const val DATASOURCE_URL = "DATASOURCE_URL"
+ const val DATASOURCE_USER = "DATASOURCE_USER"
+ const val DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD"
+ const val SQLITE_IS_NOT_SUPPORTED_IN_JVM = "SQLite is not supported in JVM"
+ }
+}
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
new file mode 100644
index 00000000..73d1248b
--- /dev/null
+++ b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/Persistence.kt
@@ -0,0 +1,11 @@
+package com.sphereon.oid.fed.persistence
+
+import com.sphereon.oid.fed.persistence.repositories.AccountRepository
+import com.sphereon.oid.fed.persistence.repositories.KeyRepository
+import com.sphereon.oid.fed.persistence.repositories.SubordinateRepository
+
+expect object Persistence {
+ val accountRepository: AccountRepository
+ val keyRepository: KeyRepository
+ val subordinateRepository: SubordinateRepository
+}
diff --git a/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/database/PlatformSqlDriver.kt b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/database/PlatformSqlDriver.kt
new file mode 100644
index 00000000..f84b254a
--- /dev/null
+++ b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/database/PlatformSqlDriver.kt
@@ -0,0 +1,8 @@
+package com.sphereon.oid.fed.persistence.database
+
+import app.cash.sqldelight.db.SqlDriver
+
+expect class PlatformSqlDriver {
+ fun createPostgresDriver(url: String, username: String, password: String): SqlDriver
+ fun createSqliteDriver(path: String): SqlDriver
+}
diff --git a/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/AccountRepository.kt b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/AccountRepository.kt
new file mode 100644
index 00000000..d6fad69a
--- /dev/null
+++ b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/AccountRepository.kt
@@ -0,0 +1,32 @@
+package com.sphereon.oid.fed.persistence.repositories
+
+import app.cash.sqldelight.ExecutableQuery
+import com.sphereon.oid.fed.openapi.models.CreateAccountDTO
+import com.sphereon.oid.fed.persistence.models.Account
+import com.sphereon.oid.fed.persistence.models.AccountQueries
+
+class AccountRepository(private val accountQueries: AccountQueries) {
+ fun findById(id: Int): Account? {
+ return accountQueries.findById(id).executeAsOneOrNull()
+ }
+
+ fun findByUsername(username: String): Account? {
+ return accountQueries.findByUsername(username).executeAsOneOrNull()
+ }
+
+ fun create(account: CreateAccountDTO): ExecutableQuery {
+ return accountQueries.create(username = account.username)
+ }
+
+ fun findAll(): List {
+ return accountQueries.findAll().executeAsList()
+ }
+
+ fun delete(id: Int) {
+ return accountQueries.delete(id)
+ }
+
+ fun update(id: Int, account: Account) {
+ return accountQueries.update(account.username, id)
+ }
+}
diff --git a/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/KeyRepository.kt b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/KeyRepository.kt
new file mode 100644
index 00000000..394b74f3
--- /dev/null
+++ b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/KeyRepository.kt
@@ -0,0 +1,44 @@
+package com.sphereon.oid.fed.persistence.repositories
+
+import com.sphereon.oid.fed.openapi.models.Jwk
+import com.sphereon.oid.fed.persistence.models.KeyQueries
+import com.sphereon.oid.fed.persistence.models.Jwk as JwkPersistence
+
+class KeyRepository(private val keyQueries: KeyQueries) {
+ fun findById(id: Int): JwkPersistence? {
+ return keyQueries.findById(id).executeAsOneOrNull()
+ }
+
+ fun create(accountId: Int, jwk: Jwk): JwkPersistence {
+ return keyQueries.create(
+ account_id = accountId,
+ kty = jwk.kty,
+ e = jwk.e,
+ n = jwk.n,
+ x = jwk.x,
+ y = jwk.y,
+ alg = jwk.alg,
+ crv = jwk.crv,
+ kid = jwk.kid,
+ use = jwk.use,
+ x5c = jwk.x5c as Array?,
+ x5t = jwk.x5t,
+ x5u = jwk.x5u,
+ d = jwk.d,
+ p = jwk.p,
+ q = jwk.q,
+ dp = jwk.dp,
+ dq = jwk.dq,
+ qi = jwk.qi,
+ x5t_s256 = jwk.x5tS256
+ ).executeAsOne()
+ }
+
+ fun findByAccountId(accountId: Int): List {
+ return keyQueries.findByAccountId(accountId).executeAsList()
+ }
+
+ fun revokeKey(id: Int, reason: String? = null) {
+ return keyQueries.revoke(reason, id)
+ }
+}
diff --git a/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/SubordinateRepository.kt b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/SubordinateRepository.kt
new file mode 100644
index 00000000..8cf9bc6e
--- /dev/null
+++ b/modules/persistence/src/commonMain/kotlin/com/sphereon/oid/fed/persistence/repositories/SubordinateRepository.kt
@@ -0,0 +1,19 @@
+package com.sphereon.oid.fed.persistence.repositories
+
+import com.sphereon.oid.fed.persistence.models.Subordinate
+import com.sphereon.oid.fed.persistence.models.SubordinateQueries
+
+class SubordinateRepository(private val subordinateQueries: SubordinateQueries) {
+ fun findByAccountId(accountId: Int): List {
+ return subordinateQueries.findByAccountId(accountId).executeAsList()
+ }
+
+ fun create(accountId: Int, subordinateIdentifier: String): Subordinate {
+ return subordinateQueries.create(account_id = accountId, subordinate_identifier = subordinateIdentifier)
+ .executeAsOne()
+ }
+
+ fun delete(id: Int) {
+ return subordinateQueries.delete(id)
+ }
+}
\ No newline at end of file
diff --git a/modules/persistence/src/commonMain/resources/db/migration/1.sql b/modules/persistence/src/commonMain/resources/db/migration/1.sql
new file mode 100644
index 00000000..43de324a
--- /dev/null
+++ b/modules/persistence/src/commonMain/resources/db/migration/1.sql
@@ -0,0 +1,11 @@
+CREATE TABLE account (
+ id SERIAL PRIMARY KEY,
+ username VARCHAR(255) UNIQUE NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted_at TIMESTAMP
+);
+
+CREATE INDEX account_username_index ON account (username);
+
+INSERT INTO account (username) VALUES ('root');
\ No newline at end of file
diff --git a/modules/persistence/src/commonMain/resources/db/migration/2.sql b/modules/persistence/src/commonMain/resources/db/migration/2.sql
new file mode 100644
index 00000000..a41f2a95
--- /dev/null
+++ b/modules/persistence/src/commonMain/resources/db/migration/2.sql
@@ -0,0 +1,30 @@
+CREATE TABLE jwk (
+ id SERIAL PRIMARY KEY,
+ uuid UUID DEFAULT gen_random_uuid(),
+ account_id INT NOT NULL,
+ kty VARCHAR(10) NOT NULL,
+ crv VARCHAR(10),
+ kid VARCHAR(255) UNIQUE,
+ x TEXT,
+ y TEXT,
+ d TEXT,
+ n TEXT,
+ e TEXT,
+ p TEXT,
+ q TEXT,
+ dp TEXT,
+ dq TEXT,
+ qi TEXT,
+ x5u TEXT,
+ x5c TEXT,
+ x5t TEXT,
+ x5t_s256 TEXT,
+ alg VARCHAR(10),
+ use VARCHAR(10) NULL,
+ revoked_at TIMESTAMP,
+ revoked_reason TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT FK_AccountJwk FOREIGN KEY (account_id) REFERENCES account (id)
+);
+
+CREATE INDEX jwk_account_id_index ON jwk (account_id);
\ No newline at end of file
diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/1.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/1.sqm
new file mode 100644
index 00000000..0c59f113
--- /dev/null
+++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/1.sqm
@@ -0,0 +1,11 @@
+CREATE TABLE account (
+ id SERIAL PRIMARY KEY,
+ username VARCHAR(255) UNIQUE NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ deleted_at TIMESTAMP
+);
+
+CREATE INDEX account_username_index ON account (username);
+
+INSERT INTO account (username) VALUES ('root');
diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/2.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/2.sqm
new file mode 100644
index 00000000..7b42cf9e
--- /dev/null
+++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/2.sqm
@@ -0,0 +1,30 @@
+CREATE TABLE jwk (
+ id SERIAL PRIMARY KEY,
+ uuid UUID DEFAULT gen_random_uuid(),
+ account_id INT NOT NULL,
+ kty VARCHAR(10) NOT NULL,
+ crv VARCHAR(10),
+ kid VARCHAR(255) UNIQUE,
+ x TEXT,
+ y TEXT,
+ d TEXT,
+ n TEXT,
+ e TEXT,
+ p TEXT,
+ q TEXT,
+ dp TEXT,
+ dq TEXT,
+ qi TEXT,
+ x5u TEXT,
+ x5c TEXT[],
+ x5t TEXT,
+ x5t_s256 TEXT,
+ alg VARCHAR(10),
+ use VARCHAR(10) NULL,
+ revoked_at TIMESTAMP,
+ revoked_reason TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT FK_AccountJwk FOREIGN KEY (account_id) REFERENCES account (id)
+);
+
+CREATE INDEX jwk_account_id_index ON jwk (account_id);
diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/3.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/3.sqm
new file mode 100644
index 00000000..e8764795
--- /dev/null
+++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/3.sqm
@@ -0,0 +1,12 @@
+CREATE TABLE subordinate (
+ id SERIAL PRIMARY KEY,
+ account_id INT NOT NULL,
+ subordinate_identifier TEXT NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ deleted_at TIMESTAMP,
+ CONSTRAINT FK_ParentSubordinate FOREIGN KEY (account_id) REFERENCES account (id),
+ UNIQUE (account_id, subordinate_identifier)
+);
+
+CREATE INDEX subordinate_account_id_index ON subordinate (account_id);
+CREATE INDEX subordinate_account_id_subordinate_identifier_index ON subordinate (account_id, subordinate_identifier);
diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Account.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Account.sq
new file mode 100644
index 00000000..ed78d03a
--- /dev/null
+++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Account.sq
@@ -0,0 +1,18 @@
+findAll:
+SELECT * FROM account;
+
+create:
+INSERT INTO account (username) VALUES (?) RETURNING *;
+
+delete:
+UPDATE account SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?;
+
+findByUsername:
+SELECT * FROM account WHERE username = ?;
+
+findById:
+SELECT * FROM account WHERE id = ?;
+
+update:
+UPDATE account SET username = ? WHERE id = ?;
+
diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Key.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Key.sq
new file mode 100644
index 00000000..04ff78c0
--- /dev/null
+++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Key.sq
@@ -0,0 +1,33 @@
+create:
+INSERT INTO jwk (
+ account_id,
+ kty,
+ crv,
+ kid,
+ x,
+ y,
+ d,
+ n,
+ e,
+ p,
+ q,
+ dp,
+ dq,
+ qi,
+ x5u,
+ x5c,
+ x5t,
+ x5t_s256,
+ alg,
+ use
+)
+VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *;
+
+revoke:
+UPDATE jwk SET (revoked_at, revoked_reason) = (CURRENT_TIMESTAMP, ?) WHERE id = ?;
+
+findByAccountId:
+SELECT * FROM jwk WHERE account_id = ?;
+
+findById:
+SELECT * FROM jwk WHERE id = ?;
diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Subordinate.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Subordinate.sq
new file mode 100644
index 00000000..af7164d0
--- /dev/null
+++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Subordinate.sq
@@ -0,0 +1,14 @@
+create:
+INSERT INTO subordinate (
+ account_id,
+ subordinate_identifier
+) VALUES (?, ?) RETURNING *;
+
+delete:
+UPDATE subordinate SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL;
+
+findByAccountId:
+SELECT * FROM subordinate WHERE account_id = ? AND deleted_at IS NULL;
+
+findById:
+SELECT * FROM subordinate WHERE 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
new file mode 100644
index 00000000..913b31d5
--- /dev/null
+++ b/modules/persistence/src/jvmMain/kotlin/com.sphereon.oid.fed.persistence/Persistence.jvm.kt
@@ -0,0 +1,66 @@
+package com.sphereon.oid.fed.persistence
+
+import app.cash.sqldelight.db.QueryResult
+import app.cash.sqldelight.db.SqlCursor
+import app.cash.sqldelight.db.SqlDriver
+import com.sphereon.oid.fed.persistence.database.PlatformSqlDriver
+import com.sphereon.oid.fed.persistence.repositories.AccountRepository
+import com.sphereon.oid.fed.persistence.repositories.KeyRepository
+import com.sphereon.oid.fed.persistence.repositories.SubordinateRepository
+
+actual object Persistence {
+ actual val accountRepository: AccountRepository
+ actual val keyRepository: KeyRepository
+ actual val subordinateRepository: SubordinateRepository
+
+ init {
+ val driver = getDriver()
+ runMigrations(driver)
+
+ val database = Database(driver)
+ accountRepository = AccountRepository(database.accountQueries)
+ keyRepository = KeyRepository(database.keyQueries)
+ subordinateRepository = SubordinateRepository(database.subordinateQueries)
+ }
+
+ private fun getDriver(): SqlDriver {
+ return PlatformSqlDriver().createPostgresDriver(
+ System.getenv(Constants.DATASOURCE_URL),
+ System.getenv(Constants.DATASOURCE_USER),
+ System.getenv(Constants.DATASOURCE_PASSWORD)
+ )
+ }
+
+ private fun runMigrations(driver: SqlDriver) {
+ setupSchemaVersioningTable(driver)
+
+ val currentVersion = getCurrentDatabaseVersion(driver)
+ val newVersion = Database.Schema.version
+
+ if (currentVersion < newVersion) {
+ Database.Schema.migrate(driver, currentVersion, newVersion)
+ updateDatabaseVersion(driver, newVersion)
+ }
+ }
+
+ private fun setupSchemaVersioningTable(driver: SqlDriver) {
+ driver.execute(null, "CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)", 0)
+ }
+
+ private fun getCurrentDatabaseVersion(driver: SqlDriver): Long {
+ val versionQuery = "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1"
+
+ val version = driver.executeQuery(null, versionQuery, parameters = 0, mapper = { cursor: SqlCursor ->
+ QueryResult.Value(if (cursor.next().value) cursor.getLong(0) else null)
+ })
+
+ return version.value ?: 0
+ }
+
+ private fun updateDatabaseVersion(driver: SqlDriver, newVersion: Long) {
+ val updateQuery = "INSERT INTO schema_version (version) VALUES (?)"
+ driver.execute(null, updateQuery, 1) {
+ bindLong(0, newVersion)
+ }
+ }
+}
diff --git a/modules/persistence/src/jvmMain/kotlin/com/sphereon/oid/fed/persistence/database/PlatformSqlDriver.jvm.kt b/modules/persistence/src/jvmMain/kotlin/com/sphereon/oid/fed/persistence/database/PlatformSqlDriver.jvm.kt
new file mode 100644
index 00000000..a3c3e667
--- /dev/null
+++ b/modules/persistence/src/jvmMain/kotlin/com/sphereon/oid/fed/persistence/database/PlatformSqlDriver.jvm.kt
@@ -0,0 +1,23 @@
+package com.sphereon.oid.fed.persistence.database
+
+import app.cash.sqldelight.db.SqlDriver
+import app.cash.sqldelight.driver.jdbc.asJdbcDriver
+import com.sphereon.oid.fed.persistence.Constants
+import com.zaxxer.hikari.HikariConfig
+import com.zaxxer.hikari.HikariDataSource
+
+actual class PlatformSqlDriver {
+ actual fun createPostgresDriver(url: String, username: String, password: String): SqlDriver {
+ val config = HikariConfig()
+ config.jdbcUrl = url
+ config.username = username
+ config.password = password
+
+ val dataSource = HikariDataSource(config)
+ return dataSource.asJdbcDriver()
+ }
+
+ actual fun createSqliteDriver(path: String): SqlDriver {
+ throw UnsupportedOperationException(Constants.SQLITE_IS_NOT_SUPPORTED_IN_JVM)
+ }
+}
diff --git a/modules/services/build.gradle.kts b/modules/services/build.gradle.kts
new file mode 100644
index 00000000..92d06037
--- /dev/null
+++ b/modules/services/build.gradle.kts
@@ -0,0 +1,33 @@
+plugins {
+ kotlin("multiplatform") version "2.0.0"
+ kotlin("plugin.serialization") version "2.0.0"
+}
+
+group = "com.sphereon.oid.fed.services"
+version = "0.1.0"
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+ google()
+}
+
+kotlin {
+ jvm()
+
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ api(projects.modules.openapi)
+ api(projects.modules.persistence)
+ api(projects.modules.openidFederationCommon)
+ }
+ }
+
+ val jvmTest by getting {
+ dependencies {
+ implementation(kotlin("test-junit"))
+ }
+ }
+ }
+}
diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/AccountService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/AccountService.kt
new file mode 100644
index 00000000..3adc608d
--- /dev/null
+++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/AccountService.kt
@@ -0,0 +1,24 @@
+package com.sphereon.oid.fed.services
+
+import com.sphereon.oid.fed.openapi.models.AccountDTO
+import com.sphereon.oid.fed.openapi.models.CreateAccountDTO
+import com.sphereon.oid.fed.persistence.Persistence
+import com.sphereon.oid.fed.services.extensions.toAccountDTO
+
+class AccountService {
+ private val accountRepository = Persistence.accountRepository
+
+ fun create(account: CreateAccountDTO): AccountDTO {
+ val accountAlreadyExists = accountRepository.findByUsername(account.username) != null
+
+ if (accountAlreadyExists) {
+ throw IllegalArgumentException(Constants.ACCOUNT_ALREADY_EXISTS)
+ }
+
+ return accountRepository.create(account).executeAsOne().toAccountDTO()
+ }
+
+ fun findAll(): List {
+ return accountRepository.findAll().map { it.toAccountDTO() }
+ }
+}
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
new file mode 100644
index 00000000..4d2511ad
--- /dev/null
+++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt
@@ -0,0 +1,10 @@
+package com.sphereon.oid.fed.services
+
+class Constants {
+ companion object {
+ const val ACCOUNT_ALREADY_EXISTS = "Account already exists"
+ const val ACCOUNT_NOT_FOUND = "Account not found"
+ const val KEY_NOT_FOUND = "Key not found"
+ const val KEY_ALREADY_REVOKED = "Key already revoked"
+ }
+}
diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KeyService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KeyService.kt
new file mode 100644
index 00000000..4d286a0c
--- /dev/null
+++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KeyService.kt
@@ -0,0 +1,63 @@
+package com.sphereon.oid.fed.services
+
+import com.sphereon.oid.fed.common.jwk.generateKeyPair
+import com.sphereon.oid.fed.openapi.models.JwkAdminDTO
+import com.sphereon.oid.fed.persistence.Persistence
+import com.sphereon.oid.fed.persistence.models.Jwk
+import com.sphereon.oid.fed.services.extensions.decrypt
+import com.sphereon.oid.fed.services.extensions.encrypt
+import com.sphereon.oid.fed.services.extensions.toJwkAdminDTO
+
+class KeyService {
+ private val accountRepository = Persistence.accountRepository
+ private val keyRepository = Persistence.keyRepository
+
+ fun create(accountUsername: String): Jwk {
+ val account =
+ accountRepository.findByUsername(accountUsername)
+ ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND)
+
+ val key = keyRepository.create(
+ account.id,
+ generateKeyPair().encrypt()
+ )
+
+ return key
+ }
+
+ fun getDecryptedKey(keyId: Int): Jwk {
+ var key = keyRepository.findById(keyId) ?: throw IllegalArgumentException(Constants.KEY_NOT_FOUND)
+ return key.decrypt()
+ }
+
+ fun getKeys(accountUsername: String): List {
+ val account =
+ accountRepository.findByUsername(accountUsername)
+ ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND)
+ val accountId = account.id
+ return keyRepository.findByAccountId(accountId).map { it.toJwkAdminDTO() }
+ }
+
+ fun revokeKey(accountUsername: String, keyId: Int, reason: String?): JwkAdminDTO {
+ val account =
+ accountRepository.findByUsername(accountUsername)
+ ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND)
+ val accountId = account.id
+
+ var key = keyRepository.findById(keyId) ?: throw IllegalArgumentException(Constants.KEY_NOT_FOUND)
+
+ if (key.account_id != accountId) {
+ throw IllegalArgumentException(Constants.KEY_NOT_FOUND)
+ }
+
+ if (key.revoked_at != null) {
+ throw IllegalArgumentException(Constants.KEY_ALREADY_REVOKED)
+ }
+
+ keyRepository.revokeKey(keyId, reason)
+
+ key = keyRepository.findById(keyId) ?: throw IllegalArgumentException(Constants.KEY_NOT_FOUND)
+
+ return key.toJwkAdminDTO()
+ }
+}
diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/SubordinateService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/SubordinateService.kt
new file mode 100644
index 00000000..d517598c
--- /dev/null
+++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/SubordinateService.kt
@@ -0,0 +1,21 @@
+package com.sphereon.oid.fed.services
+
+import com.sphereon.oid.fed.persistence.Persistence
+import com.sphereon.oid.fed.persistence.models.Subordinate
+
+class SubordinateService {
+ private val accountRepository = Persistence.accountRepository
+ private val subordinateRepository = Persistence.subordinateRepository
+
+ fun findSubordinatesByAccount(accountUsername: String): List {
+ val account = accountRepository.findByUsername(accountUsername)
+ ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND)
+
+ return subordinateRepository.findByAccountId(account.id)
+ }
+
+ fun findSubordinatesByAccountAsList(accountUsername: String): List {
+ val subordinates = findSubordinatesByAccount(accountUsername)
+ return subordinates.map { it.subordinate_identifier }
+ }
+}
diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/AccountExtensions.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/AccountExtensions.kt
new file mode 100644
index 00000000..65d6dc90
--- /dev/null
+++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/AccountExtensions.kt
@@ -0,0 +1,10 @@
+package com.sphereon.oid.fed.services.extensions
+
+import com.sphereon.oid.fed.openapi.models.AccountDTO
+import com.sphereon.oid.fed.persistence.models.Account
+
+fun Account.toAccountDTO(): AccountDTO {
+ return AccountDTO(
+ username = this.username
+ )
+}
diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.kt
new file mode 100644
index 00000000..09e3c862
--- /dev/null
+++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.kt
@@ -0,0 +1,61 @@
+package com.sphereon.oid.fed.services.extensions
+
+import com.sphereon.oid.fed.openapi.models.Jwk
+import com.sphereon.oid.fed.openapi.models.JwkAdminDTO
+import com.sphereon.oid.fed.persistence.models.Jwk as JwkPersistence
+
+fun JwkPersistence.toJwkAdminDTO(): JwkAdminDTO = JwkAdminDTO(
+ id = id,
+ accountId = account_id,
+ uuid = uuid.toString(),
+ e = e,
+ n = n,
+ x = x,
+ y = y,
+ alg = alg,
+ crv = crv,
+ kid = kid,
+ kty = kty,
+ use = use,
+ x5c = x5c as? List ?: null,
+ x5t = x5t,
+ x5u = x5u,
+ x5tHashS256 = x5t_s256,
+ createdAt = created_at.toString(),
+ revokedAt = revoked_at.toString(),
+ revokedReason = revoked_reason
+)
+
+fun Jwk.encrypt(): Jwk {
+ if (System.getenv("APP_KEY") == null) return this
+
+ fun String?.encryptOrNull() = this?.let { aesEncrypt(it, System.getenv("APP_KEY")) }
+
+ return copy(
+ d = d.encryptOrNull(),
+ dq = dq.encryptOrNull(),
+ qi = qi.encryptOrNull(),
+ dp = dp.encryptOrNull(),
+ p = p.encryptOrNull(),
+ q = q.encryptOrNull()
+ )
+}
+
+fun JwkPersistence.decrypt(): JwkPersistence {
+ if (System.getenv("APP_KEY") == null) return this
+
+ fun String?.decryptOrNull() = this?.let { aesDecrypt(it, System.getenv("APP_KEY")) }
+
+ return copy(
+ d = d.decryptOrNull(),
+ dq = dq.decryptOrNull(),
+ qi = qi.decryptOrNull(),
+ dp = dp.decryptOrNull(),
+ p = p.decryptOrNull(),
+ q = q.decryptOrNull()
+ )
+}
+
+expect fun aesEncrypt(data: String, key: String): String
+expect fun aesDecrypt(data: String, key: String): String
+
diff --git a/modules/services/src/jsMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.js.kt b/modules/services/src/jsMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.js.kt
new file mode 100644
index 00000000..01f6164c
--- /dev/null
+++ b/modules/services/src/jsMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.js.kt
@@ -0,0 +1,9 @@
+package com.sphereon.oid.fed.services.extensions
+
+actual fun aesEncrypt(data: String): String {
+ return data
+}
+
+actual fun aesDecrypt(data: String): String {
+ return data
+}
\ No newline at end of file
diff --git a/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.jvm.kt b/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.jvm.kt
new file mode 100644
index 00000000..9aa632c6
--- /dev/null
+++ b/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.jvm.kt
@@ -0,0 +1,29 @@
+package com.sphereon.oid.fed.services.extensions
+
+import java.util.*
+import javax.crypto.Cipher
+import javax.crypto.spec.SecretKeySpec
+
+private const val ALGORITHM = "AES"
+private const val KEY_SIZE = 32
+
+actual fun aesEncrypt(data: String, key: String): String {
+ val secretKey = SecretKeySpec(key.padEnd(KEY_SIZE, '0').toByteArray(Charsets.UTF_8), ALGORITHM)
+
+ val cipher = Cipher.getInstance(ALGORITHM)
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey)
+
+ val encryptedValue = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
+ return Base64.getEncoder().encodeToString(encryptedValue)
+}
+
+actual fun aesDecrypt(data: String, key: String): String {
+ val secretKey = SecretKeySpec(key.padEnd(KEY_SIZE, '0').toByteArray(Charsets.UTF_8), ALGORITHM)
+
+ val cipher = Cipher.getInstance(ALGORITHM)
+ cipher.init(Cipher.DECRYPT_MODE, secretKey)
+
+ val decodedValue = Base64.getDecoder().decode(data)
+ val decryptedValue = cipher.doFinal(decodedValue)
+ return String(decryptedValue, Charsets.UTF_8)
+}
diff --git a/modules/services/src/jvmTest/kotlin/com/sphereon/oid/fed/services/KeyServiceTest.jvm.kt b/modules/services/src/jvmTest/kotlin/com/sphereon/oid/fed/services/KeyServiceTest.jvm.kt
new file mode 100644
index 00000000..dac55489
--- /dev/null
+++ b/modules/services/src/jvmTest/kotlin/com/sphereon/oid/fed/services/KeyServiceTest.jvm.kt
@@ -0,0 +1,57 @@
+package com.sphereon.oid.fed.services
+
+import com.sphereon.oid.fed.common.jwk.generateKeyPair
+import com.sphereon.oid.fed.services.extensions.decrypt
+import com.sphereon.oid.fed.services.extensions.encrypt
+import org.junit.Test
+import java.time.LocalDateTime
+import java.util.*
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import com.sphereon.oid.fed.persistence.models.Jwk as JwkPersistence
+
+class KeyServiceTest {
+ @Test
+ fun testEncryption() {
+ val key = generateKeyPair()
+ val encryptedKey = key.encrypt()
+
+ if (System.getenv("APP_KEY") == null) {
+ assertEquals(key.d, encryptedKey.d)
+ } else {
+ assertNotEquals(key.d, encryptedKey.d)
+ }
+
+ val persistenceJwk = JwkPersistence(
+ id = 1,
+ account_id = 1,
+ d = encryptedKey.d,
+ e = encryptedKey.e,
+ n = encryptedKey.n,
+ x = encryptedKey.x,
+ y = encryptedKey.y,
+ alg = encryptedKey.alg,
+ crv = encryptedKey.crv,
+ p = encryptedKey.p,
+ q = encryptedKey.q,
+ dp = encryptedKey.dp,
+ qi = encryptedKey.qi,
+ dq = encryptedKey.dq,
+ x5t = encryptedKey.x5t,
+ x5t_s256 = encryptedKey.x5tS256,
+ x5u = encryptedKey.x5u,
+ kid = encryptedKey.kid,
+ kty = encryptedKey.kty,
+ x5c = encryptedKey.x5c?.toTypedArray(),
+ created_at = LocalDateTime.now(),
+ revoked_reason = null,
+ revoked_at = null,
+ uuid = UUID.randomUUID(),
+ use = encryptedKey.use
+ )
+
+ val decryptedPersistenceJwk = persistenceJwk.decrypt()
+
+ assertEquals(key.d, decryptedPersistenceJwk.d)
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index bd06755a..bff086b8 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -44,4 +44,7 @@ dependencyResolutionManagement {
include(":modules:openid-federation-common")
include(":modules:admin-server")
+include(":modules:federation-server")
include(":modules:openapi")
+include(":modules:persistence")
+include(":modules:services")