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
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)
}
26 changes: 24 additions & 2 deletions prism-agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,35 +71,57 @@ 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 {
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
@@ -0,0 +1,9 @@
package io.iohk.atala.prism.walletsdk.prismagent.helpers

import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.HttpClient as KtorClient

internal actual fun HttpClient(config: HttpClientConfig<*>.() -> Unit) = KtorClient(OkHttp) {
config(this)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,32 @@ 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.Api
import io.iohk.atala.prism.walletsdk.prismagent.protocols.prismOnboarding.PrismOnboardingInvitation
import io.ktor.http.HttpMethod
import io.ktor.http.Url
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.serialization.Serializable

final class PrismAgent {
open class PrismAgent {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a point of this being open ;) I think it should be final.

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
open val api: Api = Api()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think if you really want to make a mocked API. There is a better way than adding this open. This would be creating an interface for the API then use dependency injection to pass the Api interface on init of the PrismAgent.


constructor(
apollo: Apollo,
Expand Down Expand Up @@ -72,4 +83,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,84 @@
package io.iohk.atala.prism.walletsdk.prismagent.helpers

import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
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 io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.serialization.json.Json
import io.ktor.client.HttpClient as KtorClient

@DelicateCoroutinesApi
open class Api(
private val client: KtorClient = HttpClient {
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
}
)
}
}
) {

private suspend fun prepareRequest(
httpMethod: HttpMethod,
url: Url,
urlParameters: Map<String, String> = mapOf(),
httpHeaders: Map<String, String> = mapOf(),
body: Any? = null
): 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)
}
}
}
}

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

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

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()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.iohk.atala.prism.walletsdk.prismagent

import io.iohk.atala.prism.walletsdk.prismagent.helpers.Api
import io.ktor.http.HttpStatusCode

class AgentApiMock(
statusCode: HttpStatusCode,
response: String
) : PrismAgent(ApolloMock(), CastorMock(), PlutoMock()) {
override val api: Api = ApiMock(statusCode, response)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.iohk.atala.prism.walletsdk.prismagent

import io.iohk.atala.prism.walletsdk.prismagent.helpers.Api
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.respond
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.headersOf
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.serialization.json.Json
import io.ktor.client.HttpClient as KtorClient

@OptIn(DelicateCoroutinesApi::class)
class ApiMock : Api {

constructor(statusCode: HttpStatusCode, response: String) : super(
KtorClient(
engine = MockEngine { _ ->
respond(
content = response,
status = statusCode,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
) {
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
}
)
}
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.iohk.atala.prism.domain.models.PublicKey
class CastorMock : Castor {
var parseDIDReturn: DID? = null
var createPrismDIDReturn: DID? = null
var createPeerDIDReturn: DID? = null
var createPeerDIDReturn: DID? = DID("did", "prism", "b6c0c33d701ac1b9a262a14454d1bbde3d127d697a76950963c5fd930605:Cj8KPRI7CgdtYXN0ZXIwEAFKLgoJc2VmsxEiECSTjyV7sUfCr_ArpN9rvCwR9fRMAhcsr_S7ZRiJk4p5k")
var resolveDIDReturn: DIDDocument? = null
var verifySignatureReturn: Boolean = false

Expand Down
Loading