Skip to content

Commit

Permalink
Merge branch 'main' into skit-232-iso-mdl-reader-interface-in-swift
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryanmtate authored Aug 29, 2024
2 parents b94d87f + c755516 commit fa1b5df
Show file tree
Hide file tree
Showing 16 changed files with 1,373 additions and 260 deletions.
3 changes: 3 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ excluded:
disabled_rules:
- cyclomatic_complexity
- todo
- file_length
- force_try
- non_optional_string_data_conversion
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Contributing Documentation

## Setup
The XCode project is generated using `xcodgen`.

## Making Changes Involving the Rust Layer
During development, you can simply depend on your local instance of
`mobile-sdk-rs` by editing the `Package.swift`.

Once everything is complete, you can depend on specific commits from
`mobile-sdk-rs` as Swift's Package Manager enables it, but because of
limitations on the Android's side, you might need to publish a new version of
`mobile-sdk-rs`.

## Release
1. Ensure the dependencies rely on published versions and not commits or
branches.
2. Ensure `SpruceIDMobileSdk.podspec`'s version is bumped and that the
dependencies' versions match the versions in `Package.swift`.
3. Push a tag in the format `x.y.z` which should match the version in the
podspec.
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ let package = Package(
targets: ["SpruceIDMobileSdk"])
],
dependencies: [
// .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", .branch("main")),
// .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", .branch("main")),
// .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.28"),
.package(path: "../mobile-sdk-rs"),
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0")
],
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ only. We welcome feedback on the usability, architecture, and security of this
implementation and are committed to a conducting a formal audit with a reputable
security firm before the v1.0 release.

## Setup

The XCode project is generated using `xcodgen`.

## Architecture

Our Mobile SDKs use shared code, with most of the logic being written once in
Expand All @@ -37,3 +33,11 @@ called in native SDKs.
- [Kotlin SDK](https://github.com/spruceid/mobile-sdk-kt)
- [Swift SDK](https://github.com/spruceid/mobile-sdk-swift)
- [Rust layer](https://github.com/spruceid/mobile-sdk-rs)

## Configuring Deep Links for same device flows

Click [here](./Sources/MobileSdk/ui/SameDeviceOID4VP.md) to see how to configure the same device OpenID4VP flow.

## Contributing

See [CONTRIBUTING.md](/CONTRIBUTING.md).
10 changes: 9 additions & 1 deletion Sources/MobileSdk/Credential.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import Foundation

public class Credential: Identifiable {
open class Credential: Identifiable {
public var id: String

public init(id: String) {
self.id = id
}

open func get(keys: [String]) -> [String: GenericJSON] {
if keys.contains("id") {
return ["id": GenericJSON.string(self.id)]
} else {
return [:]
}
}
}
53 changes: 53 additions & 0 deletions Sources/MobileSdk/CredentialPack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation
import CryptoKit

public class CredentialPack {

private var credentials: [Credential]

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

public init(credentials: [Credential]) {
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
}
}

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)
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
}

public func get(credentialsIds: [String]) -> [Credential] {
return self.credentials.filter { credentialsIds.contains($0.id) }
}

public func get(credentialId: String) -> Credential? {
if let credential = self.credentials.first(where: { $0.id == credentialId }) {
return credential
} else {
return nil
}
}
}
139 changes: 139 additions & 0 deletions Sources/MobileSdk/GenericJSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// GenericJSON implementation based on https://github.com/iwill/generic-json-swift
import Foundation

public enum GenericJSON {
case string(String)
case number(Double)
case object([String: GenericJSON])
case array([GenericJSON])
case bool(Bool)
case null
}

extension GenericJSON: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .array(array):
try container.encode(array)
case let .object(object):
try container.encode(object)
case let .string(string):
try container.encode(string)
case let .number(number):
try container.encode(number)
case let .bool(bool):
try container.encode(bool)
case .null:
try container.encodeNil()
}
}

public func toString() -> String {
switch self {
case .string(let str):
return str
case .number(let num):
return num.debugDescription
case .bool(let bool):
return bool.description
case .null:
return "null"
default:
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try! String(data: encoder.encode(self), encoding: .utf8)!
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let object = try? container.decode([String: GenericJSON].self) {
self = .object(object)
} else if let array = try? container.decode([GenericJSON].self) {
self = .array(array)
} else if let string = try? container.decode(String.self) {
self = .string(string)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let number = try? container.decode(Double.self) {
self = .number(number)
} else if container.decodeNil() {
self = .null
} else {
throw DecodingError.dataCorrupted(
.init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.")
)
}
}
}

extension GenericJSON: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .string(let str):
return str.debugDescription
case .number(let num):
return num.debugDescription
case .bool(let bool):
return bool.description
case .null:
return "null"
default:
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try! String(data: encoder.encode(self), encoding: .utf8)!
}
}
}

public extension GenericJSON {
var dictValue: [String: GenericJSON]? {
if case .object(let value) = self {
return value
}
return nil
}
var arrayValue: [GenericJSON]? {
if case .array(let value) = self {
return value
}
return nil
}
subscript(index: Int) -> GenericJSON? {
if case .array(let arr) = self, arr.indices.contains(index) {
return arr[index]
}
return nil
}

subscript(key: String) -> GenericJSON? {
if case .object(let dict) = self {
return dict[key]
}
return nil
}

subscript(dynamicMember member: String) -> GenericJSON? {
return self[member]
}

subscript(keyPath keyPath: String) -> GenericJSON? {
return queryKeyPath(keyPath.components(separatedBy: "."))
}

func queryKeyPath<T>(_ path: T) -> GenericJSON? where T: Collection, T.Element == String {
guard case .object(let object) = self else {
return nil
}
guard let head = path.first else {
return nil
}
guard let value = object[head] else {
return nil
}
let tail = path.dropFirst()
return tail.isEmpty ? value : value.queryKeyPath(tail)
}

}
36 changes: 36 additions & 0 deletions Sources/MobileSdk/W3CVC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

enum W3CError: Error {
case initializationError(String)
}

public class W3CVC: Credential {
private let credentialString: String
private let credential: GenericJSON?

public init(credentialString: String) throws {
self.credentialString = credentialString
if let data = credentialString.data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
self.credential = json
super.init(id: json["id"]!.toString())
} catch let error as NSError {
throw error
}
} else {
self.credential = nil
super.init(id: "")
throw W3CError.initializationError("Failed to process credential string.")
}
}

override public func get(keys: [String]) -> [String: GenericJSON] {
if let cred = credential!.dictValue {
return cred.filter { keys.contains($0.key) }
} else {
return [:]
}

}
}
Loading

0 comments on commit fa1b5df

Please sign in to comment.