Skip to content

Commit

Permalink
Update CredentialPack to use ParsedCredential (#39)
Browse files Browse the repository at this point in the history
* Update CredentialPack to use ParsedCredential
* Make CredentialStore and MDoc compatible to ParsedCredential


---------

Co-authored-by: Juliano Cezar Chagas Tavares <[email protected]>
  • Loading branch information
cobward and Juliano1612 authored Oct 3, 2024
1 parent daedd38 commit 76d2b77
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 45 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/spruceid/mobile-sdk-rs.git",
"state" : {
"revision" : "06e0cfcbfe01bccf0e014688be4e0b6275638bd5",
"version" : "0.0.32"
"revision" : "4dcde0bee6374e19fcc3840fc82c144b99ffc54c",
"version" : "0.0.33"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
targets: ["SpruceIDMobileSdk"])
],
dependencies: [
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", exact: "0.0.32"),
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", exact: "0.0.33"),
// .package(path: "../mobile-sdk-rs"),
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0")
],
Expand Down
93 changes: 93 additions & 0 deletions Sources/MobileSdk/Credential.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import SpruceIDMobileSdkRs

open class Credential: Identifiable {
public var id: String
Expand All @@ -15,3 +16,95 @@ open class Credential: Identifiable {
}
}
}

extension Mdoc {
/// Access all of the elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as JSON.
public func jsonEncodedDetails() -> [String: GenericJSON] {
self.jsonEncodedDetailsInternal(containing: nil)
}

/// Access the specified elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as
/// JSON.
public func jsonEncodedDetails(containing elementIdentifiers: [String]) -> [String: GenericJSON] {
self.jsonEncodedDetailsInternal(containing: elementIdentifiers)
}

private func jsonEncodedDetailsInternal(containing elementIdentifiers: [String]?) -> [String: GenericJSON] {
// Ignore the namespaces.
Dictionary(uniqueKeysWithValues: self.details().flatMap {
$1.compactMap {
let id = $0.identifier

// If a filter is provided, filter out non-specified ids.
if let ids = elementIdentifiers {
if !ids.contains(id) {
return nil
}
}
if let data = $0.value?.data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
return (id, json)
} catch let error as NSError {
print("failed to decode '\(id)' as JSON: \(error)")
}
}
return nil
}
})
}
}

extension JwtVc {
/// Access the W3C VCDM credential (not including the JWT envelope).
public func credentialClaims() -> [String: GenericJSON] {
if let data = self.credentialAsJsonEncodedUtf8String().data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
if let object = json.dictValue {
return object
} else {
print("unexpected format for VCDM")
}
} catch let error as NSError {
print("failed to decode as JSON: \(error)")
}
}
print("failed to decode VCDM data from UTF-8")
return [:]
}

/// Access the specified claims from the W3C VCDM credential (not including the JWT envelope).
public func credentialClaims(containing claimNames: [String]) -> [String: GenericJSON] {
self.credentialClaims().filter { (key, _) in
claimNames.contains(key)
}
}
}

extension JsonVc {
/// Access the W3C VCDM credential
public func credentialClaims() -> [String: GenericJSON] {
if let data = self.credentialAsJsonEncodedUtf8String().data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
if let object = json.dictValue {
return object
} else {
print("unexpected format for VCDM")
}
} catch let error as NSError {
print("failed to decode as JSON: \(error)")
}
}
print("failed to decode VCDM data from UTF-8")
return [:]
}

/// Access the specified claims from the W3C VCDM credential.
public func credentialClaims(containing claimNames: [String]) -> [String: GenericJSON] {
self.credentialClaims().filter { (key, _) in
claimNames.contains(key)
}
}
}
81 changes: 53 additions & 28 deletions Sources/MobileSdk/CredentialPack.swift
Original file line number Diff line number Diff line change
@@ -1,53 +1,78 @@
import Foundation
import SpruceIDMobileSdkRs
import CryptoKit

