diff --git a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift index ce3cc7c8..9514fb7b 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift +++ b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift @@ -47,8 +47,7 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable { self.fileManager = fileManager } - @MainActor - public func getVersion() async -> String { + public static func libdigidocppVersion() -> String { return DigiDocContainerWrapper.libdigidocppVersion() } diff --git a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapperProtocol.swift b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapperProtocol.swift index 316f52b6..3381dab5 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapperProtocol.swift +++ b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapperProtocol.swift @@ -22,7 +22,6 @@ import CommonsLib /// @mockable public protocol ContainerWrapperProtocol: Sendable { - func getVersion() async -> String func getSignatures() async -> [SignatureWrapper] func getDataFiles() async -> [DataFileWrapper] func getMimetype() async -> String diff --git a/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/Container/ContainerWrapperTests.swift b/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/Container/ContainerWrapperTests.swift index 7e1d4293..92e1327a 100644 --- a/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/Container/ContainerWrapperTests.swift +++ b/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/Container/ContainerWrapperTests.swift @@ -447,6 +447,11 @@ struct ContainerWrapperTests { } } + @Test + func libdigidocppVersion_isNonEmpty() { + #expect(!ContainerWrapper.libdigidocppVersion().isEmpty) + } + @discardableResult private func createSampleContainer(dataFileURLs: [URL?]) async throws -> SignedContainerProtocol { let dataFilesUrls: [URL] = dataFileURLs.compactMap { $0 } diff --git a/Modules/UtilsLib/Sources/UtilsLib/DI/UtilsLibContainer.swift b/Modules/UtilsLib/Sources/UtilsLib/DI/UtilsLibContainer.swift index f483c171..92b3b2c9 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/DI/UtilsLibContainer.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/DI/UtilsLibContainer.swift @@ -54,8 +54,4 @@ extension Container { public var notificationUtil: Factory { self { @MainActor in NotificationUtil() } } - - public var userAgentUtil: Factory { - self { UserAgentUtil() } - } } diff --git a/Modules/UtilsLib/Sources/UtilsLib/System/SystemUtil.swift b/Modules/UtilsLib/Sources/UtilsLib/System/SystemUtil.swift index 00defa74..99b226bc 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/System/SystemUtil.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/System/SystemUtil.swift @@ -35,4 +35,17 @@ public struct SystemUtil: Loggable { logger().info("Operating system version: \(versionString, privacy: .public)") return versionString } + + public static func getDeviceModelIdentifier() -> String { + if let simulatorModel = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] { + return simulatorModel.lowercased() + } + + var systemInfo = utsname() + uname(&systemInfo) + let identifier = withUnsafeBytes(of: &systemInfo.machine) { raw in + String(cString: raw.bindMemory(to: CChar.self).baseAddress!) + } + return identifier.lowercased() + } } diff --git a/Modules/UtilsLib/Sources/UtilsLib/User-Agent/DeviceCategory.swift b/Modules/UtilsLib/Sources/UtilsLib/User-Agent/DeviceCategory.swift new file mode 100644 index 00000000..59fc3ac1 --- /dev/null +++ b/Modules/UtilsLib/Sources/UtilsLib/User-Agent/DeviceCategory.swift @@ -0,0 +1,36 @@ +/* + * Copyright 2017 - 2026 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation + +enum DeviceCategory: String { + case mobile + case tablet + + init(modelIdentifier: String) { + self = modelIdentifier.hasPrefix("ipad") ? .tablet : .mobile + } + + var osName: String { + switch self { + case .mobile: "iOS" + case .tablet: "iPadOS" + } + } +} diff --git a/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtil.swift b/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtil.swift index df43cf74..9805751d 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtil.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtil.swift @@ -23,56 +23,89 @@ import ExternalAccessory public struct UserAgentUtil: UserAgentUtilProtocol { + private static let schemaVersion = 1 + + private let libdigidocppVersion: String + + public init(libdigidocppVersion: String = "") { + self.libdigidocppVersion = libdigidocppVersion + } + public func userAgent( diagnostics: UserAgentDiagnostics = .none, language: String ) -> String { - var components: [String] = [ - appIdentifier(), - osInfo(), - languageInfo(language: language) - ] - - switch diagnostics { - case .none: - break - - case .devices: - if let devicesInfo = devicesInfo() { - components.append(devicesInfo) - } + let info = appInfo(diagnostics: diagnostics, language: language) - case .nfc: - components.append("NFC: true") - } - - return components.joined(separator: " ") + guard !libdigidocppVersion.isEmpty else { return "APP \(info)" } + return "LIB libdigidocpp/\(libdigidocppVersion) (\(architecture())) APP \(info)" } - private func appIdentifier() -> String { - "riadigidoc/\(appVersion())" + public func appInfo( + diagnostics: UserAgentDiagnostics = .none, + language: String + ) -> String { + let metadata = metadataFields(diagnostics: diagnostics, language: language) + .joined(separator: "; ") + return "\(appIdentifier()) (\(metadata))" } - private func osInfo() -> String { - "(iOS \(SystemUtil.getOSVersion()))" + private func metadataFields( + diagnostics: UserAgentDiagnostics, + language: String + ) -> [String] { + let model = SystemUtil.getDeviceModelIdentifier() + let category = DeviceCategory(modelIdentifier: model) + + let diagnosticsField: String? = switch diagnostics { + case .none: nil + case .devices: devicesInfo().map { "devices=\($0)" } + case .nfc: "nfc=true" + } + + return [ + "schema=\(Self.schemaVersion)", + "os=\(category.osName) \(SystemUtil.getOSVersion())", + "lang=\(sanitizeField(language))", + "devicetype=\(category.rawValue)/\(sanitizeField(model))", + diagnosticsField + ].compactMap { $0 } } - private func languageInfo(language: String) -> String { - return "Lang: \(language)" + private func appIdentifier() -> String { + "riadigidoc/\(BundleUtil.getAppVersion())" } - private func appVersion() -> String { - return BundleUtil.getAppVersion() + private func architecture() -> String { + #if arch(arm64) + return "arm64" + #elseif arch(x86_64) + return "x86_64" + #elseif arch(arm) + return "arm" + #elseif arch(i386) + return "i386" + #else + return "unknown" + #endif } private func devicesInfo() -> String? { let devices = EAAccessoryManager.shared() .connectedAccessories .map { - "\($0.manufacturer) \($0.name) (\($0.modelNumber))" + sanitizeField("\($0.manufacturer) \($0.name) \($0.modelNumber)") } + .filter { !$0.isEmpty } guard !devices.isEmpty else { return nil } - return "Devices: \(devices.joined(separator: ", "))" + return devices.joined(separator: ", ") + } + + // Remove delimiters and line breaks so a field can't break the header structure. + private func sanitizeField(_ value: String) -> String { + let forbidden = CharacterSet(charactersIn: ";()\u{2028}\u{2029}").union(.controlCharacters) + let cleaned = String.UnicodeScalarView(value.unicodeScalars.filter { !forbidden.contains($0) }) + return String(cleaned).trimmingCharacters(in: .whitespacesAndNewlines) } } diff --git a/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtilProtocol.swift b/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtilProtocol.swift index 1c47ed81..f4e63ecd 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtilProtocol.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/User-Agent/UserAgentUtilProtocol.swift @@ -25,4 +25,9 @@ public protocol UserAgentUtilProtocol: Sendable { diagnostics: UserAgentDiagnostics, language: String ) -> String + + func appInfo( + diagnostics: UserAgentDiagnostics, + language: String + ) -> String } diff --git a/Modules/UtilsLib/Tests/UtilsLibTests/System/SystemUtilTests.swift b/Modules/UtilsLib/Tests/UtilsLibTests/System/SystemUtilTests.swift index 10bfdc12..4a94a0ea 100644 --- a/Modules/UtilsLib/Tests/UtilsLibTests/System/SystemUtilTests.swift +++ b/Modules/UtilsLib/Tests/UtilsLibTests/System/SystemUtilTests.swift @@ -41,4 +41,23 @@ struct SystemUtilTests { #expect(match != nil) } } + + @Test + func getDeviceModelIdentifier_isNonEmptyAndLowercased() { + let model = SystemUtil.getDeviceModelIdentifier() + + #expect(!model.isEmpty) + #expect(model == model.lowercased()) + #expect(!model.contains(" ")) + #expect(!model.contains("\0")) + } + + @Test + func getDeviceModelIdentifier_matchesSimulatorEnvironmentWhenPresent() { + guard let simulatorModel = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] else { + return + } + + #expect(SystemUtil.getDeviceModelIdentifier() == simulatorModel.lowercased()) + } } diff --git a/Modules/UtilsLib/Tests/UtilsLibTests/User-Agent/DeviceCategoryTests.swift b/Modules/UtilsLib/Tests/UtilsLibTests/User-Agent/DeviceCategoryTests.swift new file mode 100644 index 00000000..02393126 --- /dev/null +++ b/Modules/UtilsLib/Tests/UtilsLibTests/User-Agent/DeviceCategoryTests.swift @@ -0,0 +1,50 @@ +/* + * Copyright 2017 - 2026 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation +import Testing + +@testable import UtilsLib + +struct DeviceCategoryTests { + + @Test( + "iPad models map to tablet/iPadOS", + arguments: ["ipad13,4", "ipad8,12", "ipad14,1", "ipad"] + ) + func tabletModels(model: String) { + let category = DeviceCategory(modelIdentifier: model) + + #expect(category == .tablet) + #expect(category.rawValue == "tablet") + #expect(category.osName == "iPadOS") + } + + @Test( + "Non-iPad models map to mobile/iOS", + arguments: ["iphone15,2", "iphone16,2", "ipod9,1", "", "x86_64"] + ) + func mobileModels(model: String) { + let category = DeviceCategory(modelIdentifier: model) + + #expect(category == .mobile) + #expect(category.rawValue == "mobile") + #expect(category.osName == "iOS") + } +} diff --git a/Modules/UtilsLib/Tests/UtilsLibTests/User-Agent/UserAgentUtilTests.swift b/Modules/UtilsLib/Tests/UtilsLibTests/User-Agent/UserAgentUtilTests.swift index cc5a31b4..ed48cd0a 100644 --- a/Modules/UtilsLib/Tests/UtilsLibTests/User-Agent/UserAgentUtilTests.swift +++ b/Modules/UtilsLib/Tests/UtilsLibTests/User-Agent/UserAgentUtilTests.swift @@ -25,37 +25,203 @@ import Testing struct UserAgentUtilTests { @Test( - "User-Agent contains expected info", + "User-Agent follows schema structure", arguments: [ (UserAgentDiagnostics.none, "en"), (UserAgentDiagnostics.devices, "en"), (UserAgentDiagnostics.nfc, "en"), - (UserAgentDiagnostics.none, "ee"), - (UserAgentDiagnostics.devices, "ee"), - (UserAgentDiagnostics.nfc, "ee") + (UserAgentDiagnostics.none, "et"), + (UserAgentDiagnostics.devices, "et"), + (UserAgentDiagnostics.nfc, "et") ] ) - func userAgent_containsExpectedUserAgentInfo(diagnostics: UserAgentDiagnostics, language: String) { + func userAgent_followsSchemaStructure(diagnostics: UserAgentDiagnostics, language: String) throws { let userAgentUtil = UserAgentUtil() let userAgent = userAgentUtil.userAgent(diagnostics: diagnostics, language: language) - #expect(userAgent.starts(with: "riadigidoc/")) - #expect(userAgent.contains("(iOS ")) - #expect(userAgent.contains("Lang: \(language)")) + #expect(userAgent.starts(with: "APP riadigidoc/")) - switch diagnostics { - case .none: - #expect(!userAgent.contains("Devices:")) - #expect(!userAgent.contains("NFC:")) + let block = try #require(metadataBlock(of: userAgent), "Expected a parenthesized metadata block") - case .devices: - #expect(userAgent.contains("Devices:") || !userAgent.contains("Devices:")) - #expect(!userAgent.contains("NFC:")) + let identifier = String(userAgent.prefix(while: { $0 != "(" })).trimmingCharacters(in: .whitespaces) + #expect(identifier.hasPrefix("APP riadigidoc/")) + #expect(identifier.count > "APP riadigidoc/".count, "Expected a version after the identifier") - case .nfc: - #expect(!userAgent.contains("Devices:")) - #expect(userAgent.contains("NFC: true")) + #expect(block.contains("schema=1")) + #expect(block.contains("lang=\(language)")) + + let osIsIOS = block.contains("os=iOS ") + let osIsIPadOS = block.contains("os=iPadOS ") + #expect(osIsIOS || osIsIPadOS) + + let isMobile = block.contains("devicetype=mobile/") + let isTablet = block.contains("devicetype=tablet/") + #expect(isMobile || isTablet) + + #expect(osIsIPadOS == isTablet) + } + + @Test + func userAgent_mandatoryFieldsAppearInOrder() throws { + let userAgentUtil = UserAgentUtil() + + let block = try #require(metadataBlock(of: userAgentUtil.userAgent(diagnostics: .none, language: "et"))) + + let schemaIndex = try #require(block.range(of: "schema=")).lowerBound + let osIndex = try #require(block.range(of: "os=")).lowerBound + let langIndex = try #require(block.range(of: "lang=")).lowerBound + let deviceTypeIndex = try #require(block.range(of: "devicetype=")).lowerBound + + #expect(schemaIndex < osIndex) + #expect(osIndex < langIndex) + #expect(langIndex < deviceTypeIndex) + } + + @Test + func userAgent_fieldsSeparatedBySemicolon() throws { + let userAgentUtil = UserAgentUtil() + + let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: "en") + + #expect(userAgent.filter { $0 == "(" }.count == 1) + #expect(userAgent.filter { $0 == ")" }.count == 1) + + let block = try #require(metadataBlock(of: userAgent)) + let fields = block.components(separatedBy: "; ") + #expect(fields.count == 4) + #expect(fields[0] == "schema=1") + #expect(fields[1].hasPrefix("os=")) + #expect(fields[2].hasPrefix("lang=")) + #expect(fields[3].hasPrefix("devicetype=")) + } + + @Test( + ".none omits devices and nfc fields", + arguments: ["en", "et"] + ) + func userAgent_noneDiagnosticsNoDevicesAndNFCFields(language: String) { + let userAgentUtil = UserAgentUtil() + + let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: language) + + #expect(!userAgent.contains("nfc=")) + #expect(!userAgent.contains("devices=")) + } + + @Test( + ".nfc diagnostics appends nfc and shows no devices", + arguments: ["en", "et"] + ) + func userAgent_nfcDiagnostics(language: String) { + let userAgentUtil = UserAgentUtil() + + let userAgent = userAgentUtil.userAgent(diagnostics: .nfc, language: language) + + #expect(userAgent.contains("; nfc=true")) + #expect(!userAgent.contains("devices=")) + #expect(userAgent.hasSuffix("nfc=true)")) + } + + @Test + func userAgent_devicesDoesntShowNFC() throws { + let userAgentUtil = UserAgentUtil() + + let userAgent = userAgentUtil.userAgent(diagnostics: .devices, language: "en") + + #expect(!userAgent.contains("nfc=")) + + let block = try #require(metadataBlock(of: userAgent)) + if let devicesRange = block.range(of: "devices=") { + let deviceTypeIndex = try #require(block.range(of: "devicetype=")).lowerBound + #expect(deviceTypeIndex < devicesRange.lowerBound) + } + } + + @Test + func userAgent_prependsLibdigidocppPrefixWhenVersionProvided() { + let userAgentUtil = UserAgentUtil(libdigidocppVersion: "4.3.0.1910") + + let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: "et") + + #expect(userAgent.hasPrefix("LIB libdigidocpp/4.3.0.1910 (")) + #expect(userAgent.contains(") APP riadigidoc/")) + #expect(userAgent.hasSuffix(")")) + } + + @Test + func userAgent_libdigidocppPrefixUsesKnownArchitecture() { + let userAgentUtil = UserAgentUtil(libdigidocppVersion: "4.3.0.1910") + + let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: "et") + + let knownArchitectures = ["arm64", "x86_64", "arm", "i386"] + #expect(knownArchitectures.contains { userAgent.contains("(\($0)) APP ") }) + } + + @Test + func userAgent_omitsLibdigidocppPrefixWhenVersionEmpty() { + let userAgentUtil = UserAgentUtil() + + let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: "en") + + #expect(!userAgent.contains("LIB libdigidocpp/")) + #expect(userAgent.hasPrefix("APP riadigidoc/")) + } + + @Test( + "Delimiters, control characters and surrounding whitespace are stripped from fields", + arguments: [ + ("e;n", "en"), + ("e(n)", "en"), + ("en\n", "en"), + ("e\u{2028}n\u{2029}", "en"), + (" en ", "en") + ] + ) + func userAgent_sanitizesFields(rawLanguage: String, expected: String) throws { + let userAgent = UserAgentUtil().userAgent(diagnostics: .none, language: rawLanguage) + + let block = try #require(metadataBlock(of: userAgent)) + + #expect(block.contains("lang=\(expected)")) + #expect(block.components(separatedBy: "; ").count == 4) + } + + @Test + func appInfo_isTheAppPartWithoutLibOrAppTokens() { + let appInfo = UserAgentUtil(libdigidocppVersion: "4.3.0.1910").appInfo(diagnostics: .none, language: "et") + + #expect(appInfo.hasPrefix("riadigidoc/")) + #expect(!appInfo.contains("LIB libdigidocpp/")) + #expect(!appInfo.hasPrefix("APP ")) + #expect(appInfo.contains("(schema=1;")) + } + + @Test + func appInfo_isIndependentOfLibdigidocppVersion() { + let withVersion = UserAgentUtil(libdigidocppVersion: "4.3.0.1910").appInfo(diagnostics: .none, language: "en") + let withoutVersion = UserAgentUtil().appInfo(diagnostics: .none, language: "en") + + #expect(withVersion == withoutVersion) + } + + @Test + func userAgent_wrapsAppInfoWithLibAndAppTokens() { + let userAgentUtil = UserAgentUtil(libdigidocppVersion: "4.3.0.1910") + + let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: "et") + let appInfo = userAgentUtil.appInfo(diagnostics: .none, language: "et") + + #expect(userAgent.hasSuffix("APP \(appInfo)")) + } + + private func metadataBlock(of userAgent: String) -> String? { + guard let open = userAgent.lastIndex(of: "("), + let close = userAgent.lastIndex(of: ")"), + open < close else { + return nil } + return String(userAgent[open...close].dropFirst().dropLast()) } } diff --git a/RIADigiDoc.xcodeproj/project.pbxproj b/RIADigiDoc.xcodeproj/project.pbxproj index d741f1f0..9a85f071 100644 --- a/RIADigiDoc.xcodeproj/project.pbxproj +++ b/RIADigiDoc.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ DF4E43CB2D0BA38600967997 /* FileImportShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DF4E43C12D0BA38600967997 /* FileImportShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DF4E6D822D11DE7B00F6111A /* CommonsLib in Frameworks */ = {isa = PBXBuildFile; productRef = DF4E6D812D11DE7B00F6111A /* CommonsLib */; }; DF4E6D842D120CFC00F6111A /* UtilsLib in Frameworks */ = {isa = PBXBuildFile; productRef = DF4E6D832D120CFC00F6111A /* UtilsLib */; }; + DF54F82F2D431BD50021D05A /* X509 in Frameworks */ = {isa = PBXBuildFile; productRef = DF54F82E2D431BD50021D05A /* X509 */; }; DF5903762ECCC41F00D1A278 /* MobileIdLibMocks in Frameworks */ = {isa = PBXBuildFile; productRef = DF5903752ECCC41F00D1A278 /* MobileIdLibMocks */; }; DF5903782ECCC42500D1A278 /* SmartIdLibMocks in Frameworks */ = {isa = PBXBuildFile; productRef = DF5903772ECCC42500D1A278 /* SmartIdLibMocks */; }; DF5C54272E82F07A006E2251 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DFA3EE5C2D14C9B7001D951B /* Alamofire */; }; @@ -53,7 +54,6 @@ DFB663A22F16917E00804545 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB663A12F16917E00804545 /* SwiftUI.framework */; }; DFB663AF2F16918100804545 /* WidgetExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DFB6639E2F16917D00804545 /* WidgetExtensionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DFBD09F62CE3FBDC006AF9C2 /* LibdigidocLib in Frameworks */ = {isa = PBXBuildFile; productRef = DFBD09F52CE3FBDC006AF9C2 /* LibdigidocLib */; }; - DF54F82F2D431BD50021D05A /* X509 in Frameworks */ = {isa = PBXBuildFile; productRef = DF54F82E2D431BD50021D05A /* X509 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1704,6 +1704,14 @@ version = 12.12.1; }; }; + DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-certificates.git"; + requirement = { + kind = exactVersion; + version = 1.19.0; + }; + }; DF9AFE902E00D30A0062C64D /* XCRemoteSwiftPackageReference "Factory" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/hmlongco/Factory"; @@ -1728,14 +1736,6 @@ version = 0.63.2; }; }; - DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-certificates.git"; - requirement = { - kind = exactVersion; - version = 1.19.0; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1818,6 +1818,11 @@ isa = XCSwiftPackageProductDependency; productName = UtilsLib; }; + DF54F82E2D431BD50021D05A /* X509 */ = { + isa = XCSwiftPackageProductDependency; + package = DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */; + productName = X509; + }; DF5903752ECCC41F00D1A278 /* MobileIdLibMocks */ = { isa = XCSwiftPackageProductDependency; productName = MobileIdLibMocks; @@ -1917,11 +1922,6 @@ isa = XCSwiftPackageProductDependency; productName = LibdigidocLib; }; - DF54F82E2D431BD50021D05A /* X509 */ = { - isa = XCSwiftPackageProductDependency; - package = DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */; - productName = X509; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = DFDB14732CC97B0E00153876 /* Project object */; diff --git a/RIADigiDoc/DI/AppContainer.swift b/RIADigiDoc/DI/AppContainer.swift index ebdf4655..0a31cd76 100644 --- a/RIADigiDoc/DI/AppContainer.swift +++ b/RIADigiDoc/DI/AppContainer.swift @@ -22,9 +22,15 @@ import ConfigLib import FactoryKit import Foundation import UtilsLib +import LibdigidocLibSwift import nfclib extension Container { + var userAgentUtil: Factory { + self { UserAgentUtil(libdigidocppVersion: ContainerWrapper.libdigidocppVersion()) } + .singleton + } + @MainActor var librarySetup: Factory { self { @MainActor in @@ -238,7 +244,6 @@ extension Container { var diagnosticsViewModel: Factory { self { @MainActor in DiagnosticsViewModel( - containerWrapper: self.containerWrapper(), fileManager: self.fileManager(), configurationLoader: self.configurationLoader(), configurationRepository: self.configurationRepository(), diff --git a/RIADigiDoc/LibrarySetup.swift b/RIADigiDoc/LibrarySetup.swift index 78777720..103aae7f 100644 --- a/RIADigiDoc/LibrarySetup.swift +++ b/RIADigiDoc/LibrarySetup.swift @@ -77,6 +77,7 @@ actor LibrarySetup: Loggable { let proxyInfo = await proxyUtil.getProxyInfo() let appLanguage = await dataStore.getSelectedLanguage() let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: appLanguage) + let appInfo = userAgentUtil.appInfo(diagnostics: .none, language: appLanguage) try DigiDocConf.observeConfigurationUpdates( configurationRepository: configurationRepository @@ -114,7 +115,7 @@ actor LibrarySetup: Loggable { sivaUrl: getSiVaUrl(), sivaCert: getSiVaCert(), proxyInfo: proxyInfo, - userAgent: userAgent + userAgent: appInfo ) LibrarySetup.logger().info("Libdigidocpp initialized successfully") diff --git a/RIADigiDoc/RIADigiDocApp.swift b/RIADigiDoc/RIADigiDocApp.swift index d144dc7f..279c7ea1 100644 --- a/RIADigiDoc/RIADigiDocApp.swift +++ b/RIADigiDoc/RIADigiDocApp.swift @@ -19,7 +19,6 @@ import SwiftUI import FactoryKit -import LibdigidocLibSwift import UtilsLib import ConfigLib import CommonsLib diff --git a/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift b/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift index 09f883c3..8764bf90 100644 --- a/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift +++ b/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift @@ -45,7 +45,6 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { var centralConfigurationSectionContent: [(key: String, content: String)] = [(key: "", content: "")] // MARK: - dependencies - private let containerWrapper: ContainerWrapperProtocol private let fileManager: FileManagerProtocol private let configurationLoader: ConfigurationLoaderProtocol private let configurationRepository: ConfigurationRepositoryProtocol @@ -59,7 +58,6 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { private var configurationObservationTask: Task? init( - containerWrapper: ContainerWrapperProtocol, fileManager: FileManagerProtocol, configurationLoader: ConfigurationLoaderProtocol, configurationRepository: ConfigurationRepositoryProtocol, @@ -70,7 +68,6 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { fileUtil: FileUtilProtocol, cryptoSetup: CryptoSetupProtocol ) { - self.containerWrapper = containerWrapper self.fileManager = fileManager self.configurationLoader = configurationLoader self.configurationRepository = configurationRepository @@ -85,8 +82,9 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { await observeConfigurationUpdates() } + loadLibdigidocVersion() + Task { - await loadLibdigidocVersion() await loadLoggingVariables() } } @@ -132,10 +130,10 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { self.osSectionContent = (key: "Main diagnostics operating system ios", content: SystemUtil.getOSVersion()) } - private func loadLibdigidocVersion() async { - let libdigidocVersion = await containerWrapper.getVersion() + private func loadLibdigidocVersion() { + let version = ContainerWrapper.libdigidocppVersion() - self.libdigidocVersion = "libdigidocpp \(libdigidocVersion)" + self.libdigidocVersion = "libdigidocpp \(version)" } private func loadUrlSectionContent(configuration: ConfigurationProvider?) async { diff --git a/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift b/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift index 0cd05bed..c50d650a 100644 --- a/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift +++ b/RIADigiDoc/ViewModel/Signing/MobileId/MobileIdViewModel.swift @@ -141,6 +141,7 @@ class MobileIdViewModel: MobileIdViewModelProtocol, Loggable { MobileIdViewModel.logger().info("Mobile-ID: Getting User-Agent") let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: appLanguage) + let appInfo = userAgentUtil.appInfo(diagnostics: .none, language: appLanguage) let containerFile: URL do { @@ -175,7 +176,7 @@ class MobileIdViewModel: MobileIdViewModelProtocol, Loggable { containerFile: containerFile, roleData: roleData, signedContainer: signedContainer, - userAgent: userAgent + userAgent: appInfo ) } catch { MobileIdViewModel.logger().info("Mobile-ID: Unable to prepare signature for signing") diff --git a/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift b/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift index bd537e1c..834a987a 100644 --- a/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift +++ b/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift @@ -399,7 +399,7 @@ class NFCViewModel: NFCViewModelProtocol, Loggable { let appLanguage = await dataStore.getSelectedLanguage() NFCViewModel.logger().info("NFC: Getting User-Agent") - let userAgent = userAgentUtil.userAgent(diagnostics: .nfc, language: appLanguage) + let appInfo = userAgentUtil.appInfo(diagnostics: .nfc, language: appLanguage) do { NFCViewModel.logger().info("NFC: Starting signing operation") @@ -409,7 +409,7 @@ class NFCViewModel: NFCViewModelProtocol, Loggable { signedContainer: signedContainer, containerPath: containerFile, roleData: roleData, - userAgent: userAgent, + userAgent: appInfo, strings: strings ) NFCViewModel.logger().info("NFC: Signature added successfully") diff --git a/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift b/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift index b152c285..45da0423 100644 --- a/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift +++ b/RIADigiDoc/ViewModel/Signing/SmartId/SmartIdViewModel.swift @@ -155,6 +155,7 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { SmartIdViewModel.logger().info("Smart-ID: Getting User-Agent") let userAgent = userAgentUtil.userAgent(diagnostics: .none, language: appLanguage) + let appInfo = userAgentUtil.appInfo(diagnostics: .none, language: appLanguage) let containerFile: URL do { @@ -212,7 +213,7 @@ class SmartIdViewModel: SmartIdViewModelProtocol, Loggable { containerFile: containerFile, roleData: roleData, signedContainer: signedContainer, - userAgent: userAgent + userAgent: appInfo ) } catch { SmartIdViewModel.logger().info("Smart-ID: Unable to prepare signature for signing") diff --git a/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift b/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift index 8d0da47b..8ce362f7 100644 --- a/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift +++ b/RIADigiDocTests/ViewModel/DiagnosticsViewModelTests.swift @@ -33,7 +33,6 @@ import UtilsLibMocks final class DiagnosticsViewModelTests { private let viewModel: DiagnosticsViewModel! - private let mockContainerWrapper: ContainerWrapperProtocolMock private let mockFileManager: FileManagerProtocolMock private let mockConfigurationLoader: ConfigurationLoaderProtocolMock private let mockConfigurationRepository: ConfigurationRepositoryProtocolMock @@ -47,7 +46,6 @@ final class DiagnosticsViewModelTests { let mockConfigProvider: ConfigurationProvider? init() async throws { - mockContainerWrapper = ContainerWrapperProtocolMock() mockFileManager = FileManagerProtocolMock() mockConfigurationLoader = ConfigurationLoaderProtocolMock() mockConfigurationRepository = ConfigurationRepositoryProtocolMock() @@ -67,7 +65,6 @@ final class DiagnosticsViewModelTests { mockProxyUtil.getProxyInfoHandler = { ProxyInfo() } viewModel = DiagnosticsViewModel( - containerWrapper: mockContainerWrapper, fileManager: mockFileManager, configurationLoader: mockConfigurationLoader, configurationRepository: mockConfigurationRepository, diff --git a/RIADigiDocTests/ViewModel/Signing/NFC/NFCViewModelTests.swift b/RIADigiDocTests/ViewModel/Signing/NFC/NFCViewModelTests.swift index f844d052..2e2a1738 100644 --- a/RIADigiDocTests/ViewModel/Signing/NFC/NFCViewModelTests.swift +++ b/RIADigiDocTests/ViewModel/Signing/NFC/NFCViewModelTests.swift @@ -401,7 +401,7 @@ final class NFCViewModelTests { "et" } - mockUserAgentUtil.userAgentHandler = { _, language in + mockUserAgentUtil.appInfoHandler = { _, language in #expect(language == "et") return "TestUserAgent" } @@ -416,7 +416,7 @@ final class NFCViewModelTests { #expect(mockContainer.getRawContainerFileCallCount == 1) #expect(mockDataStore.getSelectedLanguageCallCount == 1) - #expect(mockUserAgentUtil.userAgentCallCount == 1) + #expect(mockUserAgentUtil.appInfoCallCount == 1) #expect(mockOperationReadCertAndSign.startOperationCallCount == 1) #expect(viewModel.nfcErrorKey == nil) } @@ -441,7 +441,7 @@ final class NFCViewModelTests { "et" } - mockUserAgentUtil.userAgentHandler = { _, language in + mockUserAgentUtil.appInfoHandler = { _, language in #expect(language == "et") return "TestUserAgent" } @@ -462,7 +462,7 @@ final class NFCViewModelTests { #expect(mockContainer.getRawContainerFileCallCount == 1) #expect(mockDataStore.getSelectedLanguageCallCount == 1) - #expect(mockUserAgentUtil.userAgentCallCount == 1) + #expect(mockUserAgentUtil.appInfoCallCount == 1) #expect(viewModel.nfcErrorKey != nil) } } @@ -488,7 +488,7 @@ final class NFCViewModelTests { "en" } - mockUserAgentUtil.userAgentHandler = { _, _ in + mockUserAgentUtil.appInfoHandler = { _, _ in "TestUserAgent" }