Skip to content

Commit

Permalink
Merge branch 'main' into gh-pages
Browse files Browse the repository at this point in the history
  • Loading branch information
phisakel committed Jan 14, 2025
2 parents ef59001 + ef353c4 commit f8e5fb7
Show file tree
Hide file tree
Showing 19 changed files with 1,401 additions and 1,363 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@

name: Swift

on: [push]

on:
pull_request:
types: [opened, reopened]
push:
branches: ['main']
tags: [ v* ]
jobs:
build:

Expand Down
66 changes: 66 additions & 0 deletions Sources/MdocDataModel18013/DocumentClaims/DocClaim.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright (c) 2023 European Commission

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/


import Foundation

/// This structure is used to store document claim values and associated metadata.
/// It provides functionality for generating string representations for
/// debugging and display purposes.
@DebugDescription
public struct DocClaim: Equatable, CustomStringConvertible, CustomDebugStringConvertible, Sendable {
public init(name: String, displayName: String? = nil, dataValue: DocDataValue, stringValue: String, valueType: String? = nil, isOptional: Bool = false, order: Int = 0, namespace: String? = nil, children: [DocClaim]? = nil) {
self.name = name
self.displayName = displayName
self.dataValue = dataValue
self.valueType = valueType
self.stringValue = stringValue
self.isOptional = isOptional
self.order = order
self.namespace = namespace
self.children = children
}
/// The namespace of the claim (if document is a mso-mdoc)
public let namespace: String?
/// The name of the claim.
public let name: String
/// The display name of the claim, originated from VCI metadata/claims.
public let displayName: String?
/// The value of the claim as a string.
public let stringValue: String
/// The value of the claim as a `DocDataValue` (enum with associated values)
public let dataValue: DocDataValue
/// The type of the value of the claim, originated from VCI metadata/claims.
public let valueType: String?
/// A flag indicating whether the claim is optional, originated from VCI metadata/claims.
public var isOptional: Bool = false
/// The order of the claim in the document.
public var order: Int = 0
/// A string for Wallet UI usage to define the style of the claim.
public var style: String?
/// The children of the claim.
public var children: [DocClaim]?
/// Description of the claim.
public var description: String { "\(name): \(stringValue)" }
/// Debug description of the claim.
public var debugDescription: String { "\(order). \t\(name): \(stringValue)" }

/// Adds a child to the claim.
public mutating func add(child: DocClaim) {
if children == nil { children = [] }
children!.append(child)
}
}
158 changes: 158 additions & 0 deletions Sources/MdocDataModel18013/DocumentClaims/DocClaimsDecodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
Copyright (c) 2023 European Commission

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// ClaimsDecodable.swift

import Foundation
import SwiftCBOR

/// A conforming type represents claims data.
///
/// Can be decoded by CBOR or SD-JWT data
public protocol DocClaimsDecodable: Sendable, AgeAttesting {
/// The unique identifier of the document.
var id: String { get }
/// The date and time the document was created.
var createdAt: Date { get }
/// The date and time the document was last modified.
var modifiedAt: Date? { get }
/// The display name of the document.
var displayName: String? { get }
// The document type. For CBOR (mso_mdoc) documents is native, for SD-JWT (vc+sd-jwt) documents is the type of the document.
var docType: String? { get }
// document claims in a format agnostic way
var docClaims: [DocClaim] { get }
/// The format of the document data.
var docDataFormat: DocDataFormat { get }
} // end protocol