public class CredentialPack {

private var credentials: [Credential]
private var credentials: [ParsedCredential]

/// Initialize an empty CredentialPack.
public init() {
self.credentials = []
}

public init(credentials: [Credential]) {
/// Initialize a CredentialPack from existing credentials.
public init(credentials: [ParsedCredential]) {
self.credentials = credentials
}

public func addW3CVC(credentialString: String) throws -> [Credential]? {
do {
let credential = try W3CVC(credentialString: credentialString)
self.credentials.append(credential)
return self.credentials
} catch {
throw error
}
/// Add a JwtVc to the CredentialPack.
public func addJwtVc(jwtVc: JwtVc) -> [ParsedCredential] {
self.credentials.append(ParsedCredential.newJwtVcJson(jwtVc: jwtVc))
return self.credentials
}

public func addMDoc(mdocBase64: String, keyAlias: String = UUID().uuidString) throws -> [Credential]? {
let mdocData = Data(base64Encoded: mdocBase64)!
let credential = MDoc(fromMDoc: mdocData, namespaces: [:], keyAlias: keyAlias)!
self.credentials.append(credential)
/// Add a JsonVc to the CredentialPack.
public func addJsonVc(jsonVc: JsonVc) -> [ParsedCredential] {
self.credentials.append(ParsedCredential.newLdpVc(jsonVc: jsonVc))
return self.credentials
}

public func get(keys: [String]) -> [String: [String: GenericJSON]] {
var values: [String: [String: GenericJSON]] = [:]
for cred in self.credentials {
values[cred.id] = cred.get(keys: keys)
}

return values
/// Add an Mdoc to the CredentialPack.
public func addMDoc(mdoc: Mdoc) -> [ParsedCredential] {
self.credentials.append(ParsedCredential.newMsoMdoc(mdoc: mdoc))
return self.credentials
}

public func get(credentialsIds: [String]) -> [Credential] {
return self.credentials.filter { credentialsIds.contains($0.id) }
/// Find credential claims from all credentials in this CredentialPack.
public func findCredentialClaims(claimNames: [String]) -> [Uuid: [String: GenericJSON]] {
Dictionary(uniqueKeysWithValues: self.list()
.map { credential in
var claims: [String: GenericJSON]
if let mdoc = credential.asMsoMdoc() {
claims = mdoc.jsonEncodedDetails(containing: claimNames)
} else if let jwtVc = credential.asJwtVc() {
claims = jwtVc.credentialClaims(containing: claimNames)
} else if let jsonVc = credential.asJsonVc() {
claims = jsonVc.credentialClaims(containing: claimNames)
} else {
var type: String
do {
type = try credential.intoGenericForm().type
} catch {
type = "unknown"
}
print("unsupported credential type: \(type)")
claims = [:]
}
return (credential.id(), claims)
})
}

public func get(credentialId: String) -> Credential? {
if let credential = self.credentials.first(where: { $0.id == credentialId }) {
return credential
} else {
return nil
/// Get credentials by id.
public func get(credentialsIds: [Uuid]) -> [ParsedCredential] {
return self.credentials.filter {
credentialsIds.contains($0.id())
}
}

/// Get a credential by id.
public func get(credentialId: Uuid) -> ParsedCredential? {
return self.credentials.first(where: { $0.id() == credentialId })
}

/// List all of the credentials in the CredentialPack.
public func list() -> [ParsedCredential] {
return self.credentials
}
}
12 changes: 6 additions & 6 deletions Sources/MobileSdk/Credentials.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import Foundation
import SpruceIDMobileSdkRs

public class CredentialStore {
public var credentials: [Credential]
public var credentials: [ParsedCredential]

public init(credentials: [Credential]) {
public init(credentials: [ParsedCredential]) {
self.credentials = credentials
}

// swiftlint:disable force_cast
public func presentMdocBLE(deviceEngagement: DeviceEngagement,
callback: BLESessionStateDelegate,
useL2CAP: Bool = true
// , trustedReaders: TrustedReaders
) async -> IsoMdlPresentation? {
if let firstMdoc = self.credentials.first(where: {$0 is MDoc}) {
return await IsoMdlPresentation(mdoc: firstMdoc as! MDoc,
if let firstMdoc = self.credentials.first(where: { $0.asMsoMdoc() != nil }) {
let mdoc = firstMdoc.asMsoMdoc()!
return await IsoMdlPresentation(mdoc: MDoc(Mdoc: mdoc),
engagement: DeviceEngagement.QRCode,
callback: callback,
useL2CAP: useL2CAP)
} else {
return nil
}
}
// swiftlint:enable force_cast
}
6 changes: 6 additions & 0 deletions Sources/MobileSdk/MDoc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ public class MDoc: Credential {
}
super.init(id: inner.id())
}

public init(Mdoc mdoc: SpruceIDMobileSdkRs.Mdoc) {
self.keyAlias = mdoc.keyAlias()
self.inner = mdoc
super.init(id: inner.id())
}
}
10 changes: 5 additions & 5 deletions Sources/MobileSdk/ui/Card.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ public struct CardListView: View {
}

public var body: some View {
let descriptionValues = credentialPack.get(keys: rendering.descriptionKeys ?? [])
let titleValues = credentialPack.get(keys: rendering.titleKeys)
let descriptionValues = credentialPack.findCredentialClaims(claimNames: rendering.descriptionKeys ?? [])
let titleValues = credentialPack.findCredentialClaims(claimNames: rendering.titleKeys ?? [])
HStack {
// Leading icon
if rendering.leadingIconFormatter != nil {
AnyView(
rendering.leadingIconFormatter!(
credentialPack.get(keys: rendering.leadingIconKeys ?? [])
credentialPack.findCredentialClaims(claimNames: rendering.leadingIconKeys ?? [])
)
)
}
Expand Down Expand Up @@ -171,7 +171,7 @@ public struct CardListView: View {
if rendering.trailingActionButton != nil {
AnyView(
rendering.trailingActionButton!(
credentialPack.get(keys: rendering.trailingActionKeys ?? [])
credentialPack.findCredentialClaims(claimNames: rendering.trailingActionKeys ?? [])
)
)
}
Expand All @@ -195,7 +195,7 @@ public struct CardDetailsView: View {
public var body: some View {
ScrollView(.vertical, showsIndicators: false) {
ForEach(rendering.fields, id: \.id) { field in
let values = credentialPack.get(keys: field.keys)
let values = credentialPack.findCredentialClaims(claimNames: field.keys)
if field.formatter != nil {
AnyView(field.formatter!(values))
} else {
Expand Down
2 changes: 1 addition & 1 deletion SpruceIDMobileSdk.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Pod::Spec.new do |spec|
spec.source_files = "Sources/MobileSdk/*.swift"

spec.static_framework = true
spec.dependency 'SpruceIDMobileSdkRs', "~> 0.0.32"
spec.dependency 'SpruceIDMobileSdkRs', "~> 0.0.33"
spec.dependency 'SwiftAlgorithms', "~> 1.0.0"
spec.frameworks = 'Foundation', 'CoreBluetooth', 'CryptoKit'
end
2 changes: 0 additions & 2 deletions project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ packages:
SpruceIDMobileSdkRs:
url: https://github.com/spruceid/mobile-sdk-rs
revision: "d559d750dbd5945eace315a05ce05b2702ed2865"
# from: 0.0.27
# path: "../mobile-sdk-rs"
SwiftAlgorithms:
url: https://github.com/apple/swift-algorithms
from: 1.2.0
Expand Down

0 comments on commit 76d2b77

Please sign in to comment.