Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(agent): Implementation Onboarding invitation on Agent #18

Merged
merged 10 commits into from
Jan 27, 2023
1 change: 1 addition & 0 deletions castor/src/commonMain/kotlin/CastorImpl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.iohk.atala.prism.castor

import io.iohk.atala.prism.castor.io.iohk.atala.prism.castor.resolvers.PeerDIDResolver
import io.iohk.atala.prism.domain.buildingBlocks.Castor
import io.iohk.atala.prism.domain.models.CastorError
import io.iohk.atala.prism.domain.models.DID
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.iohk.atala.prism.castor
package io.iohk.atala.prism.castor.io.iohk.atala.prism.castor.resolvers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong package name here


import io.iohk.atala.prism.castor.DIDParser
import io.iohk.atala.prism.castor.DIDUrlParser
import io.iohk.atala.prism.domain.models.DIDDocument
import io.iohk.atala.prism.domain.models.DIDDocumentCoreProperty
import io.iohk.atala.prism.domain.models.DIDResolver
Expand Down
2 changes: 2 additions & 0 deletions domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("io.iohk.atala.prism:uuid:1.0.0-alpha")
implementation("io.ktor:ktor-client-core:2.1.3")
}
}
val commonTest by getting {
Expand All @@ -77,6 +78,7 @@ kotlin {
}
}
val jvmMain by getting

val jvmTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.iohk.atala.prism.domain.models

import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.HttpStatement
import io.ktor.http.HttpMethod
import io.ktor.http.Url
import io.ktor.client.HttpClient as KtorClient

interface Api {
val client: KtorClient
suspend fun prepareRequest(
httpMethod: HttpMethod,
url: Url,
urlParameters: Map<String, String> = mapOf(),
httpHeaders: Map<String, String> = mapOf(),
body: Any? = null
): HttpStatement

suspend fun request(
httpMethod: HttpMethod,
url: Url,
urlParameters: Map<String, String> = mapOf(),
httpHeaders: Map<String, String> = mapOf(),
body: Any? = null
): HttpResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,27 @@ sealed class PlutoError(message: String? = null) : Throwable(message) {
sealed class PolluxError(message: String? = null) : Throwable(message) {
class InvalidCredentialError(message: String? = null) : PolluxError(message)
}

sealed class PrismAgentError(message: String? = null) : Throwable(message) {
class invalidURLError(message: String? = null) : PrismAgentError(message)
class cannotFindDIDKeyPairIndex(message: String? = null) : PrismAgentError(message)
class invitationHasNoFromDIDError(message: String? = null) : PrismAgentError(message)
class noValidServiceEndpointError(message: String? = null) : PrismAgentError(message)
class invitationIsInvalidError(message: String? = null) : PrismAgentError(message)
class noConnectionOpenError(message: String? = null) : PrismAgentError(message)
class noHandshakeResponseError(message: String? = null) : PrismAgentError(message)
class unknownInvitationTypeError(message: String? = null) : PrismAgentError(message)
class unknownPrismOnboardingTypeError(message: String? = null) : PrismAgentError(message)
class failedToOnboardError(message: String? = null) : PrismAgentError(message)
class invalidPickupDeliveryMessageError(message: String? = null) : PrismAgentError(message)
class invalidOfferCredentialMessageError(message: String? = null) : PrismAgentError(message)
class invalidProposedCredentialMessageError(message: String? = null) : PrismAgentError(message)
class invalidIssueCredentialMessageError(message: String? = null) : PrismAgentError(message)
class invalidRequestCredentialMessageError(message: String? = null) : PrismAgentError(message)
class invalidPresentationMessageError(message: String? = null) : PrismAgentError(message)
class invalidRequestPresentationMessageError(message: String? = null) : PrismAgentError(message)
class invalidProposePresentationMessageError(message: String? = null) : PrismAgentError(message)
class invalidMediationGrantMessageError(message: String? = null) : PrismAgentError(message)
class noMediatorAvailableError(message: String? = null) : PrismAgentError(message)
class mediationRequestFailedError(message: String? = null) : PrismAgentError(message)
}
29 changes: 27 additions & 2 deletions prism-agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,35 +71,60 @@ kotlin {
implementation(project(":domain"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")

implementation("io.ktor:ktor-client-core:2.1.3")
implementation("io.ktor:ktor-client-content-negotiation:2.1.3")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.3")
implementation("io.ktor:ktor-client-logging:2.1.3")
}
}
val commonTest by getting {
dependencies {
implementation(project(":domain"))
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
implementation("io.ktor:ktor-client-mock:2.1.3")
}
}
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.1.3")
}
}
val jvmMain by getting
val jvmTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
implementation("io.ktor:ktor-client-mock:2.1.3")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to reimport this one here as you already imported it in 'commonTest'

implementation(kotlin("test"))
}
}
val androidMain by getting {
kotlin {
srcDir("src/jvmMain/kotlin")
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
implementation("io.ktor:ktor-client-okhttp:2.1.3")
}
}
val androidTest by getting {
dependencies {
implementation("junit:junit:4.13.2")
}
}
val jsMain by getting
val jsMain by getting {
dependencies {
implementation("io.ktor:ktor-client-js:2.1.3")
implementation("io.ktor:ktor-client-content-negotiation:2.1.3")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to reimport it here as it already was imported in 'commonMain'

implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.3")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to reimport it here as it already was imported in 'commonMain'

implementation(npm("abort-controller", "3.0.0"))
implementation(npm("node-fetch", "2.6.7"))
}
}
val jsTest by getting