/// Methods to extract CBOR values.
extension DocClaimsDecodable {

public static func getCborItemValue<T>(_ issuerSigned: IssuerSigned, _ s: String) -> T? {
let nameSpaceItems = Self.getCborSignedItems(issuerSigned)
guard let nameSpaceItems else { return nil }
return Self.getCborItemValue(nameSpaceItems, string: s)
}

static func getCborItemValue<T>(_ nameSpaceItems: [NameSpace: [IssuerSignedItem]], string name: String) -> T? {
for (_,v) in nameSpaceItems {
if let item = v.first(where: { name == $0.elementIdentifier }) { return item.getTypedValue() }
}
return nil
}

public static func getCborSignedItems(_ issuerSigned: IssuerSigned, _ ns: [NameSpace]? = nil) -> [String: [IssuerSignedItem]]? {
guard var nameSpaces = issuerSigned.issuerNameSpaces?.nameSpaces else { return nil }
if let ns { nameSpaces = nameSpaces.filter { ns.contains($0.key) } }
return nameSpaces
}


/// Extracts age-over values from the provided namespaces and updates the given dictionary with the results.
///
/// - Parameters:
/// - nameSpaces: A dictionary where the key is a `NameSpace` and the value is an array of `IssuerSignedItem`.
/// - ageOverXX: An inout parameter that is a dictionary where the key is an integer representing the age and the value is a boolean indicating whether the age condition is met.
public static func extractAgeOverValues(_ nameSpaces: [NameSpace: [IssuerSignedItem]], _ ageOverXX: inout [Int: Bool]) {
for (_, items) in nameSpaces {
for item in items {
let k = item.elementIdentifier
if !k.hasPrefix("age_over_") { continue }
if let age = Int(k.suffix(k.count - 9)) {
let b: Bool? = item.getTypedValue()
if let b { ageOverXX[age] = b }
}
}
}
}

/// Determines if there are more than two age-over element identifiers in the provided list.
///
/// - Parameters:
/// - reqDocType: The required document type.
/// - reqNamespace: The required namespace.pa
/// - ageAttest: An instance conforming to the `AgeAttesting` protocol.s
/// - reqElementIdentifiers: A list of data element identifiers.
/// - Returns: A set of strings representing the identifiers that meet the criteria. }

public static func moreThan2AgeOverElementIdentifiers(_ reqDocType: DocType, _ reqNamespace: NameSpace, _ ageAttest: any AgeAttesting, _ reqElementIdentifiers: [DataElementIdentifier]) -> Set<String> {
// special case for maximum two age_over_NN data elements shall be returned
guard reqDocType == IsoMdlModel.isoDocType, reqNamespace == IsoMdlModel.isoNamespace else { return Set() }
let ages = reqElementIdentifiers.filter { $0.hasPrefix("age_over_")}.compactMap { k in Int(k.suffix(k.count - 9)) }
let agesDict = ageAttest.max2AgesOver(ages: ages)
return Set( agesDict.filter { $1 == false }.keys.map { "age_over_\($0)" })
}

/// Extracts a CBOR value.
///
/// - Parameters:
/// - name: The name associated with the CBOR value.
/// - cborValue: The CBOR value to be processed.pa
/// - bDebugDisplay: A boolean flag indicating whether to enable debug display.s
/// - ns: The namespace associated with the CBOR value.
/// - order: The order in which the value should be processed.
/// - labels: A dictionary where the key is the elementIdentifier and the value is a string representing the label.
/// - Returns: A `DocClaim` object containing the extracted display string or image.
public static func extractCborClaim(_ name: String, _ cborValue: CBOR, _ bDebugDisplay: Bool, _ namespace: NameSpace, _ order: Int, _ displayNames: [String:String]? = nil, _ mandatory: [String:Bool]? = nil, _ valueTypes: [String:String]? = nil) -> DocClaim {
var stringValue = bDebugDisplay ? cborValue.debugDescription : cborValue.description
let dt = cborValue.mdocDataValue ?? .string(stringValue)
if name == "sex", let isex = Int(stringValue), isex <= 2 { stringValue = NSLocalizedString(isex == 1 ? "male" : "female", comment: "") }
let isMandatory = mandatory?[name] ?? true
var node = DocClaim(name: name, displayName: displayNames?[name], dataValue: dt, stringValue: stringValue, valueType: valueTypes?[name], isOptional: !isMandatory, order: order, namespace: namespace)
if case let .map(m) = cborValue {
let innerJsonMap = CBOR.decodeDictionary(m, unwrap: false)
for (o2,(k,v)) in innerJsonMap.enumerated() {
guard let cv = v as? CBOR else { continue }
node.add(child: extractCborClaim(k, cv, bDebugDisplay, namespace, o2, displayNames, mandatory, valueTypes))
}
} else if case let .array(a) = cborValue {
let innerJsonArray = CBOR.decodeList(a, unwrap: false)
for (o2,v) in innerJsonArray.enumerated() {
guard let cv = v as? CBOR else { continue }
let k = "\(name)[\(o2)]"
node.add(child: extractCborClaim(k, cv, bDebugDisplay, namespace, o2, displayNames, mandatory, valueTypes))
}
}
return node
}

/// Extracts display strings and images from the provided namespaces and populates the given arrays.
///
/// - Parameters:
/// - nameSpaces: A dictionary where the key is a `NameSpace` and the value is an array of `IssuerSignedItem`.
/// - docClaims: An inout parameter that will be populated with `DocClaim` items extracted from the namespaces.
/// - claimDisplayNames: A dictionary where the key is the elementIdentifier and the value is a string representing the label.
/// - mandatoryClaims: A dictionary where the key is the elementIdentifier and the value is a boolean indicating whether the claim is mandatory.
/// - claimValueTypes: A dictionary where the key is the elementIdentifier and the value is a string representing the value type.
/// - nsFilter: An optional array of `NameSpace` to filter/sort the extraction. Defaults to `nil`.
public static func extractCborClaims(_ nameSpaces: [NameSpace: [IssuerSignedItem]], _ docClaims: inout [DocClaim], _ claimDisplayNames: [NameSpace: [String: String]]? = nil, _ mandatoryClaims: [NameSpace: [String: Bool]]? = nil, _ claimValueTypes: [NameSpace: [String: String]]? = nil, nsFilter: [NameSpace]? = nil) {
let bDebugDisplay = UserDefaults.standard.bool(forKey: "DebugDisplay")
var order = 0
let nsFilterUsed = nsFilter ?? Array(nameSpaces.keys)
for ns in nsFilterUsed {
let items = nameSpaces[ns] ?? []
for item in items {
let n = extractCborClaim(item.elementIdentifier, item.elementValue, bDebugDisplay, ns, order, claimDisplayNames?[ns], mandatoryClaims?[ns], claimValueTypes?[ns])
docClaims.append(n)
order = order + 1
}
}
}


} // end extension

Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
/*
Copyright (c) 2023 European Commission

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import Foundation

public protocol MdocModelFactory: Sendable {
func makeMdocDecodable(id: String, createdAt: Date, issuerSigned: IssuerSigned, devicePrivateKey: CoseKeyPrivate, docType: String, displayName: String?, statusDescription: String?) -> (any MdocDecodable)?
}
/*
Copyright (c) 2023 European Commission

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import Foundation

/// A protocol to create a ``DocClaimsDecodable`` from a cbor encoded ``IssuerSigned`` struct
public protocol DocClaimsDecodableFactory: Sendable {
func makeClaimsDecodableFromCbor(id: String, createdAt: Date, issuerSigned: IssuerSigned, displayName: String?, claimDisplayNames: [NameSpace: [String: String]]?, mandatoryClaims: [NameSpace: [String: Bool]]?, claimValueTypes: [NameSpace: [String: String]]?) -> (any DocClaimsDecodable)?
}
22 changes: 22 additions & 0 deletions Sources/MdocDataModel18013/DocumentClaims/DocDataFormat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation
/// Format of document data
/// ``cbor``: IssuerSigned struct cbor encoded
/// ``sdjwt``: vc+sd-jwt encoded
///
/// Raw value must be a 4-length string due to keychain requirements
public enum DocDataFormat: String, Sendable, CustomStringConvertible, CustomDebugStringConvertible, Codable {
case cbor = "cbor"
case sdjwt = "sjwt"

/// A description to display
public var description: String {
switch self {
case .cbor: return "mso_mdoc"
case .sdjwt: return "vc+sd-jwt"
}
}

public var debugDescription: String {
description
}
}
27 changes: 0 additions & 27 deletions Sources/MdocDataModel18013/DocumentProtocol.swift

This file was deleted.

Loading

0 comments on commit f8e5fb7

Please sign in to comment.