Skip to content

Commit

Permalink
Merge pull request #671 from walt-id/feat/optional-defaults
Browse files Browse the repository at this point in the history
Feat/optional registration defaults
  • Loading branch information
mikeplotean authored Aug 7, 2024
2 parents f1a676f + 941a896 commit 10f01b4
Show file tree
Hide file tree
Showing 16 changed files with 127 additions and 53 deletions.
4 changes: 2 additions & 2 deletions docker-compose/wallet-api/config/_features.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
enabledFeatures = [

]
"registration-defaults",
]
1 change: 1 addition & 0 deletions waltid-services/waltid-e2e-tests/config/_features.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ enabledFeatures = [
# entra,
# lsp-potential
# ...
"registration-defaults",
]

disabledFeatures = [
Expand Down
43 changes: 43 additions & 0 deletions waltid-services/waltid-e2e-tests/config/registration-defaults.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Setup what key should be generated on registration
defaultKeyConfig: {
backend: jwk
keyType: Ed25519
}

// Setup what DID should be generated based on above above defined key on registration
defaultDidConfig: {
method: jwk
}

// -- Hashicorp Vault TSE key example --
// defaultKeyConfig: {
// backend: tse
// config: {
// server: "http://127.0.0.1:8200/v1/transit"
// accessKey: "<your token here>"
// }
// keyType: Ed25519
// }

// -- Oracle Cloud Infrastructure Vault KMS key example --
// defaultKeyConfig: {
// backend: oci
// config: {
// tenancyOcid: "ocid1.tenancy.oc1..<long id>",
// userOcid: "ocid1.user.oc1..<long id>",
// fingerprint: "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
// cryptoEndpoint: "<some id>-crypto.kms.<the location>.oraclecloud.com",
// managementEndpoint: "<some id>-management.kms.<the location>.oraclecloud.com",
// signingKeyPem: "<private request signing key in PEM format>"
// }
// keyType: secp256r1
// }

// -- did:web example --
// defaultDidConfig: {
// method: web
// config: {
// domain: "https://wallet.walt.id"
// path: "/wallet-api/registry/[random-uuid]" // automatically generates random UUID for path
// }
// }
2 changes: 2 additions & 0 deletions waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class E2ETest {

//region -Keys-
val keysApi = KeysApi(client)
// requires registration-defaults to be enabled in _features.conf
val defaultKeyConfig = ConfigManager.getConfig<RegistrationDefaultsConfig>().defaultKeyConfig
val keyGenRequest = KeyGenerationRequest("jwk", KeyType.Ed25519)
lateinit var generatedKeyId: String
Expand All @@ -84,6 +85,7 @@ class E2ETest {
assert(it.first().default)
did = it.first().did
}
//todo: test for optional registration defaults
didsApi.create(wallet, DidsApi.DidCreateRequest(method = "key", options = mapOf("useJwkJcsPub" to false))) {
createdDids.add(it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,6 @@ object E2ETestWebService {
URLDecoder.decode(object {}.javaClass.getResource(relativePath)!!.path, "UTF-8").let { File(it).readText() }
}

typealias TestFunctionType = (String, suspend() -> Any?) -> Unit

private fun Application.e2eTestModule() {
webWalletModule(true)
issuerModule(false)
Expand Down
11 changes: 11 additions & 0 deletions waltid-services/waltid-e2e-tests/src/test/kotlin/KeyAssertions.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import id.walt.crypto.keys.KeyGenerationRequest
import id.walt.crypto.keys.KeyType
import id.walt.webwallet.service.keys.SingleKeyResponse
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonPrimitive
import kotlin.test.assertNotNull
Expand Down Expand Up @@ -52,4 +54,13 @@ fun assertRSAKeyComponents(document: JsonElement, isPrivate: Boolean) {
assertNotNull(document.tryGetData("dp")?.jsonPrimitive?.content) { "Missing _dp_ component!" }
assertNotNull(document.tryGetData("dq")?.jsonPrimitive?.content) { "Missing _dq_ component!" }
}
}

fun assertDefaultKey(listing: List<SingleKeyResponse>, default: KeyGenerationRequest) {
assert(listing.isNotEmpty()) { "No default key was created!" }
assert(KeyType.valueOf(listing[0].algorithm) == default.keyType) { "Default key type not ${default.keyType}" }
}

fun assertNoDefaultKey(listing: List<SingleKeyResponse>) {
assert(listing.isEmpty()) { "Expected no default key!" }
}
9 changes: 5 additions & 4 deletions waltid-services/waltid-e2e-tests/src/test/kotlin/KeysApi.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import E2ETestWebService.test
import id.walt.crypto.keys.KeyGenerationRequest
import id.walt.crypto.keys.KeyType
import id.walt.webwallet.service.keys.SingleKeyResponse
import io.ktor.client.*
import io.ktor.client.call.*
Expand All @@ -13,12 +12,14 @@ import kotlin.test.assertNotNull

class KeysApi(private val client: HttpClient) {

suspend fun list(wallet: UUID, expected: KeyGenerationRequest) =
suspend fun list(wallet: UUID, expected: KeyGenerationRequest?) =
test("/wallet-api/wallet/{wallet}/keys - get keys") {
client.get("/wallet-api/wallet/$wallet/keys").expectSuccess().apply {
val listing = body<List<SingleKeyResponse>>()
assert(listing.isNotEmpty()) { "No default key was created!" }
assert(KeyType.valueOf(listing[0].algorithm) == expected.keyType) { "Default key type not ${expected.keyType}" }
when (expected) {
null -> assertNoDefaultKey(listing)
else -> assertDefaultKey(listing, expected)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import id.walt.commons.config.ConfigManager
import id.walt.commons.config.ConfigurationException
import id.walt.commons.config.statics.RunConfiguration
import io.klogging.logger
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess

object FeatureManager {
Expand Down Expand Up @@ -61,22 +62,25 @@ object FeatureManager {
/**
* Run block if provided feature is enabled
*/
fun runIfEnabled(feature: OptionalFeature, block: () -> Any?) {
if (isFeatureEnabled(feature)) block.invoke()
private fun <T> runIfEnabledBlocking(feature: OptionalFeature, block: () -> T?): T? = runBlocking {
runIfEnabled(feature, block)
}

private suspend fun <T> runIfEnabled(feature: OptionalFeature, block: suspend () -> T?): T? =
feature.takeIf { isFeatureEnabled(it) }?.let { block.invoke() }

infix fun OptionalFeature.feature(block: () -> Unit) {
runIfEnabled(this, block)
runIfEnabledBlocking(this, block)
}

/**
* ```kotlin
* { your code... } whenFeature (FeatureCatalog.xyz)
* ```
*/
infix fun (() -> Any?).whenFeature(feature: OptionalFeature) {
runIfEnabled(feature, this)
}
infix fun <T> (() -> T?).whenFeature(feature: OptionalFeature) = runIfEnabledBlocking(feature, this)

suspend infix fun <T> (suspend () -> T?).whenFeature(feature: OptionalFeature) = runIfEnabled(feature, this)

suspend fun registerBaseFeatures(baseFeatures: List<BaseFeature>) {
baseFeatures.forEach {
Expand Down
1 change: 1 addition & 0 deletions waltid-services/waltid-wallet-api/config/_features.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
enabledFeatures = [
# entra,
# ...
"registration-defaults",
]
4 changes: 4 additions & 0 deletions waltid-services/waltid-wallet-api/k8s/deployment-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ kind: ConfigMap
metadata:
name: wallet-config
data:
_features.conf: |
enabledFeatures = [
"registration-defaults",
]
db.conf: |
dataSource {
jdbcUrl = "jdbc:sqlite:/waltid-wallet-api/data/data.db"
Expand Down
4 changes: 4 additions & 0 deletions waltid-services/waltid-wallet-api/k8s/deployment-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ kind: ConfigMap
metadata:
name: wallet-config
data:
_features.conf: |
enabledFeatures = [
"registration-defaults",
]
db.conf: |
dataSource {
jdbcUrl = "jdbc:sqlite:/waltid-wallet-api/data/data.db"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ object FeatureCatalog : ServiceFeatureCatalog {
val rejectionReasonsFeature = OptionalFeature("rejectionreason", "Rejection reasons use case", RejectionReasonConfig::class, false)

val registrationDefaultsFeature =
OptionalFeature("registration-defaults", "Registration defaults (key, did) configuration", RegistrationDefaultsConfig::class, true)
OptionalFeature("registration-defaults", "Registration defaults (key, did) configuration", RegistrationDefaultsConfig::class, false)
val keyGenerationDefaultsFeature = OptionalFeature(
"key-generation-defaults",
"Key generation defaults (key backend & generation config) configuration",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import kotlinx.serialization.json.JsonPrimitive

@Serializable
data class RegistrationDefaultsConfig(
val defaultKeyConfig: KeyGenerationRequest = KeyGenerationRequest(),

val defaultDidConfig: DidMethodConfig = DidMethodConfig(),
val defaultKeyConfig: KeyGenerationRequest,
val defaultDidConfig: DidMethodConfig,
) : WalletConfig() {

@Serializable
data class DidMethodConfig(
val method: String = "jwk",
Expand All @@ -19,6 +19,11 @@ data class RegistrationDefaultsConfig(

@Transient
val didMethod = defaultDidConfig.method

@Transient
val didConfig: Map<String, JsonPrimitive> = defaultDidConfig.config
}
val didConfig: Map<String, JsonPrimitive>? = defaultDidConfig.config

companion object {
val Default = RegistrationDefaultsConfig(KeyGenerationRequest(), DidMethodConfig())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.jwk.KeyUse
import com.nimbusds.jose.util.Base64URL
import id.walt.commons.config.ConfigManager
import id.walt.commons.featureflag.FeatureManager.whenFeature
import id.walt.commons.web.ConflictException
import id.walt.commons.web.UnsupportedMediaTypeException
import id.walt.crypto.keys.*
Expand All @@ -25,6 +26,7 @@ import id.walt.oid4vc.requests.AuthorizationRequest
import id.walt.oid4vc.requests.CredentialOfferRequest
import id.walt.oid4vc.responses.AuthorizationErrorCode
import id.walt.oid4vc.responses.TokenResponse
import id.walt.webwallet.FeatureCatalog
import id.walt.webwallet.config.KeyGenerationDefaultsConfig
import id.walt.webwallet.config.RegistrationDefaultsConfig
import id.walt.webwallet.db.models.WalletCategoryData
Expand Down Expand Up @@ -329,7 +331,9 @@ class SSIKit2WalletService(
/* DIDs */

override suspend fun createDid(method: String, args: Map<String, JsonPrimitive>): String {
val keyId = args["keyId"]?.content?.takeIf { it.isNotEmpty() } ?: generateKey(defaultGenerationConfig.defaultKeyConfig)
val keyId = args["keyId"]?.content?.takeIf { it.isNotEmpty() } ?: generateKey(
({ defaultGenerationConfig.defaultKeyConfig } whenFeature FeatureCatalog.registrationDefaultsFeature)
?: throw IllegalArgumentException("No valid keyId provided and no default key available."))
val key = getKey(keyId)
val result = DidService.registerDefaultDidMethodByKey(method, key, args)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package id.walt.webwallet.service.account

import id.walt.commons.config.ConfigManager
import id.walt.commons.featureflag.FeatureManager.whenFeature
import id.walt.webwallet.FeatureCatalog
import id.walt.webwallet.config.RegistrationDefaultsConfig
import id.walt.webwallet.db.models.*
import id.walt.webwallet.service.WalletService
import id.walt.webwallet.service.WalletServiceManager
import id.walt.webwallet.service.events.AccountEventData
import id.walt.webwallet.service.events.EventType
Expand All @@ -18,13 +21,6 @@ import org.jetbrains.exposed.sql.transactions.transaction

object AccountsService {

fun registerAuthenticationMethods() {
// val loginMethods = ConfigManager.getConfig<LoginMethodsConfig>().enabledLoginMethods
}

private val defaultGenerationConfig by lazy { ConfigManager.getConfig<RegistrationDefaultsConfig>() }


private suspend fun initializeUserAccount(tenant: String, name: String?, registrationResult: RegistrationResult) =
let {
val registeredUserId = registrationResult.id
Expand All @@ -37,32 +33,19 @@ object AccountsService {
}

val walletService = WalletServiceManager.getWalletService(tenant, registeredUserId, createdInitialWalletId)
WalletServiceManager.eventUseCase.log(
action = EventType.Account.Create,
originator = "wallet",
tenant = tenant,
accountId = registeredUserId,
walletId = createdInitialWalletId,
data = AccountEventData(accountId = name)
)

// Add default data:

val createdKey = walletService.generateKey(defaultGenerationConfig.defaultKeyConfig)

val createdDid = walletService.createDid(
method = defaultGenerationConfig.didMethod,
args = defaultGenerationConfig.didConfig.toMutableMap().apply {
put("keyId", JsonPrimitive(createdKey))
put("alias", JsonPrimitive("Onboarding"))
}
)

walletService.setDefault(createdDid)
registrationResult
suspend { tryAddDefaultData(walletService) } whenFeature (FeatureCatalog.registrationDefaultsFeature)
registrationResult.also {
WalletServiceManager.eventUseCase.log(
action = EventType.Account.Create,
originator = "wallet",
tenant = tenant,
accountId = registeredUserId,
walletId = createdInitialWalletId,
data = AccountEventData(accountId = name)
)
}
}


suspend fun register(tenant: String = "", request: AccountRequest): Result<RegistrationResult> = runCatching {
when (request) {
is EmailAccountRequest -> EmailAccountStrategy.register(tenant, request)
Expand Down Expand Up @@ -174,6 +157,19 @@ object AccountsService {
Account(it)
}
}

private suspend fun tryAddDefaultData(walletService: WalletService) = runCatching {
val defaultGenerationConfig by lazy { ConfigManager.getConfig<RegistrationDefaultsConfig>() }
val createdKey = walletService.generateKey(defaultGenerationConfig.defaultKeyConfig)
val createdDid =
walletService.createDid(
method = defaultGenerationConfig.didMethod,
args = defaultGenerationConfig.didConfig!!.toMutableMap().apply {
put("keyId", JsonPrimitive(createdKey))
put("alias", JsonPrimitive("Onboarding"))
})
walletService.setDefault(createdDid)
}
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class E2EWalletTestLocal : E2EWalletTestBase() {
)

ConfigManager.preloadAndRegisterConfig("web", WebConfig(webPort = 4545))
ConfigManager.preloadAndRegisterConfig("registration-defaults", RegistrationDefaultsConfig())
ConfigManager.preloadAndRegisterConfig("registration-defaults", RegistrationDefaultsConfig.Default)
ConfigManager.preloadAndRegisterConfig("key-generation-defaults", KeyGenerationDefaultsConfig(emptyMap()))
webWalletSetup()
ConfigManager.loadConfigs(emptyArray())
Expand Down

0 comments on commit 10f01b4

Please sign in to comment.