Skip to content

Commit

Permalink
feat: implement subordinate relationship create
Browse files Browse the repository at this point in the history
  • Loading branch information
jcmelati committed Aug 21, 2024
1 parent c37d975 commit 35b8e90
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
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.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
import com.sphereon.oid.fed.services.extensions.toSubordinateAdminDTO
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/accounts/{accountUsername}/subordinates")
class SubordinateController {
private val subordinateService = SubordinateService()

@GetMapping
fun getSubordinates(@PathVariable accountUsername: String): List<Subordinate> {
return subordinateService.findSubordinatesByAccount(accountUsername)
fun getSubordinates(@PathVariable accountUsername: String): Array<SubordinateAdminDTO> {
return subordinateService.findSubordinatesByAccount(accountUsername).map { it.toSubordinateAdminDTO() }
.toTypedArray()
}

@PostMapping
fun createSubordinate(
@PathVariable accountUsername: String,
@RequestBody subordinate: CreateSubordinateDTO
): Subordinate {
return subordinateService.createSubordinate(accountUsername, subordinate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,81 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'

/accounts/{accountUsername}/subordinates:
post:
tags:
- Account Admin
- Account User
summary: Create a new Subordinate entry
description: Create a new Subordinate relationship.
parameters:
- name: accountUsername
in: path
required: true
schema:
type: string
description: The username of the tenant account.
requestBody:
description: Subordinate data
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSubordinateDTO'
responses:
'201':
description: Subordinate relationship created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SubordinateAdminDTO'
'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 Subordinates
description: List all active Subordinates 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 list of Subordinates
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/SubordinateAdminDTO'
'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:
Expand Down Expand Up @@ -3399,6 +3474,50 @@ components:
required:
- username

CreateSubordinateDTO:
type: object
properties:
identifier:
type: string
description: The identifier of the subordinate account.
example: https://www.sphereon.com/subordinate
required:
- identifier

SubordinateAdminDTO:
type: object
properties:
id:
type: integer
format: int32
description: The unique identifier of the subordinate.
example: 123
accountId:
type: integer
format: int32
description: The ID of the account associated with this subordinate.
example: 456
identifier:
type: string
description: The unique identifier for the subordinate.
example: https://www.sphereon.com/subordinate
createdAt:
type: string
format: date-time
description: The timestamp when the subordinate was created.
example: 2023-08-21T14:52:00Z
deletedAt:
type: string
format: date-time
nullable: true
description: The timestamp when the subordinate was deleted, if applicable.
example: 2024-08-21T14:52:00Z
required:
- id
- accountId
- identifier
- createdAt

AddUserToAccountRequest:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.sphereon.oid.fed.common.builder

import com.sphereon.oid.fed.openapi.models.FederationEntityMetadata

class FederationEntityMetadataBuilder {
private var identifier: String? = null

fun identifier(identifier: String) = apply { this.identifier = identifier }

fun build(): FederationEntityMetadata {
return FederationEntityMetadata(
federationListEndpoint = "${identifier}/list",
federationFetchEndpoint = "${identifier}/fetch"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class SubordinateRepository(private val subordinateQueries: SubordinateQueries)
return subordinateQueries.findByAccountId(accountId).executeAsList()
}

fun create(accountId: Int, subordinateIdentifier: String): Subordinate {
return subordinateQueries.create(account_id = accountId, subordinate_identifier = subordinateIdentifier)
fun create(accountId: Int, identifier: String): Subordinate {
return subordinateQueries.create(account_id = accountId, identifier)
.executeAsOne()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
CREATE TABLE subordinate (
id SERIAL PRIMARY KEY,
account_id INT NOT NULL,
subordinate_identifier TEXT NOT NULL,
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)
UNIQUE (account_id, 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);
CREATE INDEX subordinate_account_id_subordinate_identifier_index ON subordinate (account_id, identifier);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
create:
INSERT INTO subordinate (
account_id,
subordinate_identifier
identifier
) VALUES (?, ?) RETURNING *;

delete:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ class Constants {
const val ACCOUNT_NOT_FOUND = "Account not found"
const val KEY_NOT_FOUND = "Key not found"
const val KEY_ALREADY_REVOKED = "Key already revoked"
const val SUBORDINATE_ALREADY_EXISTS = "Subordinate already exists"
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
package com.sphereon.oid.fed.services

import com.sphereon.oid.fed.common.builder.EntityConfigurationStatementBuilder
import com.sphereon.oid.fed.common.builder.FederationEntityMetadataBuilder
import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
import com.sphereon.oid.fed.openapi.models.FederationEntityMetadata
import com.sphereon.oid.fed.services.extensions.toJwkDTO
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject

class EntityStatementService {
private val keyService = KeyService()
private val subordinateService = SubordinateService()

fun findByUsername(accountUsername: String): EntityConfigurationStatement {
val metadata = Pair(
"federation_entity", Json.parseToJsonElement(
"{\n" +
" \"federation_fetch_endpoint\": \"https://www.sphereon.com/fetch\",\n" +
" \"federation_resolve_endpoint\": \"https://www.sphereon.com/resolve\",\n" +
" \"federation_list_endpoint\": \"https://www.sphereon.com/list\"\n" +
" }"
).jsonObject
)

val keys = keyService.getKeys(accountUsername).map { it.toJwkDTO() }.toTypedArray()

val hasSubordinates = subordinateService.findSubordinatesByAccount(accountUsername).isNotEmpty()
println(hasSubordinates);

val entityConfigurationStatement = EntityConfigurationStatementBuilder()
.iss("https://www.sphereon.com")
.iat((System.currentTimeMillis() / 1000).toInt())
.exp((System.currentTimeMillis() / 1000 + 3600 * 24 * 365).toInt())
.metadata(
metadata
)
.jwks(keys)
.build()

return entityConfigurationStatement
if (hasSubordinates) {
val federationEntityMetadata = FederationEntityMetadataBuilder()
.identifier(accountUsername)
.build()

println(federationEntityMetadata);

entityConfigurationStatement.metadata(
Pair(
"federation_entity",
Json.encodeToJsonElement(FederationEntityMetadata.serializer(), federationEntityMetadata).jsonObject
)
)
}

return entityConfigurationStatement.build()
}

fun publishByUsername(accountUsername: String): EntityConfigurationStatement {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sphereon.oid.fed.services

import com.sphereon.oid.fed.openapi.models.CreateSubordinateDTO
import com.sphereon.oid.fed.persistence.Persistence
import com.sphereon.oid.fed.persistence.models.Subordinate

Expand All @@ -16,6 +17,20 @@ class SubordinateService {

fun findSubordinatesByAccountAsList(accountUsername: String): List<String> {
val subordinates = findSubordinatesByAccount(accountUsername)
return subordinates.map { it.subordinate_identifier }
return subordinates.map { it.identifier }
}

fun createSubordinate(accountUsername: String, subordinateDTO: CreateSubordinateDTO): Subordinate {
val account = accountRepository.findByUsername(accountUsername)
?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND)

val subordinateAlreadyExists = subordinateRepository.findByAccountId(account.id)
.any { it.identifier == subordinateDTO.identifier }

if (subordinateAlreadyExists) {
throw IllegalArgumentException(Constants.SUBORDINATE_ALREADY_EXISTS)
}

return subordinateRepository.create(account.id, subordinateDTO.identifier)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.sphereon.oid.fed.services.extensions

import com.sphereon.oid.fed.openapi.models.SubordinateAdminDTO
import com.sphereon.oid.fed.persistence.models.Subordinate

fun Subordinate.toSubordinateAdminDTO(): SubordinateAdminDTO {
return SubordinateAdminDTO(
id = this.id,
accountId = this.account_id,
identifier = this.identifier,
createdAt = this.created_at.toString(),
)
}

0 comments on commit 35b8e90

Please sign in to comment.