all {
languageSettings.optIn("kotlin.js.ExperimentalJsExport")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't add this compiler flag. As this will disable so many helpful warnings that are very useful during our development of the JS target

languageSettings.optIn("kotlin.RequiresOptIn")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,61 @@ import io.iohk.atala.prism.domain.buildingBlocks.Pluto
import io.iohk.atala.prism.domain.models.DID
import io.iohk.atala.prism.domain.models.DIDDocument
import io.iohk.atala.prism.domain.models.KeyCurve
import io.iohk.atala.prism.domain.models.PrismAgentError
import io.iohk.atala.prism.domain.models.Seed
import io.iohk.atala.prism.walletsdk.prismagent.helpers.ApiImpl
import io.iohk.atala.prism.walletsdk.prismagent.helpers.HttpClient
import io.iohk.atala.prism.walletsdk.prismagent.protocols.prismOnboarding.PrismOnboardingInvitation
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.HttpMethod
import io.ktor.http.Url
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

final class PrismAgent {
enum class State {
STOPED, STARTING, RUNNING, STOPING
}

sealed class InvitationType

data class PrismOnboardingInvitation(val from: String, val endpoint: String, val ownDID: DID) : InvitationType()

val seed: Seed
var state = State.STOPED

private val apollo: Apollo
private val castor: Castor
private val pluto: Pluto
private val api: ApiImpl

constructor(
apollo: Apollo,
castor: Castor,
pluto: Pluto,
seed: Seed? = null,
api: ApiImpl? = null
) {
this.apollo = apollo
this.castor = castor
this.pluto = pluto
this.seed = seed ?: apollo.createRandomSeed().second
this.api = api ?: ApiImpl(
HttpClient {
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
}
)
}
}
)
}

suspend fun createNewPrismDID(
Expand Down Expand Up @@ -72,4 +101,57 @@ final class PrismAgent {

return did
}

@Throws(PrismAgentError.unknownInvitationTypeError::class)
suspend fun parseInvitation(str: String): InvitationType {
val invite = try {
parsePrismInvitation(str)
} catch (e: Throwable) {
println(e)
throw PrismAgentError.unknownInvitationTypeError()
}
return invite
}

suspend fun acceptInvitation(invitation: PrismOnboardingInvitation) {
@Serializable
data class SendDID(val did: String)

var response = api.request(
HttpMethod.Post,
Url(invitation.endpoint),
mapOf(),
mapOf(),
SendDID(invitation.ownDID.toString())
)

if (response.status.value != 200) {
throw PrismAgentError.failedToOnboardError()
}
}

private suspend fun parsePrismInvitation(str: String): PrismOnboardingInvitation {
val prismOnboarding = PrismOnboardingInvitation(str)
val url = prismOnboarding.body.onboardEndpoint
val did = createNewPeerDID(
arrayOf(
DIDDocument.Service(
id = "#didcomm-1",
type = arrayOf("DIDCommMessaging"),
serviceEndpoint = DIDDocument.ServiceEndpoint(
uri = url,
accept = arrayOf("DIDCommMessaging"),
routingKeys = arrayOf()
)
)
),
true
)

return PrismOnboardingInvitation(
from = prismOnboarding.body.from,
endpoint = url,
ownDID = did
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.iohk.atala.prism.walletsdk.prismagent.helpers

import io.iohk.atala.prism.domain.models.Api
import io.ktor.client.request.prepareRequest
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.HttpStatement
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.Url
import io.ktor.http.contentType
import io.ktor.http.path
import kotlinx.coroutines.DelicateCoroutinesApi
import io.ktor.client.HttpClient as KtorClient

@DelicateCoroutinesApi
open class ApiImpl(override val client: KtorClient) : Api {

override suspend fun prepareRequest(
httpMethod: HttpMethod,
url: Url,
urlParameters: Map<String, String>,
httpHeaders: Map<String, String>,
body: Any?
): HttpStatement {

return client.prepareRequest {
for (header in httpHeaders) {
if (
httpMethod == HttpMethod.Get &&
header.key == HttpHeaders.ContentType &&
header.value.contains(ContentType.Application.Json.contentSubtype)
) {
continue
}
headers.append(header.key, header.value)
}

contentType(ContentType.Application.Json)

body?.let {
setBody(body)
}

url {
method = httpMethod
protocol = url.protocol
host = url.host
port = url.specifiedPort

path(url.encodedPath)

for (parameter in urlParameters) {
parameters.append(parameter.key, parameter.value)
}
}
}
}

override suspend fun request(
httpMethod: HttpMethod,
url: Url,
urlParameters: Map<String, String>,
httpHeaders: Map<String, String>,
body: Any?
): HttpResponse {
return prepareRequest(httpMethod, url, urlParameters, httpHeaders, body).execute()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.iohk.atala.prism.walletsdk.prismagent.helpers

import io.ktor.client.HttpClientConfig
import io.ktor.client.HttpClient as KtorClient

@Suppress("NO_ACTUAL_FOR_EXPECT")
internal expect fun HttpClient(config: HttpClientConfig<*>.() -> Unit = {}): KtorClient
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.iohk.atala.prism.walletsdk.prismagent.protocols.prismOnboarding

import io.iohk.atala.prism.domain.models.PrismAgentError
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

class PrismOnboardingInvitation(jsonString: String) {

@Serializable
data class Body(
val type: String,
val onboardEndpoint: String,
val from: String
)

var body: Body

init {
val json = Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
}
body = try {
json.decodeFromString(jsonString)
} catch (e: Throwable) {
throw PrismAgentError.invitationIsInvalidError()
}
}
}
Loading