From 15c5d10a09fa4e59c0cb7e04fa22eade49fd3443 Mon Sep 17 00:00:00 2001 From: Severin Stampler Date: Wed, 15 Mar 2023 17:59:24 +0100 Subject: [PATCH 1/4] refactoring and impl of support for multiple ecosystems in authorization request (claim) and wallet connect page --- .editorconfig | 4 +- config/idp-config.json | 35 +++- .../kotlin/id/walt/idp/config/ClaimConfig.kt | 28 ++- src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt | 11 +- .../kotlin/id/walt/idp/nfts/NFTController.kt | 124 +++++-------- .../kotlin/id/walt/idp/nfts/NFTManager.kt | 168 ++++++++---------- .../idp/nfts/NftResponseVerificationResult.kt | 1 + .../kotlin/id/walt/idp/oidc/OIDCManager.kt | 8 +- web/waltid-idpkit-ui/pages/connect-wallet.vue | 6 +- 9 files changed, 182 insertions(+), 203 deletions(-) diff --git a/.editorconfig b/.editorconfig index 4b7be75..067b648 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,11 +3,11 @@ root = true [*] charset = utf-8 end_of_line = lf -indent_size = 4 +indent_size = 2 indent_style = space insert_final_newline = true max_line_length = 120 -tab_width = 4 +tab_width = 2 trim_trailing_whitespace = true ij_continuation_indent_size = 8 ij_formatter_off_tag = @formatter:off diff --git a/config/idp-config.json b/config/idp-config.json index 23b50cb..fbc1d08 100644 --- a/config/idp-config.json +++ b/config/idp-config.json @@ -56,16 +56,37 @@ { "scope": [ "award" ], "claim": "awd", - "chain": "POLYGON", - "smartContractAddress": "0x9bc4d80c7b77ecc7107eac3961cb1dd98930f2b2", - "factorySmartContractAddress": "", - "trait": "award" + "claimMappings": { + "EVM": { + "nftTokenConstraint": { + "chain": "POLYGON", + "smartContractAddress": "0x9bc4d80c7b77ecc7107eac3961cb1dd98930f2b2", + "factorySmartContractAddress": "" + }, + "trait": "award" + } + } } ], "default_nft_token_claim": { - "chain": "TESTNET", - "factorySmartContractAddress": "", - "smartContractAddress": "demo.khaled_lightency1.testnet" + "ecosystems": [ "EVM", "TEZOS", "NEAR" ], + "nftTokenContraints": { + "EVM": { + "chain": "TESTNET", + "factorySmartContractAddress": "", + "smartContractAddress": "demo.khaled_lightency1.testnet" + }, + "TEZOS": { + "chain": "TESTNET", + "factorySmartContractAddress": "", + "smartContractAddress": "demo.khaled_lightency1.testnet" + }, + "NEAR": { + "chain": "TESTNET", + "factorySmartContractAddress": "", + "smartContractAddress": "demo.khaled_lightency1.testnet" + } + } }, "default_vp_token_claim": { "presentation_definition": { diff --git a/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt b/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt index 8d484b9..dbd55d7 100644 --- a/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt +++ b/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt @@ -3,7 +3,10 @@ package id.walt.idp.config import com.jayway.jsonpath.JsonPath import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.oauth2.sdk.Scope +import id.walt.idp.nfts.ChainEcosystem +import id.walt.idp.nfts.NFTManager import id.walt.idp.nfts.NftTokenClaim +import id.walt.idp.nfts.NftTokenConstraint import id.walt.idp.oidc.OIDCManager import id.walt.idp.oidc.ResponseVerificationResult import id.walt.model.oidc.VpTokenClaim @@ -36,19 +39,28 @@ class VCClaimMapping( get() = OIDCManager.AuthorizationMode.SIOP } +data class NFTClaimMappingDefinition( + val nftTokenConstraint: NftTokenConstraint, + val trait: String +) + class NFTClaimMapping( scope: Set, claim: String, - val chain: String, - val smartContractAddress: String, - val factorySmartContractAddress: String, - val trait: String + val claimMappings: Map ) : ClaimMapping(scope, claim) { override fun fillClaims(verificationResult: ResponseVerificationResult, claimBuilder: JWTClaimsSet.Builder) { - val attribute = - verificationResult.nftresponseVerificationResult?.metadata?.evmNftMetadata?.attributes?.firstOrNull { a -> a.trait_type == trait } - ?: throw BadRequestResponse("Requested nft metadata trait not found in verification response") - claimBuilder.claim(trait, attribute.value) + val mappingDefinition = verificationResult.nftresponseVerificationResult?.ecosystem?.let { + claimMappings[it.name] + } ?: throw BadRequestResponse("No mapping definition found for the given ecosystem") + + val claimValue = when(verificationResult.nftresponseVerificationResult?.ecosystem) { + ChainEcosystem.EVM -> verificationResult.nftresponseVerificationResult?.metadata?.evmNftMetadata?.attributes?.firstOrNull { a -> a.trait_type == mappingDefinition.trait }?.value + ChainEcosystem.TEZOS -> verificationResult.nftresponseVerificationResult?.metadata?.tezosNftMetadata?.attributes?.firstOrNull { a -> a.name == mappingDefinition.trait }?.value + ChainEcosystem.NEAR -> verificationResult.nftresponseVerificationResult?.metadata?.nearNftMetadata?.metadata?.let { NFTManager.getNearNftAttributeValue(it, mappingDefinition.trait) } + }?: throw BadRequestResponse("Requested nft metadata trait not found in verification response") + + claimBuilder.claim(mappingDefinition.trait, claimValue) } override val authorizationMode: OIDCManager.AuthorizationMode diff --git a/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt b/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt index 1aebc1e..63fed0c 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt @@ -7,7 +7,7 @@ import id.walt.nftkit.services.Chain import net.minidev.json.JSONObject import net.minidev.json.parser.JSONParser -data class NftTokenClaim( +data class NftTokenConstraint( @Json(serializeNull = false) val chain: Chain?, @Json(serializeNull = false) @@ -16,6 +16,15 @@ data class NftTokenClaim( val factorySmartContractAddress: String?, ) +enum class ChainEcosystem { + EVM, TEZOS, NEAR +} + +data class NftTokenClaim( + val ecosystems: Set, + val nftTokenContraints: Map +) + class NFTClaims( @Json(serializeNull = false) val nft_token: NftTokenClaim? = null, ) : OIDCClaimsRequest() { diff --git a/src/main/kotlin/id/walt/idp/nfts/NFTController.kt b/src/main/kotlin/id/walt/idp/nfts/NFTController.kt index 6f4f3d2..e637d36 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NFTController.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NFTController.kt @@ -44,105 +44,65 @@ object NFTController { val sessionId = ctx.queryParam("session") ?: throw BadRequestResponse("Session not specified") val message = ctx.queryParam("message") ?: throw BadRequestResponse("Message not specified") val signature = ctx.queryParam("signature") ?: throw BadRequestResponse("Signature not specified") - val chain = ctx.queryParam("chain") ?: throw BadRequestResponse("Chain not specified") + val ecosystem = ctx.queryParam("ecosystem")?.let { ChainEcosystem.valueOf(it) } ?: throw BadRequestResponse("Ecosystem not specified") val session = OIDCManager.getOIDCSession(sessionId) if (session == null) { - val uri = NFTManager.generateErrorResponseObject(sessionId, "", "Invalid or no session was set.") + val uri = NFTManager.generateErrorResponseObject(sessionId, "", "Invalid or no session was set.", ecosystem) ctx.status(HttpCode.FOUND).header("Location", uri.toString()) } if (!OIDCManager.AuthorizationMode.NFT.equals(session?.authorizationMode)) { - val uri = NFTManager.generateErrorResponseObject(sessionId, "", "Invalid callback.") + val uri = NFTManager.generateErrorResponseObject(sessionId, "", "Invalid callback.", ecosystem) ctx.status(HttpCode.FOUND).header("Location", uri.toString()) } - if ("EVM".equals(chain)){ + var address = "" + val siwxResult = when(ecosystem) { + ChainEcosystem.EVM -> { val request = SiweRequest(message, signature) - val eip4361msg = Eip4361Message.fromString(request.message) - - if (!SiweManager.messageAndSignatureVerification(session!!, message, signature)) { - val uri = NFTManager.generateErrorResponseObject(sessionId, eip4361msg.address, "Invalid signature.") - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } else { - val result = NFTManager.verifyNftOwnershipResponse(sessionId, eip4361msg.address) - if (IDPConfig.config.claimConfig?.default_nft_policy == null) { - throw BadRequestResponse("Missed policy configuration") - } - if (result.isValid && IDPConfig.config.claimConfig?.default_nft_policy!!.withPolicyVerification!!) { - val policyVerification = NFTManager.verifyNftMetadataAgainstPolicy(result.metadata!!) - if (policyVerification) { - val responseVerificationResult = ResponseVerificationResult(siopResponseVerificationResult = null, result) - val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } else { - val uri = NFTManager.generateErrorResponseObject(sessionId, eip4361msg.address, "Invalid policy verification.") - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } - } else { - val responseVerificationResult = ResponseVerificationResult(siopResponseVerificationResult = null, result) - val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } - } - }else if("Tezos".equals(chain)){ - val publicKey= SiwtManager.getPublicKey(message) - val address= SiwtManager.getAddress(message) - if(!SiwtManager.verifySignature(session!!,message, publicKey, signature)){ - val uri = NFTManager.generateErrorResponseObject(sessionId, address, "Invalid signature.") - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - }else{ - val result = NFTManager.verifyTezosNftOwnership(sessionId, address) - if(result.isValid && IDPConfig.config.claimConfig?.default_nft_policy!!.withPolicyVerification!!){ - val policyVerification = NFTManager.verifyNftMetadataAgainstPolicy(result.metadata!!) - if (policyVerification) { - val responseVerificationResult = ResponseVerificationResult(siopResponseVerificationResult = null, result) - val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } else { - val uri = NFTManager.generateErrorResponseObject(sessionId, "", "Invalid policy verification.") - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } - }else{ - val responseVerificationResult = ResponseVerificationResult(siopResponseVerificationResult = null, result) - val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } - - } - } - else if("TESTNET".equals(chain)){ + address = Eip4361Message.fromString(request.message).address + SiweManager.messageAndSignatureVerification(session!!, message, signature) + } + ChainEcosystem.TEZOS -> { + val publicKey = SiwtManager.getPublicKey(message) + address = SiwtManager.getAddress(message) + SiwtManager.verifySignature(session!!,message, publicKey, signature) + } + ChainEcosystem.NEAR -> { val publicKey = SiwnManager.getPublicKey(message) print( "is the public key" + publicKey ) - val address = SiwnManager.getAddress(message) - print("is addresse"+address) + address = SiwnManager.getAddress(message) + print("is address " + address) + SiwnManager.verifySignature(session!!, message, publicKey, signature) + } + } - if (!SiwnManager.verifySignature(session!!, message, publicKey, signature)) { - val uri = NFTManager.generateErrorResponseObject(sessionId, address, "Invalid signature.") - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } - else{ - val result = NFTManager.verifyNearNftOwnership(sessionId, address) - if (result.isValid && IDPConfig.config.claimConfig?.default_nft_policy!!.withPolicyVerification!!){ - val policyVerification = NFTManager.verifyNftMetadataAgainstPolicy(result.metadata!!) - if (policyVerification) { - val responseVerificationResult = - ResponseVerificationResult(siopResponseVerificationResult = null, result) - val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } else { - val uri = NFTManager.generateErrorResponseObject(sessionId, "", "Invalid policy verification.") - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } - }else{ - val responseVerificationResult = ResponseVerificationResult(siopResponseVerificationResult = null, result) - val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) - ctx.status(HttpCode.FOUND).header("Location", uri.toString()) - } + if(!siwxResult) { + val uri = NFTManager.generateErrorResponseObject(sessionId, address, "Invalid signature.", ecosystem) + ctx.status(HttpCode.FOUND).header("Location", uri.toString()) + } else { + val result = NFTManager.verifyNftOwnershipResponse(sessionId, address, ecosystem) + if (IDPConfig.config.claimConfig?.default_nft_policy == null) { + throw BadRequestResponse("Missed policy configuration") + } + if (result.isValid && IDPConfig.config.claimConfig?.default_nft_policy!!.withPolicyVerification!!) { + val policyVerification = NFTManager.verifyNftMetadataAgainstPolicy(result.metadata!!) + if (policyVerification) { + val responseVerificationResult = ResponseVerificationResult(siopResponseVerificationResult = null, result) + val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) + ctx.status(HttpCode.FOUND).header("Location", uri.toString()) + } else { + val uri = NFTManager.generateErrorResponseObject(sessionId, address, "Invalid policy verification.", ecosystem) + ctx.status(HttpCode.FOUND).header("Location", uri.toString()) } + } else { + val responseVerificationResult = ResponseVerificationResult(siopResponseVerificationResult = null, result) + val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) + ctx.status(HttpCode.FOUND).header("Location", uri.toString()) + } } - } diff --git a/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt b/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt index 1c38d3f..5f28703 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt @@ -10,61 +10,29 @@ import id.walt.idp.oidc.ResponseVerificationResult import id.walt.nftkit.opa.DynamicPolicy import id.walt.nftkit.services.* import id.walt.nftkit.utilis.Common +import mu.KotlinLogging import java.math.BigInteger import java.net.URI -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.serialization.kotlinx.json.* -import kotlinx.serialization.json.Json -import io.ktor.client.plugins.logging.* -import io.ktor.client.request.* -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.Serializable -import java.net.URLEncoder -import java.nio.charset.StandardCharsets object NFTManager { private const val NFT_API_PATH: String = "api/nft" val NFTApiUrl: String get() = "${IDPConfig.config.externalUrl}/$NFT_API_PATH" + val logger = KotlinLogging.logger { } - fun verifyNftOwnershipResponse(sessionId: String, account: String): NftResponseVerificationResult { - val result = nftCollectionOwnershipVerification(sessionId, account) + fun verifyNftOwnershipResponse(sessionId: String, account: String, ecosystem: ChainEcosystem): NftResponseVerificationResult { + val result = nftCollectionOwnershipVerification(sessionId, account, ecosystem) val error = if (result) null else "Invalid Ownership" var nft: NftMetadataWrapper? = null if (result) { - nft = NftMetadataWrapper(getAccountNftMetadata(sessionId, account), null) + nft = getAccountNftMetadata(sessionId, account, ecosystem) } - val nftResponseVerificationResult = NftResponseVerificationResult(account, sessionId, result, nft, error = error) - return nftResponseVerificationResult - } - fun verifyTezosNftOwnership(sessionId: String, account: String): NftResponseVerificationResult { - val result= tezosNftCollectionOwnershipVerification(sessionId, account) - val error = if (result) null else "Invalid Ownership" - var nft: NftMetadataWrapper? = null - - if (result) { - nft = NftMetadataWrapper(null, getAccountTezosNftMetadata(sessionId, account)) - } - val nftResponseVerificationResult = NftResponseVerificationResult(account, sessionId, result, nft, error = error) + val nftResponseVerificationResult = NftResponseVerificationResult(ecosystem, account, sessionId, result, nft, error = error) return nftResponseVerificationResult } - fun verifyNearNftOwnership(sessionId: String, account: String): NftResponseVerificationResult { - val result= nearNftCollectionOwnershipVerification(sessionId, account) - val error = if (result) null else "Invalid Ownership" - var nft: NftMetadataWrapper? = null - - if (result) { - nft = NftMetadataWrapper(null,null , getAccountNearNftMetadata(sessionId, account)) - } - val nftResponseVerificationResult = NftResponseVerificationResult(account, sessionId, result, nft, error = error) - return nftResponseVerificationResult - } fun getNFTClaims(authRequest: AuthorizationRequest): NFTClaims { val claims = (authRequest.requestObject?.jwtClaimsSet?.claims?.get("claims")?.toString() @@ -84,8 +52,8 @@ object NFTManager { return getNFTClaims(authRequest) } - fun generateErrorResponseObject(sessionId: String, address: String, errorMessage: String): URI { - val nftResponseVerificationResult = NftResponseVerificationResult(address, sessionId, false, error = errorMessage) + fun generateErrorResponseObject(sessionId: String, address: String, errorMessage: String, ecosystem: ChainEcosystem): URI { + val nftResponseVerificationResult = NftResponseVerificationResult(ecosystem, address, sessionId, false, error = errorMessage) val responseVerificationResult = ResponseVerificationResult(null, nftResponseVerificationResult, null) val uri = OIDCManager.continueIDPSessionResponse(sessionId, responseVerificationResult) return uri @@ -100,68 +68,72 @@ object NFTManager { ) } - private fun nftCollectionOwnershipVerification(sessionId: String, account: String): Boolean { - val session = OIDCManager.getOIDCSession(sessionId) - if(session?.nftTokenClaim?.factorySmartContractAddress.equals("") || session?.nftTokenClaim?.factorySmartContractAddress == null ) { - val balance = NftService.balanceOf( - Common.getEVMChain(session?.nftTokenClaim?.chain!!.toString()), - session.nftTokenClaim.smartContractAddress!!, account.trim() + private fun nftCollectionOwnershipVerification(sessionId: String, account: String, ecosystem: ChainEcosystem): Boolean { + val session = OIDCManager.getOIDCSession(sessionId) ?: return false.also { logger.error { "Session not found" } } + val tokenConstraint = session.nftTokenClaim?.nftTokenContraints?.get(ecosystem.name) + ?: return false.also { logger.error { "No nft token constraint found for given ecosystem" } } + return if(tokenConstraint.factorySmartContractAddress.isNullOrEmpty()) { + logger.info { "Verifying collection ownership on $ecosystem, for account: $account, chain: ${tokenConstraint.chain}, contract: ${tokenConstraint.smartContractAddress}" } + when(ecosystem) { + ChainEcosystem.EVM -> NftService.balanceOf( + Common.getEVMChain(tokenConstraint.chain!!.toString()), + tokenConstraint.smartContractAddress!!, account.trim() + )?.compareTo(BigInteger("0")) == 1 + ChainEcosystem.TEZOS, ChainEcosystem.NEAR -> VerificationService.verifyNftOwnershipWithinCollection( + tokenConstraint.chain!!, + tokenConstraint.smartContractAddress!!,account) + } + } else { + println("data nft verification") + when(ecosystem) { + ChainEcosystem.EVM -> VerificationService.dataNftVerification( + Common.getEVMChain(tokenConstraint.chain!!.toString()), + tokenConstraint.factorySmartContractAddress!!, + tokenConstraint.smartContractAddress!!, account.trim(), "", null ) - return balance!!.compareTo(BigInteger("0")) == 1 - }else{ - println("data nft verification") - return VerificationService.dataNftVerification(Common.getEVMChain(session.nftTokenClaim.chain!!.toString()), - session.nftTokenClaim.factorySmartContractAddress, - session.nftTokenClaim.smartContractAddress!!, account.trim(), "", null) + else -> false.also { + logger.error { "Data NFT verification not supported for $ecosystem ecosystem" } + } + } } } - private fun tezosNftCollectionOwnershipVerification(sessionId: String, account: String): Boolean { - val session = OIDCManager.getOIDCSession(sessionId) - val result = VerificationService.verifyNftOwnershipWithinCollection(session?.nftTokenClaim?.chain!!, - session.nftTokenClaim.smartContractAddress!!,account) - return result - - } - private fun nearNftCollectionOwnershipVerification(sessionId: String, account: String): Boolean { - val session = OIDCManager.getOIDCSession(sessionId) - println("chain: "+session?.nftTokenClaim?.chain!!) - val chain = session?.nftTokenClaim?.chain!! - println("chain lowwer: "+ chain) - - println("contract: "+session.nftTokenClaim.smartContractAddress!!) - println("account: "+account) - val result = VerificationService.verifyNftOwnershipWithinCollection(chain, - session.nftTokenClaim.smartContractAddress!!,account) - return result - } - - private fun getAccountNftMetadata(sessionId: String, account: String): NftMetadata { - val session = OIDCManager.getOIDCSession(sessionId) - val nfts = NftService.getAccountNFTsByAlchemy(session?.nftTokenClaim?.chain!!, account) - .filter { it.contract.address.equals(session.nftTokenClaim.smartContractAddress, ignoreCase = true) } - .sortedBy { it.id.tokenId } - return nfts.get(0).metadata!! - } - - private fun getAccountTezosNftMetadata(sessionId: String, account: String): TezosNftMetadata? { - val session = OIDCManager.getOIDCSession(sessionId) - val nfts= TezosNftService.fetchAccountNFTsByTzkt(session?.nftTokenClaim?.chain!!, account, session.nftTokenClaim.smartContractAddress) - .sortedBy { it.id } - return nfts.get(0).token.metadata - } - - private fun getAccountNearNftMetadata(sessionId: String, account: String): NearNftMetadata? { - val session = OIDCManager.getOIDCSession(sessionId) - val nfts= session!!.nftTokenClaim!!.smartContractAddress?.let { - NearNftService.getNFTforAccount( account, it,NearChain.valueOf( - session.nftTokenClaim?.chain!!.toString() - )) - } - if (nfts != null) { - return nfts.get(0) - } - return null + private fun getAccountNftMetadata(sessionId: String, account: String, ecosystem: ChainEcosystem): NftMetadataWrapper { + val session = OIDCManager.getOIDCSession(sessionId) ?: return NftMetadataWrapper().also { logger.error { "Session not found" } } + val tokenConstraint = session.nftTokenClaim?.nftTokenContraints?.get(ecosystem.name) + ?: return NftMetadataWrapper().also { logger.error { "No nft token constraint found for given ecosystem" } } + return NftMetadataWrapper( + evmNftMetadata = if(ecosystem == ChainEcosystem.EVM) + NftService.getAccountNFTsByAlchemy(tokenConstraint.chain!!, account) + .filter { it.contract.address.equals(tokenConstraint.smartContractAddress, ignoreCase = true) } + .sortedBy { it.id.tokenId }.get(0).metadata!! + else null, + tezosNftMetadata = if(ecosystem == ChainEcosystem.TEZOS) + TezosNftService.fetchAccountNFTsByTzkt(tokenConstraint.chain!!, account, tokenConstraint.smartContractAddress) + .sortedBy { it.id }.get(0).token.metadata + else null, + nearNftMetadata = if(ecosystem == ChainEcosystem.NEAR) + tokenConstraint.smartContractAddress?.let { + NearNftService.getNFTforAccount( account, it, NearChain.valueOf( + tokenConstraint.chain!!.toString() + ))}?.get(0) + else null + ) } + public fun getNearNftAttributeValue(metadata: NearTokenMetadata, key: String): String? = when(key) { + "copies" -> metadata.copies?.toString() + "description" -> metadata.description + "expires_at" -> metadata.expires_at + "extra" -> metadata.extra + "issued_at" -> metadata.issued_at + "media" -> metadata.media + "media_hash" -> metadata.media_hash + "reference" -> metadata.reference + "reference_hash" -> metadata.reference_hash + "starts_at" -> metadata.starts_at + "title" -> metadata.title + "updated_at" -> metadata.updated_at + else -> null + } } diff --git a/src/main/kotlin/id/walt/idp/nfts/NftResponseVerificationResult.kt b/src/main/kotlin/id/walt/idp/nfts/NftResponseVerificationResult.kt index a232819..86ee1fa 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NftResponseVerificationResult.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NftResponseVerificationResult.kt @@ -4,6 +4,7 @@ import id.walt.nftkit.services.NftMetadata import id.walt.nftkit.services.NftMetadataWrapper class NftResponseVerificationResult( + val ecosystem: ChainEcosystem, val account: String, val sessionId: String, val valid: Boolean = false, diff --git a/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt b/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt index 2aa80d2..96392d3 100644 --- a/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt +++ b/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt @@ -31,8 +31,10 @@ import id.walt.idp.config.NFTClaimMapping import id.walt.idp.config.NFTConfig import id.walt.idp.context.ContextFactory import id.walt.idp.context.ContextId +import id.walt.idp.nfts.ChainEcosystem import id.walt.idp.nfts.NFTManager import id.walt.idp.nfts.NftTokenClaim +import id.walt.idp.nfts.NftTokenConstraint import id.walt.idp.siop.SIOPState import id.walt.idp.siwe.SiweManager import id.walt.idp.util.WaltIdAlgorithm @@ -210,8 +212,10 @@ object OIDCManager : IDPManager { IDPConfig.config.claimConfig?.mappingsForScope(s)?.filterIsInstance() ?: listOf() } - .map { m -> NftTokenClaim(Chain.valueOf(m.chain), m.smartContractAddress, m.factorySmartContractAddress) } - .distinctBy { c -> "${c.chain}@${c.smartContractAddress}" } + .map { m -> NftTokenClaim( + ecosystems = m.claimMappings.keys.map { ChainEcosystem.valueOf(it) }.toSet(), + nftTokenContraints = m.claimMappings.mapValues { entry -> entry.value.nftTokenConstraint } + ) } if (nftClaimFromMappings.size > 1) { throw BadRequestResponse("Ambiguous NFT authorization request") } diff --git a/web/waltid-idpkit-ui/pages/connect-wallet.vue b/web/waltid-idpkit-ui/pages/connect-wallet.vue index fc47876..55d7d37 100644 --- a/web/waltid-idpkit-ui/pages/connect-wallet.vue +++ b/web/waltid-idpkit-ui/pages/connect-wallet.vue @@ -98,7 +98,7 @@ Nonce: ${nonce}` return false } // callback to IDP Kit with ethereum address - window.location = `${redirect_uri}?session=${session_id}&chain=EVM&message=${encodeURIComponent(eip4361msg)}&signature=${msgSignature}` + window.location = `${redirect_uri}?session=${session_id}&ecosystem=EVM&message=${encodeURIComponent(eip4361msg)}&signature=${msgSignature}` } catch (e) { console.log(e.response.data) this.error = true @@ -133,7 +133,7 @@ Nonce: ${nonce}` const signedPayload = await wallet.client.requestSignPayload(payload); // The signature const { signature } = signedPayload; - window.location = `${redirect_uri}?session=${session_id}&chain=Tezos&message=${message}&signature=${signature}` + window.location = `${redirect_uri}?session=${session_id}&ecosystem=Tezos&message=${message}&signature=${signature}` } catch (error) { console.log("Got error:", error); } @@ -216,7 +216,7 @@ Nonce: ${nonce}` const urlSignature = encodeURIComponent(signature) - window.location = `${redirect_uri}?session=${session_id}&chain=TESTNET&message=${message}&signature=${urlSignature}` + window.location = `${redirect_uri}?session=${session_id}&ecosystem=NEAR&message=${message}&signature=${urlSignature}` From 6db3bfca9b55dc6ec945583a061a408dcaf900c8 Mon Sep 17 00:00:00 2001 From: Severin Stampler Date: Wed, 15 Mar 2023 18:08:26 +0100 Subject: [PATCH 2/4] small code cleanup --- src/main/kotlin/id/walt/idp/config/ClaimConfig.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt b/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt index dbd55d7..b5cff44 100644 --- a/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt +++ b/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt @@ -54,10 +54,10 @@ class NFTClaimMapping( claimMappings[it.name] } ?: throw BadRequestResponse("No mapping definition found for the given ecosystem") - val claimValue = when(verificationResult.nftresponseVerificationResult?.ecosystem) { - ChainEcosystem.EVM -> verificationResult.nftresponseVerificationResult?.metadata?.evmNftMetadata?.attributes?.firstOrNull { a -> a.trait_type == mappingDefinition.trait }?.value - ChainEcosystem.TEZOS -> verificationResult.nftresponseVerificationResult?.metadata?.tezosNftMetadata?.attributes?.firstOrNull { a -> a.name == mappingDefinition.trait }?.value - ChainEcosystem.NEAR -> verificationResult.nftresponseVerificationResult?.metadata?.nearNftMetadata?.metadata?.let { NFTManager.getNearNftAttributeValue(it, mappingDefinition.trait) } + val claimValue = when(verificationResult.nftresponseVerificationResult.ecosystem) { + ChainEcosystem.EVM -> verificationResult.nftresponseVerificationResult.metadata?.evmNftMetadata?.attributes?.firstOrNull { a -> a.trait_type == mappingDefinition.trait }?.value + ChainEcosystem.TEZOS -> verificationResult.nftresponseVerificationResult.metadata?.tezosNftMetadata?.attributes?.firstOrNull { a -> a.name == mappingDefinition.trait }?.value + ChainEcosystem.NEAR -> verificationResult.nftresponseVerificationResult.metadata?.nearNftMetadata?.metadata?.let { NFTManager.getNearNftAttributeValue(it, mappingDefinition.trait) } }?: throw BadRequestResponse("Requested nft metadata trait not found in verification response") claimBuilder.claim(mappingDefinition.trait, claimValue) From f938bb584b28016eb4a994b3d7eeae8871a9ac1d Mon Sep 17 00:00:00 2001 From: Severin Stampler Date: Wed, 15 Mar 2023 18:10:47 +0100 Subject: [PATCH 3/4] small code cleanup --- src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt b/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt index 96392d3..f65dce9 100644 --- a/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt +++ b/src/main/kotlin/id/walt/idp/oidc/OIDCManager.kt @@ -21,7 +21,6 @@ import com.nimbusds.openid.connect.sdk.* import com.nimbusds.openid.connect.sdk.claims.UserInfo import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata import com.nimbusds.openid.connect.sdk.token.OIDCTokens -import id.walt.common.KlaxonWithConverters import id.walt.crypto.KeyAlgorithm import id.walt.crypto.KeyId import id.walt.idp.IDPManager @@ -34,25 +33,24 @@ import id.walt.idp.context.ContextId import id.walt.idp.nfts.ChainEcosystem import id.walt.idp.nfts.NFTManager import id.walt.idp.nfts.NftTokenClaim -import id.walt.idp.nfts.NftTokenConstraint import id.walt.idp.siop.SIOPState import id.walt.idp.siwe.SiweManager import id.walt.idp.util.WaltIdAlgorithm import id.walt.model.dif.* import id.walt.multitenancy.TenantId -import id.walt.nftkit.services.Chain import id.walt.services.context.ContextManager import id.walt.services.key.KeyFormat import id.walt.services.key.KeyService import id.walt.services.keystore.KeyType import id.walt.services.oidc.OIDCUtils import id.walt.siwe.configuration.SiweSession -import id.walt.verifier.backend.* +import id.walt.verifier.backend.SIOPResponseVerificationResult +import id.walt.verifier.backend.VerifierManager +import id.walt.verifier.backend.VerifierTenant +import id.walt.verifier.backend.WalletConfiguration import id.walt.webwallet.backend.context.WalletContextManager import io.javalin.http.BadRequestResponse -import io.javalin.http.Context import io.javalin.http.ForbiddenResponse -import io.javalin.http.InternalServerErrorResponse import javalinjwt.JWTProvider import mu.KotlinLogging import java.net.URI From 40d1974a9df53ba9cdfdb0138a51eb0a18460750 Mon Sep 17 00:00:00 2001 From: walid Date: Thu, 16 Mar 2023 11:18:30 +0100 Subject: [PATCH 4/4] Fix ecosystem chain transformation --- config/idp-config.json | 8 ++++---- src/main/kotlin/id/walt/idp/nfts/NFTController.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/idp-config.json b/config/idp-config.json index fbc1d08..d11f55c 100644 --- a/config/idp-config.json +++ b/config/idp-config.json @@ -72,14 +72,14 @@ "ecosystems": [ "EVM", "TEZOS", "NEAR" ], "nftTokenContraints": { "EVM": { - "chain": "TESTNET", + "chain": "POLYGON", "factorySmartContractAddress": "", - "smartContractAddress": "demo.khaled_lightency1.testnet" + "smartContractAddress": "0x21dd9b1913d84ab295fdf19834b0b6824a5912ca" }, "TEZOS": { - "chain": "TESTNET", + "chain": "GHOSTNET", "factorySmartContractAddress": "", - "smartContractAddress": "demo.khaled_lightency1.testnet" + "smartContractAddress": "KT1Rc59ukgW32e54aUdYqVzTM9gtHrA4JDYp" }, "NEAR": { "chain": "TESTNET", diff --git a/src/main/kotlin/id/walt/idp/nfts/NFTController.kt b/src/main/kotlin/id/walt/idp/nfts/NFTController.kt index e637d36..817c110 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NFTController.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NFTController.kt @@ -44,7 +44,7 @@ object NFTController { val sessionId = ctx.queryParam("session") ?: throw BadRequestResponse("Session not specified") val message = ctx.queryParam("message") ?: throw BadRequestResponse("Message not specified") val signature = ctx.queryParam("signature") ?: throw BadRequestResponse("Signature not specified") - val ecosystem = ctx.queryParam("ecosystem")?.let { ChainEcosystem.valueOf(it) } ?: throw BadRequestResponse("Ecosystem not specified") + val ecosystem = ctx.queryParam("ecosystem")?.let { ChainEcosystem.valueOf(it.toUpperCase()) } ?: throw BadRequestResponse("Ecosystem not specified") val session = OIDCManager.getOIDCSession(sessionId)