Skip to content

Commit

Permalink
feat: implement fetch endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jcmelati committed Sep 3, 2024
2 parents f88a406 + 8811eb3 commit 6e88c66
Show file tree
Hide file tree
Showing 18 changed files with 396 additions and 17 deletions.
9 changes: 2 additions & 7 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,5 @@ DATASOURCE_URL=jdbc:postgresql://db:5432/openid-federation-db
DATASOURCE_USER=openid-federation-db-user
DATASOURCE_PASSWORD=openid-federation-db-password
DATASOURCE_DB=openid-federation-db

KMS_PROVIDER=local

LOCAL_KMS_DATASOURCE_URL=jdbc:postgresql://local-kms-db:5432/openid-federation-local-kms-db
LOCAL_KMS_DATASOURCE_USER=openid-federation-local-kms-db-user
LOCAL_KMS_DATASOURCE_PASSWORD=openid-federation-local-kms-db-password
LOCAL_KMS_DATASOURCE_DB=openid-federation-local-kms-db
APP_KEY=Nit5tWts42QeCynT1Q476LyStDeSd4xb
ROOT_IDENTIFIER=http://localhost:8080
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package com.sphereon.oid.fed.server.admin.controllers

import com.sphereon.oid.fed.openapi.models.CreateSubordinateDTO
import com.sphereon.oid.fed.openapi.models.SubordinateAdminDTO
import com.sphereon.oid.fed.openapi.models.SubordinateStatement
import com.sphereon.oid.fed.persistence.models.Subordinate
import com.sphereon.oid.fed.persistence.models.SubordinateJwk
import com.sphereon.oid.fed.services.SubordinateService
import com.sphereon.oid.fed.services.extensions.toSubordinateAdminDTO
import kotlinx.serialization.json.JsonObject
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
Expand All @@ -30,4 +34,47 @@ class SubordinateController {
): Subordinate {
return subordinateService.createSubordinate(accountUsername, subordinate)
}

@PostMapping("/{id}/jwks")
fun createSubordinateJwk(
@PathVariable accountUsername: String,
@PathVariable id: Int,
@RequestBody jwk: JsonObject
): SubordinateJwk {
return subordinateService.createSubordinateJwk(accountUsername, id, jwk)
}

@GetMapping("/{id}/jwks")
fun getSubordinateJwks(
@PathVariable accountUsername: String,
@PathVariable id: Int
): Array<SubordinateJwk> {
return subordinateService.getSubordinateJwks(accountUsername, id)
}

@DeleteMapping("/{id}/jwks/{jwkId}")
fun deleteSubordinateJwk(
@PathVariable accountUsername: String,
@PathVariable id: Int,
@PathVariable jwkId: Int
) {
subordinateService.deleteSubordinateJwk(accountUsername, id, jwkId)
}

@GetMapping("/{id}/statement")
fun getSubordinateStatement(
@PathVariable accountUsername: String,
@PathVariable id: Int
): SubordinateStatement {
return subordinateService.getSubordinateStatement(accountUsername, id)
}

@PostMapping("/{id}/statement")
fun publishSubordinateStatement(
@PathVariable accountUsername: String,
@PathVariable id: Int,
@RequestBody dryRun: Boolean?
): String {
return subordinateService.publishSubordinateStatement(accountUsername, id, dryRun)
}
}
1 change: 0 additions & 1 deletion modules/federation-server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sphereon.oid.fed.server.federation

class Constants {
companion object {
const val SUBORDINATE_STATEMENT_NOT_FOUND = "Subordinate Statement not found"
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.sphereon.oid.fed.server.federation.controllers

import com.sphereon.oid.fed.persistence.Persistence
import com.sphereon.oid.fed.services.SubordinateService
import com.sphereon.oid.fed.server.federation.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.RequestParam
import org.springframework.web.bind.annotation.RestController


Expand Down Expand Up @@ -48,7 +49,12 @@ class FederationController {
}

@GetMapping("/fetch")
fun getSubordinateStatement(): List<String> {
throw NotImplementedError()
fun getRootSubordinateStatement(@RequestParam("iss") iss: String, @RequestParam("sub") sub: String): String {
return subordinateService.fetchSubordinateStatement(iss, sub)
}

@GetMapping("/{username}/fetch")
fun getSubordinateStatement(@RequestParam("iss") iss: String, @RequestParam("sub") sub: String): String {
return subordinateService.fetchSubordinateStatement(iss, sub)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.sphereon.oid.fed.server.federation.services

import com.sphereon.oid.fed.persistence.Persistence
import com.sphereon.oid.fed.persistence.Persistence.subordinateStatementQueries
import com.sphereon.oid.fed.persistence.models.Subordinate
import com.sphereon.oid.fed.server.federation.Constants


class SubordinateService {
private val accountQueries = Persistence.accountQueries
private val subordinateQueries = Persistence.subordinateQueries

private fun findSubordinatesByAccount(accountUsername: String): Array<Subordinate> {
val account = accountQueries.findByUsername(accountUsername).executeAsOne()

return subordinateQueries.findByAccountId(account.id).executeAsList().toTypedArray()
}

fun findSubordinatesByAccountAsArray(accountUsername: String): Array<String> {
val subordinates = findSubordinatesByAccount(accountUsername)
return subordinates.map { it.identifier }.toTypedArray()
}

fun fetchSubordinateStatement(iss: String, sub: String): String {
val subordinateStatement = subordinateStatementQueries.findByIssAndSub(iss, sub).executeAsOneOrNull()
?: throw IllegalArgumentException(Constants.SUBORDINATE_STATEMENT_NOT_FOUND)

return subordinateStatement.statement
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1883,7 +1883,7 @@ components:
jwks:
additionalProperties: true
metadata:
type: object
additionalProperties: true
crit:
type: array
items:
Expand Down Expand Up @@ -1969,17 +1969,15 @@ components:
- jwks
properties:
metadata_policy:
$ref: '#/components/schemas/MetadataPolicy'
additionalProperties: true
constraints:
$ref: '#/components/schemas/Constraint'
additionalProperties: true
crit:
type: array
items:
type: string
metadata_policy_crit:
type: array
items:
type: string
additionalProperties: true
source_endpoint:
type: string
format: uri
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.sphereon.oid.fed.common.builder

import com.sphereon.oid.fed.openapi.models.JwkDTO
import com.sphereon.oid.fed.openapi.models.SubordinateStatement
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.ArraySerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject

class SubordinateStatementBuilder {
private var iss: String? = null
private var sub: String? = null
private var exp: Int? = null
private var iat: Int? = null
private lateinit var jwks: Array<JwkDTO>
private var metadata: MutableMap<String, JsonObject> = mutableMapOf()
private var metadata_policy: MutableMap<String, JsonObject> = mutableMapOf()
private var metadata_policy_crit: MutableMap<String, JsonObject> = mutableMapOf()
private var constraints: MutableMap<String, JsonObject> = mutableMapOf()
private val crit: MutableList<String> = mutableListOf()
private var source_endpoint: String? = null

fun iss(iss: String) = apply { this.iss = iss }
fun sub(sub: String) = apply { this.sub = sub }
fun exp(exp: Int) = apply { this.exp = exp }
fun iat(iat: Int) = apply { this.iat = iat }
fun jwks(jwks: JwkDTO) = apply { this.jwks = arrayOf(jwks) }

fun metadata(metadata: Pair<String, JsonObject>) = apply {
this.metadata[metadata.first] = metadata.second
}

fun metadataPolicy(metadataPolicy: Pair<String, JsonObject>) = apply {
this.metadata_policy[metadataPolicy.first] = metadataPolicy.second
}

fun metadataPolicyCrit(metadataPolicyCrit: Pair<String, JsonObject>) = apply {
this.metadata_policy_crit[metadataPolicyCrit.first] = metadataPolicyCrit.second
}

fun crit(claim: String) = apply {
this.crit.add(claim)
}

fun sourceEndpoint(sourceEndpoint: String) = apply {
this.source_endpoint = sourceEndpoint
}

@OptIn(ExperimentalSerializationApi::class)
private fun createJwks(jwks: Array<JwkDTO>): JsonObject {
val jsonArray: JsonArray =
Json.encodeToJsonElement(ArraySerializer(JwkDTO.serializer()), jwks) as JsonArray

return buildJsonObject {
put("keys", jsonArray)
}
}

fun build(): SubordinateStatement {
return SubordinateStatement(
iss = iss ?: throw IllegalArgumentException("iss must be provided"),
sub = sub ?: throw IllegalArgumentException("sub must be provided"),
exp = exp ?: throw IllegalArgumentException("exp must be provided"),
iat = iat ?: throw IllegalArgumentException("iat must be provided"),
jwks = createJwks(jwks),
crit = if (crit.isNotEmpty()) crit.toTypedArray() else null,
metadata = JsonObject(metadata),
metadataPolicy = JsonObject(metadata_policy),
metadataPolicyCrit = JsonObject(metadata_policy_crit),
constraints = JsonObject(constraints),
sourceEndpoint = source_endpoint,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import com.sphereon.oid.fed.persistence.models.CritQueries
import com.sphereon.oid.fed.persistence.models.EntityConfigurationMetadataQueries
import com.sphereon.oid.fed.persistence.models.EntityConfigurationStatementQueries
import com.sphereon.oid.fed.persistence.models.KeyQueries
import com.sphereon.oid.fed.persistence.models.SubordinateJwkQueries
import com.sphereon.oid.fed.persistence.models.SubordinateQueries
import com.sphereon.oid.fed.persistence.models.SubordinateStatementQueries

expect object Persistence {
val entityConfigurationStatementQueries: EntityConfigurationStatementQueries
Expand All @@ -16,4 +18,6 @@ expect object Persistence {
val entityConfigurationMetadataQueries: EntityConfigurationMetadataQueries
val authorityHintQueries: AuthorityHintQueries
val critQueries: CritQueries
val subordinateStatementQueries: SubordinateStatementQueries
val subordinateJwkQueries: SubordinateJwkQueries
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE SubordinateStatement (
id SERIAL PRIMARY KEY,
subordinate_id INT NOT NULL,
iss TEXT NOT NULL,
sub TEXT NOT NULL,
statement TEXT NOT NULL,
expires_at BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_ParentSubordinateStatement FOREIGN KEY (subordinate_id) REFERENCES Subordinate (id)
);

CREATE INDEX subordinate_statement_account_id_index ON SubordinateStatement (subordinate_id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE SubordinateJwk (
id SERIAL PRIMARY KEY,
subordinate_id INT NOT NULL,
key TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP,
CONSTRAINT FK_ParentSubordinateJwk FOREIGN KEY (subordinate_id) REFERENCES Subordinate (id)
);

CREATE INDEX subordinate_jwk_account_id_index ON SubordinateJwk (subordinate_id);
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ SELECT * FROM Subordinate WHERE account_id = ? AND deleted_at IS NULL;
findByAccountIdAndIdentifier:
SELECT * FROM Subordinate WHERE account_id = ? AND identifier = ? AND deleted_at IS NULL;

findPublishedByAccountIdAndIdentifier:
SELECT * FROM Subordinate WHERE account_id = ? AND identifier = ? AND deleted_at IS NULL;

findById:
SELECT * FROM Subordinate WHERE id = ? AND deleted_at IS NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
findBySubordinateId:
SELECT * FROM SubordinateJwk WHERE subordinate_id = ?;

findById:
SELECT * FROM SubordinateJwk WHERE id = ?;

create:
INSERT INTO SubordinateJwk (
subordinate_id,
key
) VALUES (?, ?) RETURNING *;

delete:
UPDATE SubordinateJwk SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING *;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
findBySubordinateId:
SELECT * FROM SubordinateStatement WHERE subordinate_id = ?;

findById:
SELECT * FROM SubordinateStatement WHERE id = ?;

create:
INSERT INTO SubordinateStatement (
subordinate_id,
iss,
sub,
statement,
expires_at
) VALUES (?, ?, ?, ?, ?) RETURNING *;

findLatestBySubordinateId:
SELECT * FROM SubordinateStatement WHERE subordinate_id = ? ORDER BY id DESC LIMIT 1;

findPublishedByAccountId:
SELECT s.*
FROM Subordinate s
JOIN SubordinateStatement ss ON ss.subordinate_id = s.id
WHERE s.account_id = ?
AND s.deleted_at IS NULL;

findByIssAndSub:
SELECT * FROM SubordinateStatement WHERE iss = ? AND sub = ? ORDER BY id DESC LIMIT 1;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import com.sphereon.oid.fed.persistence.models.CritQueries
import com.sphereon.oid.fed.persistence.models.EntityConfigurationMetadataQueries
import com.sphereon.oid.fed.persistence.models.EntityConfigurationStatementQueries
import com.sphereon.oid.fed.persistence.models.KeyQueries
import com.sphereon.oid.fed.persistence.models.SubordinateJwkQueries
import com.sphereon.oid.fed.persistence.models.SubordinateQueries
import com.sphereon.oid.fed.persistence.models.SubordinateStatementQueries

actual object Persistence {
actual val entityConfigurationStatementQueries: EntityConfigurationStatementQueries
Expand All @@ -20,6 +22,8 @@ actual object Persistence {
actual val entityConfigurationMetadataQueries: EntityConfigurationMetadataQueries
actual val authorityHintQueries: AuthorityHintQueries
actual val critQueries: CritQueries
actual val subordinateStatementQueries: SubordinateStatementQueries
actual val subordinateJwkQueries: SubordinateJwkQueries

init {
val driver = getDriver()
Expand All @@ -33,6 +37,8 @@ actual object Persistence {
entityConfigurationMetadataQueries = database.entityConfigurationMetadataQueries
authorityHintQueries = database.authorityHintQueries
critQueries = database.critQueries
subordinateStatementQueries = database.subordinateStatementQueries
subordinateJwkQueries = database.subordinateJwkQueries
}

private fun getDriver(): SqlDriver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ class Constants {
const val CRIT_NOT_FOUND = "Crit not found"
const val FAILED_TO_DELETE_CRIT = "Failed to delete crit"
const val NO_KEYS_FOUND = "No keys found"
const val SUBORDINATE_NOT_FOUND = "Subordinate not found"
const val SUBORDINATE_JWK_NOT_FOUND = "Subordinate JWK not found"
const val SUBORDINATE_STATEMENT_NOT_FOUND = "Subordinate statement not found"
}
}
Loading

0 comments on commit 6e88c66

Please sign in to comment.