From 434ce0a71bb74bcea3d055391d7a200e0ff3a67f Mon Sep 17 00:00:00 2001 From: luckyrat Date: Thu, 9 Nov 2023 18:21:50 +0000 Subject: [PATCH 1/3] Update dependencies, Flutter version, Kotlin version, logging system --- .fvm/fvm_config.json | 2 +- android/app/build.gradle | 7 +- android/app/proguard-rules.pro | 10 +- .../com/keevault/keevault/AutofillActivity.kt | 9 +- .../com/keevault/keevault/MainActivity.kt | 7 + .../org.tinylog.provider.LoggingProvider | 1 + .../app/src/main/resources/tinylog.properties | 14 ++ android/build.gradle | 2 +- .../KdbxSwift/DatabaseFileManager.swift | 6 +- .../Sources/KdbxSwift/base32/Base32.swift | 2 +- .../KdbxSwift/crypto/CryptoManager.swift | 4 +- .../Sources/KdbxSwift/db/Database.swift | 3 +- .../Sources/KdbxSwift/db/DatabaseItem.swift | 2 +- .../Sources/KdbxSwift/db/Entry.swift | 2 +- .../KdbxSwift/db/EntryFieldReference.swift | 4 +- .../Sources/KdbxSwift/db/Group.swift | 2 +- .../Sources/KdbxSwift/db/KeyHelper.swift | 2 +- .../KdbxSwift/db/cipher/DataCipher.swift | 2 +- .../db/cipher/DataCipherFactory.swift | 2 +- .../db/cipher/StreamCipherFactory.swift | 2 +- .../Sources/KdbxSwift/db/kdf/Argon2KDF.swift | 2 +- .../Sources/KdbxSwift/db/kdf/KDFParams.swift | 2 +- .../KdbxSwift/db/kp2/Attachment2.swift | 4 +- .../Sources/KdbxSwift/db/kp2/Binary2.swift | 2 +- .../KdbxSwift/db/kp2/CustomData2.swift | 10 +- .../KdbxSwift/db/kp2/CustomIcon2.swift | 4 +- .../Sources/KdbxSwift/db/kp2/Database2.swift | 36 ++-- .../KdbxSwift/db/kp2/DeletedObject2.swift | 4 +- .../Sources/KdbxSwift/db/kp2/Entry2.swift | 14 +- .../Sources/KdbxSwift/db/kp2/Group2.swift | 10 +- .../Sources/KdbxSwift/db/kp2/Header2.swift | 54 +++--- .../Sources/KdbxSwift/db/kp2/KeyHelper2.swift | 2 +- .../Sources/KdbxSwift/db/kp2/Meta2.swift | 22 +-- .../db/totp/TOTPGeneratorFactory.swift | 28 +-- .../Sources/KdbxSwift/db/util/ByteArray.swift | 2 +- .../Sources/KdbxSwift/util/Extensions.swift | 2 +- .../Sources/KdbxSwift/util/Logger.swift | 15 +- .../EntryListViewController.swift | 3 + ios/KeeVaultAutofill/MyPuppyLogHandler.swift | 122 +++++++++++++ ios/Podfile | 9 + ios/Podfile.lock | 18 +- ios/Runner.xcodeproj/project.pbxproj | 44 ++++- .../xcshareddata/swiftpm/Package.resolved | 18 ++ .../xcshareddata/swiftpm/Package.resolved | 18 ++ ios/Runner/AppDelegate.swift | 25 +++ lib/credentials/quick_unlocker.dart | 14 +- lib/cubit/autofill_cubit.dart | 1 - lib/kdbx_argon2_ffi.dart | 2 +- lib/logging/logger.dart | 9 +- lib/main.dart | 10 +- lib/model/entry.dart | 6 +- lib/model/in_app_message.dart | 14 +- lib/vault_backend/jwt.dart | 2 +- lib/widgets/account_create.dart | 2 +- lib/widgets/binaries.dart | 2 +- lib/widgets/entry.dart | 10 +- lib/widgets/entry_field.dart | 2 +- lib/widgets/import_export.dart | 4 +- lib/widgets/kee_vault_app.dart | 2 +- lib/widgets/vault.dart | 2 +- pubspec.lock | 165 ++++++++---------- pubspec.yaml | 55 +++--- 62 files changed, 567 insertions(+), 290 deletions(-) create mode 100644 android/app/src/main/resources/META-INF/services/org.tinylog.provider.LoggingProvider create mode 100644 android/app/src/main/resources/tinylog.properties create mode 100644 ios/KeeVaultAutofill/MyPuppyLogHandler.swift diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index e341c5b..d8abe1b 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.13.2", + "flutterSdkVersion": "3.13.9", "flavors": {} } \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 1a01e3f..0fe2d96 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -108,8 +108,8 @@ android { defaultConfig { applicationId "com.keevault.keevault" applicationIdSuffix idSuffix - minSdkVersion 26 - targetSdkVersion 33 + minSdkVersion 29 + targetSdk 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName resValue "string", "app_name", "Kee Vault" + nameSuffix @@ -148,5 +148,8 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'org.tinylog:tinylog-api:2.6.2' + implementation 'org.tinylog:tinylog-impl:2.6.2' + implementation 'org.tinylog:slf4j-tinylog:2.6.2' androidTestUtil "androidx.test:orchestrator:1.4.2" } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 116bc22..de4d436 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -1 +1,9 @@ --keep class androidx.lifecycle.DefaultLifecycleObserver \ No newline at end of file +-keep class androidx.lifecycle.DefaultLifecycleObserver +-keepnames interface org.tinylog.** +-keepnames class * implements org.tinylog.** +-keepclassmembers class * implements org.tinylog.** { (...); } + +-dontwarn dalvik.system.VMStack +-dontwarn java.lang.** +-dontwarn javax.naming.** +-dontwarn sun.reflect.Reflection \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/keevault/keevault/AutofillActivity.kt b/android/app/src/main/kotlin/com/keevault/keevault/AutofillActivity.kt index 27a0916..96c6ba6 100644 --- a/android/app/src/main/kotlin/com/keevault/keevault/AutofillActivity.kt +++ b/android/app/src/main/kotlin/com/keevault/keevault/AutofillActivity.kt @@ -4,6 +4,13 @@ import android.content.Intent import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine +import android.os.Bundle -class AutofillActivity: FlutterFragmentActivity() { +class AutofillActivity(): FlutterFragmentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + System.setProperty("logs.folder", filesDir.absolutePath + "/logs"); + + super.onCreate(savedInstanceState) + } + } diff --git a/android/app/src/main/kotlin/com/keevault/keevault/MainActivity.kt b/android/app/src/main/kotlin/com/keevault/keevault/MainActivity.kt index 087fb07..ad166fb 100644 --- a/android/app/src/main/kotlin/com/keevault/keevault/MainActivity.kt +++ b/android/app/src/main/kotlin/com/keevault/keevault/MainActivity.kt @@ -4,6 +4,13 @@ import android.content.Intent import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine +import android.os.Bundle class MainActivity: FlutterFragmentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + System.setProperty("logs.folder", filesDir.absolutePath + "/logs"); + + super.onCreate(savedInstanceState) + } + } diff --git a/android/app/src/main/resources/META-INF/services/org.tinylog.provider.LoggingProvider b/android/app/src/main/resources/META-INF/services/org.tinylog.provider.LoggingProvider new file mode 100644 index 0000000..ab3d85a --- /dev/null +++ b/android/app/src/main/resources/META-INF/services/org.tinylog.provider.LoggingProvider @@ -0,0 +1 @@ +com.keevault.flutter_autofill_service.DynamicLevelLoggingProvider \ No newline at end of file diff --git a/android/app/src/main/resources/tinylog.properties b/android/app/src/main/resources/tinylog.properties new file mode 100644 index 0000000..d455d08 --- /dev/null +++ b/android/app/src/main/resources/tinylog.properties @@ -0,0 +1,14 @@ +provider = dynamic level +level = trace + +writer1 = logcat + +writer2 = rolling file +writer2.file = #{logs.folder}/autofill-{pid}-log-{count}.txt +writer2.charset = UTF-8 +writer2.backups = 30 +writer2.buffered = true +writer2.policies = startup, size: 1mb, dynamic +writer2.format = {date: yyyy-MM-dd HH:mm:ss.SSS} [{thread-id}_{thread}] {class}.{method}():{line}\n{level}: {message} + +writingthread = true \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index aca8b4a..f0c5fa6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.0' + ext.kotlin_version = '1.8.22' repositories { google() mavenCentral() diff --git a/ios/KdbxSwift/Sources/KdbxSwift/DatabaseFileManager.swift b/ios/KdbxSwift/Sources/KdbxSwift/DatabaseFileManager.swift index 307563b..f6ccba7 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/DatabaseFileManager.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/DatabaseFileManager.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class DatabaseFileManager { public enum Error { @@ -68,7 +68,7 @@ public class DatabaseFileManager { Logger.mainLog.debug("Loaded main KDBX") } catch { - Logger.mainLog.error("Failed to read current KDBX file [message: \(error.localizedDescription, privacy: .public)]") + Logger.mainLog.error("Failed to read current KDBX file", metadata: ["public:message": "\(error.localizedDescription)"]) Logger.fatalError("couldn't read KDBX file") } } @@ -107,7 +107,7 @@ public class DatabaseFileManager { let fileData = try targetDatabase.save() try fileData.write(to: kdbxAutofillURL, options: .atomic) } catch { - Logger.mainLog.error("Failed to write autofill KDBX file [message: \(error.localizedDescription, privacy: .public)]") + Logger.mainLog.error("Failed to write autofill KDBX file", metadata: ["public:message": "\(error.localizedDescription)"]) } } } diff --git a/ios/KdbxSwift/Sources/KdbxSwift/base32/Base32.swift b/ios/KdbxSwift/Sources/KdbxSwift/base32/Base32.swift index 4aae4ff..13acd67 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/base32/Base32.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/base32/Base32.swift @@ -25,7 +25,7 @@ // THE SOFTWARE. import Foundation -import os.log +import Logging // https://tools.ietf.org/html/rfc4648 diff --git a/ios/KdbxSwift/Sources/KdbxSwift/crypto/CryptoManager.swift b/ios/KdbxSwift/Sources/KdbxSwift/crypto/CryptoManager.swift index e18dd45..7b65e17 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/crypto/CryptoManager.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/crypto/CryptoManager.swift @@ -1,6 +1,6 @@ import Foundation import CommonCrypto.CommonHMAC -import os.log +import Logging public enum CryptoError: Error { case invalidKDFParam(kdfName: String, paramName: String) @@ -37,7 +37,7 @@ public final class CryptoManager { return SecRandomCopyBytes(kSecRandomDefault, outBytes.count, &outBytes) } if status != errSecSuccess { - Logger.mainLog.warning("Failed to generate random bytes [count: \(count, privacy: .public), status: \(status, privacy: .public)]") + Logger.mainLog.warning("Failed to generate random bytes", metadata: ["public:count": "\(count)", "public:status": "\(status)"]) throw CryptoError.rngError(code: Int(status)) } return output diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/Database.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/Database.swift index 9e8f535..1416900 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/Database.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/Database.swift @@ -1,5 +1,6 @@ import Foundation -import os.log +import Logging + public struct SearchQuery { public let includeSubgroups: Bool diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/DatabaseItem.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/DatabaseItem.swift index 8173222..8c4d483 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/DatabaseItem.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/DatabaseItem.swift @@ -1,4 +1,4 @@ -import os.log +import Logging open class DatabaseItem { public enum TouchMode { diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/Entry.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/Entry.swift index 7941e83..81f80aa 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/Entry.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/Entry.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class EntryField: Eraseable { public static let title = "Title" diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/EntryFieldReference.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/EntryFieldReference.swift index ca04af8..6d40ce2 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/EntryFieldReference.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/EntryFieldReference.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class EntryFieldReference { public enum Status { @@ -226,7 +226,7 @@ public class EntryFieldReference { _uuid = UUID(uuidString: String(value)) } guard let uuid = _uuid else { - Logger.mainLog.debug("Malformed UUID: \(value)") + Logger.mainLog.debug("Malformed UUID", metadata: ["value": "\(value)"]) return nil } result = entries.first(where: { $0.uuid == uuid }) diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/Group.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/Group.swift index ebe4ae6..c747339 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/Group.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/Group.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class Group: DatabaseItem, Eraseable { public static let defaultIconID = IconID.folder diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/KeyHelper.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/KeyHelper.swift index b13dd7d..f6bd7e7 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/KeyHelper.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/KeyHelper.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class KeyHelper { public static let compositeKeyLength = 32 diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipher.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipher.swift index 33c14e8..b5ee45d 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipher.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipher.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging protocol DataCipher: AnyObject { var uuid: UUID { get } diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipherFactory.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipherFactory.swift index 7a5c154..ad7c95f 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipherFactory.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/DataCipherFactory.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging final class DataCipherFactory { public static let instance = DataCipherFactory() diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/StreamCipherFactory.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/StreamCipherFactory.swift index bc62428..2b52710 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/StreamCipherFactory.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/cipher/StreamCipherFactory.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging internal enum ProtectedStreamAlgorithm: UInt32 { case Null = 0 diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/Argon2KDF.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/Argon2KDF.swift index e7711a9..722f383 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/Argon2KDF.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/Argon2KDF.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging class AbstractArgon2KDF { public static let saltParam = "S" diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/KDFParams.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/KDFParams.swift index 485754b..785db86 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/KDFParams.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kdf/KDFParams.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging final class KDFParams: VarDict { public static let uuidParam = "$UUID" diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Attachment2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Attachment2.swift index b435447..0772b25 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Attachment2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Attachment2.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class Attachment2: Attachment { public var id: Int @@ -56,7 +56,7 @@ public class Attachment2: Attachment { ) } default: - Logger.mainLog.error("Unexpected XML tag in Entry/Binary: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in Entry/Binary", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag(actual: tag.name, expected: "Entry/Binary/*") } } diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Binary2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Binary2.swift index 21e65f7..3ef1551 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Binary2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Binary2.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class Binary2: Eraseable { public typealias ID = Int diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift index 033b9bc..67717c0 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class CustomData2: Eraseable { @@ -71,7 +71,7 @@ public class CustomData2: Eraseable { ) Logger.mainLog.trace("Item loaded OK") default: - Logger.mainLog.error("Unexpected XML tag in CustomData: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in CustomData", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag( actual: tag.name, expected: xmlParentName + "/CustomData/*") @@ -98,20 +98,20 @@ public class CustomData2: Eraseable { case Xml2.lastModificationTime: optionalTimestamp = timeParser.xmlStringToDate(tag.value) default: - Logger.mainLog.error("Unexpected XML tag in CustomData/Item: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in CustomData/Item", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag( actual: tag.name, expected: xmlParentName + "/CustomData/Item/*") } } guard let _key = key else { - Logger.mainLog.error("Missing \(xmlParentName)/CustomData/Item/Key") + Logger.mainLog.error("Missing parent/CustomData/Item/Key", metadata: ["parent": "\(xmlParentName)"]) throw Xml2.ParsingError.malformedValue( tag: xmlParentName + "/CustomData/Item/Key", value: nil) } guard let _value = value else { - Logger.mainLog.error("Missing \(xmlParentName)/CustomData/Item/Value") + Logger.mainLog.error("Missing parent/CustomData/Item/Value", metadata: ["parent": "\(xmlParentName)"]) throw Xml2.ParsingError.malformedValue( tag: xmlParentName + "/CustomData/Item/Value", value: nil) diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomIcon2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomIcon2.swift index 4fcbd37..ef1dfa2 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomIcon2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomIcon2.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class CustomIcon2: Eraseable { public private(set) var uuid: UUID @@ -73,7 +73,7 @@ public class CustomIcon2: Eraseable { case Xml2.lastModificationTime: xmlLastModificationTime = timeParser.xmlStringToDate(tag.value) default: - Logger.mainLog.error("Unexpected XML tag in CustomIcon: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in CustomIcon", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag(actual: tag.name, expected: "CustomIcon/*") } } diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Database2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Database2.swift index 2b34376..04bfb1f 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Database2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Database2.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging protocol Database2XMLTimeFormatter { func dateToXMLString(_ date: Date) -> String @@ -156,7 +156,7 @@ public class Database2: Database { Logger.mainLog.info("Loading KDBX database") do { try header.read(data: dbFileData) - Logger.mainLog.debug("Header read OK [format: \(self.header.formatVersion, privacy: .public)]") + Logger.mainLog.debug("Header read OK", metadata: ["public:format": "\(self.header.formatVersion)"]) importMasterKey(preTransformedKeyMaterial: preTransformedKeyMaterial, cipher: header.dataCipher) var decryptedData: ByteArray let dbWithoutHeader: ByteArray = dbFileData.suffix(from: header.size) @@ -281,7 +281,7 @@ public class Database2: Database { blockIndex += 1 } - Logger.mainLog.trace("Will decrypt \(allBlocksData.count) bytes") + Logger.mainLog.trace("Will decrypt bytes", metadata: ["count": "\(allBlocksData.count)"]) #if DEBUG print("hmacKey plain: \(hmacKey.asHexString)") @@ -296,7 +296,7 @@ public class Database2: Database { key: cipherKey, iv: header.initialVector ) - Logger.mainLog.trace("Decrypted \(decryptedData.count) bytes") + Logger.mainLog.trace("Decrypted bytes", metadata: ["count": "\(decryptedData.count)"]) return decryptedData } @@ -313,11 +313,11 @@ public class Database2: Database { Logger.mainLog.debug("Parsing XML") let xmlDoc = try AEXMLDocument(xml: xmlData.asData, options: parsingOptions) if let xmlError = xmlDoc.error { - Logger.mainLog.error("Cannot parse XML: \(xmlError.localizedDescription)") + Logger.mainLog.error("Cannot parse XML", metadata: ["error": "\(xmlError.localizedDescription)"]) throw Xml2.ParsingError.xmlError(details: xmlError.localizedDescription) } guard xmlDoc.root.name == Xml2.keePassFile else { - Logger.mainLog.error("Not a KeePass XML document [xmlRoot: \(xmlDoc.root.name)]") + Logger.mainLog.error("Not a KeePass XML document", metadata: ["xmlRoot": "\(xmlDoc.root.name)"]) throw Xml2.ParsingError.notKeePassDocument } @@ -354,13 +354,13 @@ public class Database2: Database { self.root = rootGroup Logger.mainLog.debug("XML content loaded OK") } catch let error as Header2.HeaderError { - Logger.mainLog.error("Header error [reason: \(error.localizedDescription)]") + Logger.mainLog.error("Header error", metadata: ["reason": "\(error.localizedDescription)"]) throw FormatError.parsingError(reason: error.localizedDescription) } catch let error as Xml2.ParsingError { - Logger.mainLog.error("XML parsing error [reason: \(error.localizedDescription)]") + Logger.mainLog.error("XML parsing error", metadata: ["reason": "\(error.localizedDescription)"]) throw FormatError.parsingError(reason: error.localizedDescription) } catch let error as AEXMLError { - Logger.mainLog.error("Raw XML parsing error [reason: \(error.localizedDescription)]") + Logger.mainLog.error("Raw XML parsing error", metadata: ["reason": "\(error.localizedDescription)"]) throw FormatError.parsingError(reason: error.localizedDescription) } } @@ -559,7 +559,7 @@ public class Database2: Database { header.maybeUpdateFormatVersion() let formatVersion = header.formatVersion - Logger.mainLog.debug("Format version: \(formatVersion)") + Logger.mainLog.debug("Format version", metadata: ["version": "\(formatVersion)"]) do { try header.randomizeSeeds() Logger.mainLog.debug("Seeds randomized OK") @@ -622,20 +622,20 @@ public class Database2: Database { Logger.mainLog.trace("No compression required") } - Logger.mainLog.trace("Encrypting \(dataToEncrypt.count) bytes") + Logger.mainLog.trace("Encrypting bytes", metadata: ["count": "\(dataToEncrypt.count)"]) let encData = try header.dataCipher.encrypt( plainText: dataToEncrypt, key: cipherKey, iv: header.initialVector.clone()) - Logger.mainLog.trace("Encrypted \(encData.count) bytes") + Logger.mainLog.trace("Encrypted bytes", metadata: ["count": "\(encData.count)"]) try writeAsBlocksV4(to: outStream, data: encData) Logger.mainLog.trace("Blocks written OK") } catch let error as Header2.HeaderError { - Logger.mainLog.error("Header error [message: \(error.localizedDescription)]") + Logger.mainLog.error("Header error", metadata: ["message": "\(error.localizedDescription)"]) throw DatabaseError.saveError(reason: error.localizedDescription) } catch let error as GzipError { - Logger.mainLog.error("Gzip error [kind: \(String(describing: error.kind)), message: \(error.message)]") + Logger.mainLog.error("Gzip error", metadata: ["kind": "\(error.kind)", "message": "\(error.message)"]) let errMsg = String.localizedStringWithFormat( NSLocalizedString( "[Database2/Saving/Error] Data compression error: %@", @@ -645,7 +645,7 @@ public class Database2: Database { error.localizedDescription) throw DatabaseError.saveError(reason: errMsg) } catch let error as CryptoError { - Logger.mainLog.error("Crypto error [reason: \(error.localizedDescription)]") + Logger.mainLog.error("Crypto error", metadata: ["reason": "\(error.localizedDescription)"]) let errMsg = String.localizedStringWithFormat( NSLocalizedString( "[Database2/Saving/Error] Encryption error: %@", @@ -663,7 +663,7 @@ public class Database2: Database { var blockStart: Int = 0 var blockIndex: UInt64 = 0 - Logger.mainLog.trace("\(data.count) bytes to write") + Logger.mainLog.trace("bytes to write", metadata: ["count": "\(data.count)"]) while blockStart != data.count { let blockSize = min(defaultBlockSize, data.count - blockStart) let blockData = data[blockStart.. Date { if (value == nil || value!.isEmpty) && fallbackToEpoch { - Logger.mainLog.warning("\(tag) is empty, will use 1970-01-01 instead") + Logger.mainLog.warning("tag is empty, will use 1970-01-01 instead", metadata: ["name": "\(tag)"]) return Date(timeIntervalSince1970: 0.0) } guard let time = timeParser.xmlStringToDate(value) else { - Logger.mainLog.error("Cannot parse \(tag) as Date") + Logger.mainLog.error("Cannot parse tag as Date", metadata: ["name": "\(tag)"]) throw Xml2.ParsingError.malformedValue( tag: tag, value: value) @@ -278,7 +278,7 @@ public class Group2: Group { fallbackToEpoch: true, timeParser: timeParser) default: - Logger.mainLog.error("Unexpected XML tag in Group/Times: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in Group/Times", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag(actual: tag.name, expected: "Group/Times/*") } } diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Header2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Header2.swift index 5d40c81..acbe09f 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Header2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Header2.swift @@ -1,6 +1,6 @@ import Foundation import CommonCrypto -import os.log +import Logging final class Header2: Eraseable { private static let signature1: UInt32 = 0x9AA2D903 @@ -301,11 +301,11 @@ final class Header2: Eraseable { if fileVersion == Header2.fileVersion4_1 { formatVersion = .v4_1 } - Logger.mainLog.trace("Database format: \(self.formatVersion)") + Logger.mainLog.trace("Database format", metadata: ["public:version": "\(self.formatVersion)"]) return } - Logger.mainLog.error("Unsupported file version [version: \(fileVersion.asHexString, privacy: .public)]") + Logger.mainLog.error("Unsupported file version", metadata: ["public:version": "\(fileVersion.asHexString)"]) throw HeaderError.unsupportedFileVersion(actualVersion: fileVersion.asHexString) } @@ -334,7 +334,7 @@ final class Header2: Eraseable { headerSize += MemoryLayout.size(ofValue: fSize) + fieldSize guard let fieldID: FieldID = FieldID(rawValue: rawFieldID) else { - Logger.mainLog.warning("Unknown field ID, skipping [fieldID: \(rawFieldID)]") + Logger.mainLog.warning("Unknown field ID, skipping", metadata: ["fieldID": "\(rawFieldID)"]) continue } @@ -350,10 +350,10 @@ final class Header2: Eraseable { switch fieldID { case .end: - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("end field read OK", metadata: ["name": "\(fieldID.name)"]) break case .comment: - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("comment read OK", metadata: ["name": "\(fieldID.name)"]) break case .cipherID: guard let _cipherUUID = UUID(data: fieldValueData) else { @@ -361,39 +361,39 @@ final class Header2: Eraseable { throw HeaderError.corruptedField(fieldName: fieldID.name) } guard let _dataCipher = DataCipherFactory.instance.createFor(uuid: _cipherUUID) else { - Logger.mainLog.error("Unsupported cipher ID: \(fieldValueData.asHexString, privacy: .public)") + Logger.mainLog.error("Unsupported cipher ID", metadata: ["public:value": "\(fieldValueData.asHexString)"]) throw HeaderError.unsupportedDataCipher( uuidHexString: fieldValueData.asHexString) } self.dataCipher = _dataCipher - Logger.mainLog.trace("\(fieldID.name) read OK [name: \(self.dataCipher.name)]") + Logger.mainLog.trace("cipherID read OK", metadata: ["name": "\(fieldID.name)", "cipher": "\(self.dataCipher.name)"]) case .compressionFlags: guard let compressionFlags32 = UInt32(data: fieldValueData) else { throw HeaderError.readingError } guard let compressionFlags8 = UInt8(exactly: compressionFlags32) else { - Logger.mainLog.error("Unknown compression algorithm [compressionFlags32: \(compressionFlags32, privacy: .public)]") + Logger.mainLog.error("Unknown compression algorithm", metadata: ["public:compressionFlags32": "\(compressionFlags32)"]) throw HeaderError.unknownCompressionAlgorithm } guard CompressionAlgorithm(rawValue: compressionFlags8) != nil else { - Logger.mainLog.error("Unknown compression algorithm [compressionFlags8: \(compressionFlags8, privacy: .public)]") + Logger.mainLog.error("Unknown compression algorithm", metadata: ["public:compressionFlags8": "\(compressionFlags8)"]) throw HeaderError.unknownCompressionAlgorithm } - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("compressionFlags read OK", metadata: ["name": "\(fieldID.name)"]) case .masterSeed: guard fieldSize == SHA256_SIZE else { - Logger.mainLog.error("Unexpected \(fieldID.name) field size [\(fieldSize) bytes]") + Logger.mainLog.error("Unexpected masterSeed size", metadata: ["name": "\(fieldID.name)", "bytes": "\(fieldSize)"]) throw HeaderError.corruptedField(fieldName: fieldID.name) } - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("masterSeed read OK", metadata: ["name": "\(fieldID.name)"]) case .encryptionIV: - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("encryptionIV read OK", metadata: ["name": "\(fieldID.name)"]) break case .kdfParameters: guard formatVersion >= .v4 else { - Logger.mainLog.error("Found \(fieldID.name) in non-V4 header. Database corrupted?") + Logger.mainLog.error("Found kdfParameters in non-V4 header. Database corrupted?", metadata: ["name": "\(fieldID.name)"]) throw HeaderError.corruptedField(fieldName: fieldID.name) } guard let kdfParams = KDFParams(data: fieldValueData) else { @@ -402,10 +402,10 @@ final class Header2: Eraseable { } self.kdfParams = kdfParams self.kdf = Argon2dKDF() - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("kdfParameters read OK", metadata: ["name": "\(fieldID.name)"]) case .publicCustomData: guard formatVersion >= .v4 else { - Logger.mainLog.error("Found \(fieldID.name) in non-V4 header. Database corrupted?") + Logger.mainLog.error("Found publicCustomData in non-V4 header. Database corrupted?", metadata: ["name": "\(fieldID.name)"]) throw HeaderError.corruptedField(fieldName: fieldID.name) } guard let publicCustomData = VarDict(data: fieldValueData) else { @@ -413,7 +413,7 @@ final class Header2: Eraseable { throw HeaderError.corruptedField(fieldName: fieldID.name) } self.publicCustomData = publicCustomData - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("publicCustomData read OK", metadata: ["name": "\(fieldID.name)"]) default: throw HeaderError.corruptedField(fieldName: fieldID.name) } @@ -435,18 +435,18 @@ final class Header2: Eraseable { [.cipherID, .compressionFlags, .masterSeed, .encryptionIV, .kdfParameters] for fieldID in importantFields { guard let fieldData = fields[fieldID] else { - Logger.mainLog.error("\(fieldID.name, privacy: .public) is missing") + Logger.mainLog.error("critical field is missing", metadata: ["public:name": "\(fieldID.name)"]) throw HeaderError.corruptedField(fieldName: fieldID.name) } if fieldData.isEmpty { - Logger.mainLog.error("\(fieldID.name, privacy: .public) is present, but empty") + Logger.mainLog.error("critical field is present, but empty", metadata: ["public:name": "\(fieldID.name)"]) throw HeaderError.corruptedField(fieldName: fieldID.name) } } Logger.mainLog.trace("All important fields are OK") guard initialVector.count == dataCipher.initialVectorSize else { - Logger.mainLog.error("Initial vector size is inappropritate for the cipher [size: \(self.initialVector.count, privacy: .public), cipher UUID: \(self.dataCipher.uuid, privacy: .public)]") + Logger.mainLog.error("Initial vector size is inappropriate for the cipher", metadata: ["public:size": "\(self.initialVector.count)", "public:UUID": "\(self.dataCipher.uuid)"]) throw HeaderError.corruptedField(fieldName: FieldID.encryptionIV.name) } } @@ -503,17 +503,17 @@ final class Header2: Eraseable { throw HeaderError.corruptedField(fieldName: fieldID.name) } guard let protectedStreamAlgorithm = ProtectedStreamAlgorithm(rawValue: rawID) else { - Logger.mainLog.error("Unrecognized protected stream algorithm [rawID: \(rawID, privacy: .public)]") + Logger.mainLog.error("Unrecognized protected stream algorithm", metadata: ["public:rawID": "\(rawID)"]) throw HeaderError.unsupportedStreamCipher(id: rawID) } self.innerStreamAlgorithm = protectedStreamAlgorithm - Logger.mainLog.trace("\(fieldID.name) read OK [name: \(self.innerStreamAlgorithm.name, privacy: .public)]") + Logger.mainLog.trace("innerRandomStreamID read OK", metadata: ["public:name": "\(fieldID.name)", "public:innerStream": "\(self.innerStreamAlgorithm.name)"]) case .innerRandomStreamKey: guard fieldData.count > 0 else { throw HeaderError.corruptedField(fieldName: fieldID.name) } self.protectedStreamKey = fieldData.clone() - Logger.mainLog.trace("\(fieldID.name) read OK") + Logger.mainLog.trace("innerRandomStreamKey read OK", metadata: ["name": "\(fieldID.name)"]) case .binary: let isProtected = (fieldData[0] & 0x01 != 0) let newBinaryID = database.binaries.count @@ -523,11 +523,11 @@ final class Header2: Eraseable { isCompressed: false, isProtected: isProtected) database.binaries[newBinaryID] = binary - Logger.mainLog.trace("\(fieldID.name, privacy: .public) read OK [size: \(fieldData.count) bytes]") + Logger.mainLog.trace("binary read OK", metadata: ["public:name": "\(fieldID.name)", "bytes": "\(fieldData.count)"]) case .end: initStreamCipher() Logger.mainLog.trace("Stream cipher init OK") - Logger.mainLog.trace("Inner header read OK [size: \(size) bytes]") + Logger.mainLog.trace("Inner header read OK", metadata: ["bytes": "\(size)"]) return size } } @@ -608,7 +608,7 @@ final class Header2: Eraseable { do { data = try binary.data.gunzipped() } catch { - Logger.mainLog.error("Failed to uncompress attachment data [message: \(error.localizedDescription, privacy: .public)]") + Logger.mainLog.error("Failed to uncompress attachment data", metadata: ["public:message": "\(error.localizedDescription)"]) throw HeaderError.binaryUncompressionError(reason: error.localizedDescription) } } else { diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/KeyHelper2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/KeyHelper2.swift index 3bd79cd..6d19ef2 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/KeyHelper2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/KeyHelper2.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging final class KeyHelper2: KeyHelper { diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Meta2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Meta2.swift index 685dc97..c396c61 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Meta2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Meta2.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging final class Meta2: Eraseable { public static let generatorName = "Kee Vault 2" @@ -40,7 +40,7 @@ final class Meta2: Eraseable { case Xml2.protectNotes: isProtectNotes = Bool(string: tag.value) default: - Logger.mainLog.error("Unexpected XML tag in Meta/MemoryProtection: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in Meta/MemoryProtection", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag( actual: tag.name, expected: "Meta/MemoryProtection/*") @@ -182,15 +182,15 @@ final class Meta2: Eraseable { switch tag.name { case Xml2.generator: self.generator = tag.value ?? "" - Logger.mainLog.info("Database was last edited by: \(self.generator, privacy: .public)") + Logger.mainLog.info("Database was last edited by", metadata: ["public:generator": "\(self.generator)"]) case Xml2.settingsChanged: guard formatVersion >= .v4 else { - Logger.mainLog.error("Found \(tag.name, privacy: .public) tag in non-V4 database") + Logger.mainLog.error("Found tag in non-V4 database", metadata: ["public:name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag(actual: tag.name, expected: nil) } self.settingsChangedTime = timeParser.xmlStringToDate(tag.value) ?? Date.now case Xml2.headerHash: - Logger.mainLog.warning("Found \(tag.name, privacy: .public) tag in non-V3 database. Ignoring") + Logger.mainLog.warning("Found tag in non-V3 database. Ignoring", metadata: ["public:name": "\(tag.name)"]) continue case Xml2.databaseName: self.databaseName = tag.value ?? "" @@ -223,7 +223,7 @@ final class Meta2: Eraseable { Logger.mainLog.trace("Memory protection loaded OK") case Xml2.customIcons: try loadCustomIcons(xml: tag, timeParser: timeParser) - Logger.mainLog.trace("Custom icons loaded OK [count: \(self.customIcons.count, privacy: .public)]") + Logger.mainLog.trace("Custom icons loaded OK", metadata: ["public:count": "\(self.customIcons.count)"]) case Xml2.recycleBinEnabled: self.isRecycleBinEnabled = Bool(string: tag.value) case Xml2.recycleBinUUID: @@ -245,7 +245,7 @@ final class Meta2: Eraseable { self.lastTopVisibleGroupUUID = UUID(base64Encoded: tag.value) ?? UUID.ZERO case Xml2.binaries: try loadBinaries(xml: tag, formatVersion: formatVersion, streamCipher: streamCipher) - Logger.mainLog.trace("Binaries loaded OK [count: \(self.database.binaries.count, privacy: .public)]") + Logger.mainLog.trace("Binaries loaded OK", metadata: ["public:count": "\(self.database.binaries.count)"]) case Xml2.customData: try customData.load( xml: tag, @@ -253,9 +253,9 @@ final class Meta2: Eraseable { timeParser: timeParser, xmlParentName: "Meta" ) - Logger.mainLog.trace("Custom data loaded OK [count: \(self.customData.count, privacy: .public)]") + Logger.mainLog.trace("Custom data loaded OK", metadata: ["public:count": "\(self.customData.count)"]) default: - Logger.mainLog.error("Unexpected XML tag in Meta: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in Meta", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag(actual: tag.name, expected: "Meta/*") } } @@ -272,7 +272,7 @@ final class Meta2: Eraseable { customIcons.append(icon) Logger.mainLog.trace("Custom icon loaded OK") default: - Logger.mainLog.error("Unexpected XML tag in Meta/CustomIcons: \(tag.name)") + Logger.mainLog.error("Unexpected XML tag in Meta/CustomIcons", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag( actual: tag.name, expected: "Meta/CustomIcons/*") @@ -287,7 +287,7 @@ final class Meta2: Eraseable { ) throws { assert(xml.name == Xml2.binaries) if let tag = xml.children.first { - Logger.mainLog.error("Unexpected XML content in V4 Meta/Binaries: \(tag.name)") + Logger.mainLog.error("Unexpected XML content in V4 Meta/Binaries", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag(actual: tag.name, expected: nil) } else { Logger.mainLog.warning("Found empty Meta/Binaries in a V4 database, ignoring.") diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/totp/TOTPGeneratorFactory.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/totp/TOTPGeneratorFactory.swift index 02373ab..268181c 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/totp/TOTPGeneratorFactory.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/totp/TOTPGeneratorFactory.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class TOTPGeneratorFactory { @@ -93,12 +93,12 @@ fileprivate class GAuthFormat: SingleFieldFormat { let seedData = base32DecodeToData(seedString), !seedData.isEmpty else { - Logger.mainLog.warning("OTP parameter cannot be parsed [parameter: \(seedParam)]") + Logger.mainLog.warning("OTP parameter cannot be parsed", metadata: ["parameter": "\(seedParam)"]) return nil } guard let timeStep = Int(params[timeStepParam] ?? "\(defaultTimeStep)") else { - Logger.mainLog.warning("OTP parameter cannot be parsed [parameter: \(timeStepParam)]") + Logger.mainLog.warning("OTP parameter cannot be parsed", metadata: ["parameter": "\(timeStepParam)"]) return nil } @@ -112,14 +112,14 @@ fileprivate class GAuthFormat: SingleFieldFormat { var algorithm: TOTPHashAlgorithm? if let algorithmString = params[algorithmParam] { guard let _algorithm = TOTPHashAlgorithm.fromString(algorithmString) else { - Logger.mainLog.warning("OTP algorithm is not supported [algorithm: \(algorithmString, privacy: .public)]") + Logger.mainLog.warning("OTP algorithm is not supported", metadata: ["public:algorithm": "\(algorithmString)"]) return nil } algorithm = _algorithm } guard let length = Int(params[lengthParam] ?? "\(defaultLength)") else { - Logger.mainLog.warning("OTP parameter cannot be parsed [parameter: \(lengthParam)]") + Logger.mainLog.warning("OTP parameter cannot be parsed", metadata: ["parameter": "\(lengthParam)"]) return nil } @@ -159,33 +159,33 @@ fileprivate class KeeOtpFormat: SingleFieldFormat { let seedData = base32DecodeToData(seedString), !seedData.isEmpty else { - Logger.mainLog.warning("OTP parameter cannot be parsed [parameter: \(seedParam)]") + Logger.mainLog.warning("OTP parameter cannot be parsed", metadata: ["parameter": "\(seedParam)"]) return nil } if let type = params[typeParam], type.caseInsensitiveCompare(supportedType) != .orderedSame { - Logger.mainLog.warning("OTP type is not suppoorted [type: \(type)]") + Logger.mainLog.warning("OTP type is not suppoorted", metadata: ["otptype": "\(type)"]) return nil } var algorithm: TOTPHashAlgorithm? if let algorithmString = params[algorithmParam] { guard let _algorithm = TOTPHashAlgorithm.fromString(algorithmString) else { - Logger.mainLog.warning("OTP algorithm is not supported [algorithm: \(algorithmString)]") + Logger.mainLog.warning("OTP algorithm is not supported", metadata: ["algorithm": "\(algorithmString)"]) return nil } algorithm = _algorithm } guard let timeStep = Int(params[timeStepParam] ?? "\(defaultTimeStep)") else { - Logger.mainLog.warning("OTP parameter cannot be parsed [parameter: \(timeStepParam)]") + Logger.mainLog.warning("OTP parameter cannot be parsed", metadata: ["parameter": "\(timeStepParam)"]) return nil } guard let length = Int(params[lengthParam] ?? "\(defaultLength)") else { - Logger.mainLog.warning("OTP parameter cannot be parsed [parameter: \(lengthParam)]") + Logger.mainLog.warning("OTP parameter cannot be parsed", metadata: ["parameter": "\(lengthParam)"]) return nil } @@ -211,9 +211,9 @@ fileprivate class SplitFieldFormat { let settingsString = settingsString ?? SplitFieldFormat.defaultSettingsValue let settings = settingsString.split(separator: ";") if settings.count > 2 { - Logger.mainLog.trace("Found redundant TOTP settings, ignoring [expected: 2, got: \(settings.count)]") + Logger.mainLog.trace("Found redundant TOTP settings, ignoring; expected 2", metadata: ["count": "\(settings.count)"]) } else if settings.count < 2 { - Logger.mainLog.warning("Insufficient TOTP settings number [expected: 2, got: \(settings.count)]") + Logger.mainLog.warning("Insufficient TOTP settings number; expected 2", metadata: ["count": "\(settings.count)"]) return nil } guard let timeStep = Int(settings[0]) else { @@ -221,7 +221,7 @@ fileprivate class SplitFieldFormat { return nil } guard timeStep > 0 else { - Logger.mainLog.warning("Invalid TOTP time step value: \(timeStep)") + Logger.mainLog.warning("Invalid TOTP time step value", metadata: ["value": "\(timeStep)"]) return nil } @@ -235,7 +235,7 @@ fileprivate class SplitFieldFormat { } else if settings[1] == TOTPGeneratorSteam.typeSymbol { return TOTPGeneratorSteam(seed: seed, timeStep: timeStep) } else { - Logger.mainLog.warning("Unexpected TOTP size or type: '\(settings[1])'") + Logger.mainLog.warning("Unexpected TOTP size or type", metadata: ["value": "\(settings[1])"]) return nil } } diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/util/ByteArray.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/util/ByteArray.swift index de33b24..f8e9423 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/util/ByteArray.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/util/ByteArray.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging public class ByteArray: Eraseable, Cloneable, Codable, CustomDebugStringConvertible { diff --git a/ios/KdbxSwift/Sources/KdbxSwift/util/Extensions.swift b/ios/KdbxSwift/Sources/KdbxSwift/util/Extensions.swift index 166ec06..9818a1c 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/util/Extensions.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/util/Extensions.swift @@ -1,5 +1,5 @@ import Foundation -import os.log +import Logging extension StringProtocol { func base64ToBase64url() -> String { diff --git a/ios/KdbxSwift/Sources/KdbxSwift/util/Logger.swift b/ios/KdbxSwift/Sources/KdbxSwift/util/Logger.swift index 6428612..f626d21 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/util/Logger.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/util/Logger.swift @@ -1,13 +1,22 @@ import Foundation -import os.log +import Logging extension Logger { private static var subsystem = Bundle.main.bundleIdentifier! - public static let mainLog = Logger(subsystem: subsystem, category: "main") + public static var mainLog = Logging.Logger(label: "com.keevault.kdbxswift") + public static var appLog = Logging.Logger(label: "com.keevault.keevault") public static func fatalError(_ message:String) -> Never { - mainLog.fault("\(message)") + //TODO: stack trace, metadata, etc.? + mainLog.critical("\(message)") Swift.fatalError(message) } + + public static func reloadConfig() { + mainLog = Logging.Logger(label: "com.keevault.kdbxswift") + appLog = Logging.Logger(label: "com.keevault.keevault") + mainLog.info("Log configuration refreshed") + } } + diff --git a/ios/KeeVaultAutofill/EntryListViewController.swift b/ios/KeeVaultAutofill/EntryListViewController.swift index 5163f59..d96afd9 100644 --- a/ios/KeeVaultAutofill/EntryListViewController.swift +++ b/ios/KeeVaultAutofill/EntryListViewController.swift @@ -1,4 +1,5 @@ import UIKit +import Logging protocol MyCellDelegate { func didTapEdit(data: KeeVaultAutofillEntry, category: PriorityCategory) @@ -42,6 +43,7 @@ class EntryListViewController: UITableViewController, UISearchBarDelegate, MyCel // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false + Logger.appLog.debug("entry list view controller loaded") } // MARK: - Table view data source @@ -150,6 +152,7 @@ class EntryListViewController: UITableViewController, UISearchBarDelegate, MyCel } func didTapEdit(data: KeeVaultAutofillEntry, category: PriorityCategory) { + Logger.appLog.debug("edit tapped") performSegue(withIdentifier: "editSegue", sender: [data, category]) } diff --git a/ios/KeeVaultAutofill/MyPuppyLogHandler.swift b/ios/KeeVaultAutofill/MyPuppyLogHandler.swift new file mode 100644 index 0000000..c36966f --- /dev/null +++ b/ios/KeeVaultAutofill/MyPuppyLogHandler.swift @@ -0,0 +1,122 @@ +import Foundation +import Logging +import Puppy + +public struct MyPuppyLogHandler: LogHandler, Sendable { + public var logLevel: Logger.Level = .info + public var metadata: Logger.Metadata + + public subscript(metadataKey key: String) -> Logger.Metadata.Value? { + get { + return metadata[key] + } + set(newValue) { + metadata[key] = newValue + } + } + + private let label: String + private let puppy: Puppy + + public init(label: String, puppy: Puppy, metadata: Logger.Metadata = [:]) { + self.label = label + self.puppy = puppy + self.metadata = metadata + } + + private func redact(_ metadata: Logger.Metadata) -> Logger.Metadata { + let arr = metadata.map { (key, value: Logger.MetadataValue) in + if (key.starts(with: "public:")) { + return (key, value) + } + else { + return (key, "REDACTED" as Logger.MetadataValue) + } + } + return Dictionary(uniqueKeysWithValues: arr) as Logger.Metadata + } + + public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, source: String, file: String, function: String, line: UInt) { + #if DEBUG + let merged = mergedMetadata(metadata) + #else + let merged = redact(mergedMetadata(metadata)) + #endif + let metadata = !merged.isEmpty ? "\(merged)" : "" + let swiftLogInfo = ["label": label, "source": source, "metadata": metadata] + puppy.logMessage(level.toPuppy(), message: "\(message)", tag: "swiftlog", function: function, file: file, line: line, swiftLogInfo: swiftLogInfo) + } + + private func mergedMetadata(_ metadata: Logger.Metadata?) -> Logger.Metadata { + var mergedMetadata: Logger.Metadata + if let metadata = metadata { + mergedMetadata = self.metadata.merging(metadata, uniquingKeysWith: { _, new in new }) + } else { + mergedMetadata = self.metadata + } + return mergedMetadata + } +} + +extension Logger.Level { + func toPuppy() -> LogLevel { + switch self { + case .trace: + return .trace + case .debug: + return .debug + case .info: + return .info + case .notice: + return .notice + case .warning: + return .warning + case .error: + return .error + case .critical: + return .critical + } + } +} + +struct LogFormatter: LogFormattable { + private let dateFormat = DateFormatter() + + init() { + dateFormat.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" + } + + func formatMessage(_ level: LogLevel, message: String, tag: String, function: String, + file: String, line: UInt, swiftLogInfo: [String : String], + label: String, date: Date, threadID: UInt64) -> String { + let date = dateFormatter(date, withFormatter: dateFormat) + let fileName = fileName(file) + let moduleName = moduleName(file) + let metaData = swiftLogInfo["metadata"] ?? "" + let source = swiftLogInfo["source"] ?? "" + return "\(date) [\(level.short)] \(message) \(metaData) * \(source) \(threadID) \(moduleName)/\(fileName):\(line) \(function)" + } +} + +extension LogLevel { + public var short: String { + switch self { + case .trace: + return "T" + case .verbose: + return "V" + case.debug: + return "D" + case .info: + return "I" + case .notice: + return "N" + case .warning: + return "W" + case .error: + return "E" + case .critical: + return "C" + } + } +} diff --git a/ios/Podfile b/ios/Podfile index b7eccca..442623c 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -42,6 +42,15 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end +# post_install do |installer| +# installer.generated_projects.each do |project| +# project.targets.each do |target| +# target.build_configurations.each do |config| +# config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' +# end +# end +# end + post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6e3a972..fcef2cd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -58,6 +58,8 @@ PODS: - FlutterMacOS - permission_handler_apple (9.1.1): - Flutter + - rate_my_app (1.1.4): + - Flutter - SDWebImage (5.14.2): - SDWebImage/Core (= 5.14.2) - SDWebImage/Core (5.14.2) @@ -86,6 +88,7 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - rate_my_app (from `.symlinks/plugins/rate_my_app/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -125,6 +128,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + rate_my_app: + :path: ".symlinks/plugins/rate_my_app/ios" sensitive_clipboard: :path: ".symlinks/plugins/sensitive_clipboard/ios" share_plus: @@ -138,26 +143,27 @@ SPEC CHECKSUMS: argon2_ffi: 995b9260d81aa17f5d2a8a497f2d2763ba4c45f3 barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0 biometric_storage: 1400f1382af3a4cc2bf05340e13c3d8de873ceb9 - device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: ce3938a0df3cc1ef404671531facef740d03f920 + file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_file_dialog: 4c014a45b105709a27391e266c277d7e588e9299 flutter_inapp_purchase: 5c6a1ac3f11b11d0c8c0321c0c41c1f05805e4c8 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb - package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 + rate_my_app: e249ec0751c276811746ff89558c23dc0da39f0c SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 - share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028 + share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 SwiftProtobuf: b02b5075dcf60c9f5f403000b3b0c202a11b6ae1 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86 -PODFILE CHECKSUM: 1061c2007eaabd205b946f1b9687990689969f14 +PODFILE CHECKSUM: 0f9f002d1b656a9e3daaf89beca155cdf7f30114 COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d92d8dd..3be9900 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -16,6 +16,10 @@ 8B253A1628BE694600AA6762 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B253A1528BE694600AA6762 /* CredentialProviderViewController.swift */; }; 8B253A1928BE694600AA6762 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B253A1728BE694600AA6762 /* MainInterface.storyboard */; }; 8B253A1E28BE694600AA6762 /* KeeVaultAutofill.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8B253A1228BE694600AA6762 /* KeeVaultAutofill.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 8B2842E72AF7017A00C8D2D6 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B2842E62AF7017A00C8D2D6 /* Logger.swift */; }; + 8B2842EE2AF9172300C8D2D6 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 8B2842ED2AF9172300C8D2D6 /* Puppy */; }; + 8B2842F12AF9173200C8D2D6 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 8B2842F02AF9173200C8D2D6 /* Logging */; }; + 8B2842F32AF9503900C8D2D6 /* MyPuppyLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B2842F22AF9503900C8D2D6 /* MyPuppyLogHandler.swift */; }; 8B594C4528D76F38004B10F0 /* KeeVaultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B594C4428D76F38004B10F0 /* KeeVaultViewController.swift */; }; 8B594C4728D7AB8A004B10F0 /* NewEntryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B594C4628D7AB8A004B10F0 /* NewEntryViewController.swift */; }; 8B94D3AB2902BC9300667FDA /* KdbxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8B94D3AA2902BC9300667FDA /* KdbxSwift */; }; @@ -102,6 +106,8 @@ 8B253A1B28BE694600AA6762 /* KeeVaultAutofill.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KeeVaultAutofill.entitlements; sourceTree = ""; }; 8B253A2428BF6E0100AA6762 /* Debug-autofill.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "Debug-autofill.xcconfig"; path = "Flutter/Debug-autofill.xcconfig"; sourceTree = ""; }; 8B253A2528BF6E0100AA6762 /* Release-autofill.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "Release-autofill.xcconfig"; path = "Flutter/Release-autofill.xcconfig"; sourceTree = ""; }; + 8B2842E62AF7017A00C8D2D6 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 8B2842F22AF9503900C8D2D6 /* MyPuppyLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPuppyLogHandler.swift; sourceTree = ""; }; 8B594C4428D76F38004B10F0 /* KeeVaultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeeVaultViewController.swift; sourceTree = ""; }; 8B594C4628D7AB8A004B10F0 /* NewEntryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewEntryViewController.swift; sourceTree = ""; }; 8B94D3AD291CF93D00667FDA /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; @@ -132,8 +138,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 8B2842F12AF9173200C8D2D6 /* Logging in Frameworks */, 8B253A1328BE694600AA6762 /* AuthenticationServices.framework in Frameworks */, 8BB2225828F5B39C00693094 /* DomainParser in Frameworks */, + 8B2842EE2AF9172300C8D2D6 /* Puppy in Frameworks */, 8BB2225B28F6F27A00693094 /* Punnycode in Frameworks */, 8B94D3AB2902BC9300667FDA /* KdbxSwift in Frameworks */, ); @@ -163,6 +171,7 @@ 8B253A1428BE694600AA6762 /* KeeVaultAutofill */ = { isa = PBXGroup; children = ( + 8B2842E62AF7017A00C8D2D6 /* Logger.swift */, 8B253A1528BE694600AA6762 /* CredentialProviderViewController.swift */, 8B94D3AF291D4A0A00667FDA /* KeychainError.swift */, 8B94D3AD291CF93D00667FDA /* Extensions.swift */, @@ -177,6 +186,7 @@ 8B253A1B28BE694600AA6762 /* KeeVaultAutofill.entitlements */, 8BB2225228DB542900693094 /* KeeVaultKeychainEntry.swift */, 8BB2225428DC81F100693094 /* SpinnerViewController.swift */, + 8B2842F22AF9503900C8D2D6 /* MyPuppyLogHandler.swift */, ); path = KeeVaultAutofill; sourceTree = ""; @@ -288,6 +298,8 @@ 8BB2225728F5B39C00693094 /* DomainParser */, 8BB2225A28F6F27A00693094 /* Punnycode */, 8B94D3AA2902BC9300667FDA /* KdbxSwift */, + 8B2842ED2AF9172300C8D2D6 /* Puppy */, + 8B2842F02AF9173200C8D2D6 /* Logging */, ); productName = KeeVaultAutofill; productReference = 8B253A1228BE694600AA6762 /* KeeVaultAutofill.appex */; @@ -323,11 +335,11 @@ 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, + 8B253A2328BE694600AA6762 /* Embed App Extensions */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, FF23F4DC409BF04CA9EF8E1D /* [CP] Embed Pods Frameworks */, - 8B253A2328BE694600AA6762 /* Embed App Extensions */, ); buildRules = ( ); @@ -374,6 +386,8 @@ packageReferences = ( 8BB2225628F5B39C00693094 /* XCRemoteSwiftPackageReference "SwiftDomainParser" */, 8BB2225928F6F27A00693094 /* XCRemoteSwiftPackageReference "PunycodeSwift" */, + 8B2842EC2AF9172300C8D2D6 /* XCRemoteSwiftPackageReference "Puppy" */, + 8B2842EF2AF9173200C8D2D6 /* XCRemoteSwiftPackageReference "swift-log" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; @@ -575,9 +589,11 @@ 8B594C4728D7AB8A004B10F0 /* NewEntryViewController.swift in Sources */, 8BB2225328DB542900693094 /* KeeVaultKeychainEntry.swift in Sources */, 8B94D3B0291D4A0A00667FDA /* KeychainError.swift in Sources */, + 8B2842E72AF7017A00C8D2D6 /* Logger.swift in Sources */, 8BB2225528DC81F100693094 /* SpinnerViewController.swift in Sources */, 8B594C4528D76F38004B10F0 /* KeeVaultViewController.swift in Sources */, 8B94D3AE291CF93D00667FDA /* Extensions.swift in Sources */, + 8B2842F32AF9503900C8D2D6 /* MyPuppyLogHandler.swift in Sources */, 8B94D3B4291D4D2300667FDA /* PriorityCategory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1149,6 +1165,22 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 8B2842EC2AF9172300C8D2D6 /* XCRemoteSwiftPackageReference "Puppy" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sushichop/Puppy"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.7.0; + }; + }; + 8B2842EF2AF9173200C8D2D6 /* XCRemoteSwiftPackageReference "swift-log" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-log.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.5.3; + }; + }; 8BB2225628F5B39C00693094 /* XCRemoteSwiftPackageReference "SwiftDomainParser" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Dashlane/SwiftDomainParser.git"; @@ -1168,6 +1200,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 8B2842ED2AF9172300C8D2D6 /* Puppy */ = { + isa = XCSwiftPackageProductDependency; + package = 8B2842EC2AF9172300C8D2D6 /* XCRemoteSwiftPackageReference "Puppy" */; + productName = Puppy; + }; + 8B2842F02AF9173200C8D2D6 /* Logging */ = { + isa = XCSwiftPackageProductDependency; + package = 8B2842EF2AF9173200C8D2D6 /* XCRemoteSwiftPackageReference "swift-log" */; + productName = Logging; + }; 8B94D3AA2902BC9300667FDA /* KdbxSwift */ = { isa = XCSwiftPackageProductDependency; productName = KdbxSwift; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7cfb59c..05bba63 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,24 @@ "version" : "2.1.0" } }, + { + "identity" : "puppy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sushichop/Puppy", + "state" : { + "revision" : "b5af02a72a5a1f92a68e6eceee19cac804067ad9", + "version" : "0.7.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", + "version" : "1.5.3" + } + }, { "identity" : "swiftdomainparser", "kind" : "remoteSourceControl", diff --git a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7cfb59c..05bba63 100644 --- a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,24 @@ "version" : "2.1.0" } }, + { + "identity" : "puppy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sushichop/Puppy", + "state" : { + "revision" : "b5af02a72a5a1f92a68e6eceee19cac804067ad9", + "version" : "0.7.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", + "version" : "1.5.3" + } + }, { "identity" : "swiftdomainparser", "kind" : "remoteSourceControl", diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 09bd8e1..1f65988 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -64,6 +64,31 @@ import Flutter } defaults.set(userId, forKey: "userId") result(true) + case "setDebugEnabled": + guard let args = call.arguments as? Dictionary else { + result(FlutterError.init(code: "bad args", message: nil, details: nil)) + break + } + guard let debugEnabled = args["debugEnabled"] as? Bool else { + result(FlutterError.init(code: "missing debugEnabled argument", message: nil, details: nil)) + break + } + let groupName = Bundle.main.infoDictionary!["KeeVaultSharedDefaultGroupName"] as! String + guard let defaults = UserDefaults(suiteName: groupName) else { + result(FlutterError.init(code: "missing shared user defaults group", message: nil, details: nil)) + break + } + defaults.set(debugEnabled, forKey: "debugEnabled") + result(true) + case "getDebugEnabled": + let groupName = Bundle.main.infoDictionary!["KeeVaultSharedDefaultGroupName"] as! String + guard let defaults = UserDefaults(suiteName: groupName) else { + result(FlutterError.init(code: "missing shared user defaults group", message: nil, details: nil)) + break + } + let de = defaults.bool(forKey: "debugEnabled") + result(de) + default: result(FlutterMethodNotImplemented) } diff --git a/lib/credentials/quick_unlocker.dart b/lib/credentials/quick_unlocker.dart index 20afb4c..489d01b 100644 --- a/lib/credentials/quick_unlocker.dart +++ b/lib/credentials/quick_unlocker.dart @@ -61,7 +61,7 @@ class QuickUnlocker { // any existing user's ID and they used to default to emailHashed anyway, this will keep working. // Maybe one day we could/should rename the user parameter to complete the tidy-up. Future initialiseForUser(String user, bool force) async { - l.v('initialiseForUser $user'); + l.t('initialiseForUser $user'); if (!force && _currentCreds != null && _currentUser != null && _currentUser == user) { return QUStatus.credsAvailable; } @@ -132,7 +132,7 @@ class QuickUnlocker { } Future _read(BiometricStorageFile storage) async { - l.v('QU _read'); + l.t('QU _read'); try { final contents = await storage.read( promptInfo: PromptInfo( @@ -158,7 +158,7 @@ $stackTrace'''); // This is the only place we enable autofill for ios // maybe do it in more places? one day? Future _write(BiometricStorageFile storage, String contents) async { - l.v('QU _write'); + l.t('QU _write'); try { await storage.write(contents, promptInfo: PromptInfo( @@ -167,7 +167,7 @@ $stackTrace'''); title: S.current.rememberVaultPassword, description: S.current.biometricsStoreDescription(KeeVaultPlatform.isIOS ? 'Passcode' : 'PIN')))); if (KeeVaultPlatform.isIOS) { - l.v('setUserId = $_currentUser'); + l.t('setUserId = $_currentUser'); await _autoFillMethodChannel.invokeMethod('setUserId', { 'userId': _currentUser, }); @@ -218,7 +218,7 @@ $stackTrace'''); } Future saveQuickUnlockUserPassKey(String? userPassKey) async { - l.v('saveQuickUnlockUserPassKey start'); + l.t('saveQuickUnlockUserPassKey start'); if (!(Settings.getValue('biometrics-enabled') ?? true)) { l.d('Quick unlock disabled by user'); return; @@ -247,7 +247,7 @@ $stackTrace'''); } Future saveQuickUnlockFileCredentials(Credentials? creds, int expiryTime, String kdfCacheKey) async { - l.v('saveQuickUnlockFileCredentials start'); + l.t('saveQuickUnlockFileCredentials start'); if (!(Settings.getValue('biometrics-enabled') ?? true)) { l.d('Quick unlock disabled by user'); return; @@ -293,7 +293,7 @@ $stackTrace'''); } Future saveBothSecrets(String userPassKey, Credentials creds, int expiryTime, String kdfCacheKey) async { - l.v('saveBothSecrets start'); + l.t('saveBothSecrets start'); if (!(Settings.getValue('biometrics-enabled') ?? true)) { l.d('Quick unlock disabled by user'); return; diff --git a/lib/cubit/autofill_cubit.dart b/lib/cubit/autofill_cubit.dart index 32fdb74..1bc89be 100644 --- a/lib/cubit/autofill_cubit.dart +++ b/lib/cubit/autofill_cubit.dart @@ -40,7 +40,6 @@ class AutofillCubit extends Cubit { bool autofillRequested = await AutofillService().fillRequestedAutomatic; bool autofillForceInteractive = await AutofillService().fillRequestedInteractive; final androidMetadata = await AutofillService().autofillMetadata; - l.d('androidMetadata $androidMetadata'); bool saveRequested = androidMetadata?.saveInfo != null; if (!autofillRequested && !autofillForceInteractive && !saveRequested) { diff --git a/lib/kdbx_argon2_ffi.dart b/lib/kdbx_argon2_ffi.dart index 57b2858..9a66ad9 100644 --- a/lib/kdbx_argon2_ffi.dart +++ b/lib/kdbx_argon2_ffi.dart @@ -14,7 +14,7 @@ class FlutterArgon2 extends Argon2 { Future argon2Async(Argon2Arguments args) async { final started = Stopwatch()..start(); try { - l.v('Starting argon2'); + l.t('Starting argon2'); return await compute(FlutterArgon2._runArgon2, args); } finally { l.d('Finished argon2 in ${started.elapsedMilliseconds}ms'); diff --git a/lib/logging/logger.dart b/lib/logging/logger.dart index 94eb782..fb3176a 100644 --- a/lib/logging/logger.dart +++ b/lib/logging/logger.dart @@ -1,5 +1,4 @@ import 'package:logger/logger.dart'; -import 'package:logger_flutter/logger_flutter.dart'; import 'package:logging/logging.dart' as logging; class KeeVaultLogOutput extends LogOutput { @@ -27,17 +26,19 @@ final l = getLogger(); void recordLibraryLogs() { logging.Logger.root.level = logging.Level.FINEST; logging.Logger.root.onRecord.listen((record) { - final message = '${record.loggerName}: ${record.time}: ${record.message}'; + final message = '${record.loggerName}: ${record.message}'; if (record.level == logging.Level.SHOUT) { - l.wtf(message); + l.f(message); } else if (record.level == logging.Level.SEVERE) { l.e(message); } else if (record.level == logging.Level.WARNING) { l.w(message); } else if (record.level == logging.Level.CONFIG || record.level == logging.Level.INFO) { l.i(message); - } else { + } else if (record.level == logging.Level.FINE) { l.d(message); + } else { + l.t(message); } }); } diff --git a/lib/main.dart b/lib/main.dart index 4df8bb3..75eb856 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,7 +15,7 @@ import './extension_methods.dart'; import 'package:flutter_settings_screens/flutter_settings_screens.dart'; void main() async { - Logger.level = Level.verbose; + Logger.level = Level.trace; recordLibraryLogs(); l.i('Initialized logger'); @@ -41,14 +41,14 @@ void main() async { }, (dynamic error, StackTrace stackTrace) { if (error is KeeLoginFailedMITMException) { - l.wtf('MITM attack detected!', error, stackTrace); + l.f('MITM attack detected!', error: error, stackTrace: stackTrace); navigatorKey.currentState?.overlay?.context.let((context) { var message = 'Sign in failed because the response we received from the server indicates that it may be compromised. The most likely explanation is that someone near you or at your internet service provider is attempting to interfere with the secure connection and connect you to a malicious server (A Miscreant In The Middle attack). Find a different internet connection immediately, shut down the Kee Vault app and then try again. If it keeps happening, your local device may be compromised. The security of your Kee Vault remains intact so you need not panic. More information about the error is available at https://forum.kee.pm/'; try { message = S.of(context).serverMITMWarning; } catch (e, stackTrace) { - l.w('Error while localising error message', e, stackTrace); + l.w('Error while localising error message', error: e, stackTrace: stackTrace); } DialogUtils.showErrorDialog(context, null, message); MatomoTracker.instance.trackEvent(eventInfo: EventInfo(category: 'main', action: 'error', name: 'mitm')); @@ -57,13 +57,13 @@ void main() async { error.message.startsWith('Scaffold.geometryOf() must only be accessed during the paint phase.')) { l.w("Known Flutter bug ignored: 'Scaffold.geometryOf() must only be accessed during the paint phase.' This is known to be caused by tap interactions while the animations package is actively animating from an entrylistitem to an entry for editing. Other potential causes should be investigated if this is unlikely to have been the cause in this specific situation."); } else { - l.wtf('Unhandled error in app.', error, stackTrace); + l.f('Unhandled error in app.', error: error, stackTrace: stackTrace); navigatorKey.currentState?.overlay?.context.let((context) { var message = 'Unexpected error: $error'; try { message = S.of(context).unexpected_error('$error'); } catch (e, stackTrace) { - l.w('Error while localising error message', e, stackTrace); + l.w('Error while localising error message', error: e, stackTrace: stackTrace); } DialogUtils.showErrorDialog(context, null, '$message : $stackTrace'); MatomoTracker.instance.trackEvent(eventInfo: EventInfo(category: 'main', action: 'error', name: 'wtf')); diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 748c516..e90766a 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -268,7 +268,7 @@ class EntryViewModel { digits: data['size']?.toInt() ?? OtpAuth.DEFAULT_DIGITS, ).toUri().toString(); } on FormatException catch (e, stackTrace) { - l.d('Error parsing data while normalising OTP value', e, stackTrace); + l.d('Error parsing data while normalising OTP value', error: e, stackTrace: stackTrace); rethrow; } } @@ -283,10 +283,10 @@ class EntryViewModel { ).toUri().toString(); } on FormatException catch (e, stackTrace) { // ignore format exception from base32 decoding. - l.w('Error decoding base32 secret', e, stackTrace); + l.w('Error decoding base32 secret', error: e, stackTrace: stackTrace); return null; } catch (e, stackTrace) { - l.w('Error while parsing OTP format', e, stackTrace); + l.w('Error while parsing OTP format', error: e, stackTrace: stackTrace); throw FormatException('Error parsing Tray OTP Format $e'); } } diff --git a/lib/model/in_app_message.dart b/lib/model/in_app_message.dart index 8be450f..fe02db2 100644 --- a/lib/model/in_app_message.dart +++ b/lib/model/in_app_message.dart @@ -183,32 +183,32 @@ class InAppMessage { bool isSuppressed(AccountCubit accountCubit, bool autofillUnavailableOrEnabled, InteractionBasic interactionState) { if (suppressForRegisteredUser && accountCubit.currentUserIfKnown == null) { - l.v('Suppressed because user is signed in'); + l.t('Suppressed because user is signed in'); return true; } if (suppressWhenAutofillEnabled && autofillUnavailableOrEnabled) { - l.v('Suppressed because autofill is already enabled or unavailable'); + l.t('Suppressed because autofill is already enabled or unavailable'); return true; } final now = DateTime.now().toUtc(); if (suppressUntilTime.isAfter(now)) { - l.v('Suppressed because $suppressUntilTime is after now'); + l.t('Suppressed because $suppressUntilTime is after now'); return true; } if (interactionState.anyDatabaseSavedCount < suppressUntilDatabaseSavedCount) { - l.v('Suppressed because anyDatabaseSavedCount (${interactionState.anyDatabaseSavedCount}) is too low'); + l.t('Suppressed because anyDatabaseSavedCount (${interactionState.anyDatabaseSavedCount}) is too low'); return true; } if (interactionState.anyEntrySavedCount < suppressUntilEntriesSavedCount) { - l.v('Suppressed because anyEntrySavedCount (${interactionState.anyEntrySavedCount}) is too low'); + l.t('Suppressed because anyEntrySavedCount (${interactionState.anyEntrySavedCount}) is too low'); return true; } if (interactionState.installedBefore.add(suppressUntilDurationAfterInstall).isAfter(now)) { - l.v('Suppressed because installedBefore is too recent'); + l.t('Suppressed because installedBefore is too recent'); return true; } if (lastDisplayed.add(maximumRedisplayFrequency).isAfter(now)) { - l.v('Suppressed because lastDisplayed is too recent'); + l.t('Suppressed because lastDisplayed is too recent'); return true; } return false; diff --git a/lib/vault_backend/jwt.dart b/lib/vault_backend/jwt.dart index 09c7f14..e133989 100644 --- a/lib/vault_backend/jwt.dart +++ b/lib/vault_backend/jwt.dart @@ -96,7 +96,7 @@ class JWT { final signature = Signature(base64Url.decode(base64Url.normalize(sigParts[2]))); isValid = verifier.verify(Uint8List.fromList(data), signature); } catch (e, stacktrace) { - l.e('Cryptography error during JWT verification', e, stacktrace); + l.e('Cryptography error during JWT verification', error: e, stackTrace: stacktrace); throw KeeInvalidJWTException(); } diff --git a/lib/widgets/account_create.dart b/lib/widgets/account_create.dart index 8258fb6..3a113d1 100644 --- a/lib/widgets/account_create.dart +++ b/lib/widgets/account_create.dart @@ -131,7 +131,7 @@ class _AccountCreateWidgetState extends State { return PaymentService.instance.finishTransaction(item); } on Exception catch (e) { l.w('Exception while finishing the payment transaction. Ignoring but an app restart and/or a fresh sign-in or registration attempt may be required for everything to catch up. May also require a few minutes for server-side operations to complete.', - e); + error: e); } }); if (retry) { diff --git a/lib/widgets/binaries.dart b/lib/widgets/binaries.dart index bfb30ea..ca2d837 100644 --- a/lib/widgets/binaries.dart +++ b/lib/widgets/binaries.dart @@ -306,7 +306,7 @@ class BinaryCardWidget extends StatelessWidget { duration: Duration(seconds: 3), )); } on Exception catch (e, st) { - l.e('Export failed: $e', st); + l.e('Export failed', error: e, stackTrace: st); if (e is PlatformException) { if (e.code == 'read_external_storage_denied' && context.mounted) { alertUserToPermissionsProblem(context, 'export'); diff --git a/lib/widgets/entry.dart b/lib/widgets/entry.dart index b0b64c0..46e751b 100644 --- a/lib/widgets/entry.dart +++ b/lib/widgets/entry.dart @@ -173,7 +173,7 @@ class EntryWidget extends StatelessWidget { l.d('Got totp secret with ${value.lengthInBytes} bytes.'); return OtpAuth(secret: value); } catch (e, stackTrace) { - l.w('Invalid base32 code?', e, stackTrace); + l.w('Invalid base32 code?', error: e, stackTrace: stackTrace); return null; } } @@ -237,12 +237,14 @@ class EntryWidget extends StatelessWidget { if (e.code == barcode.BarcodeScanner.cameraAccessDenied) { // We already tried to get the user to grant permission so if they really don't want to // by this point, they'll have to enter the code manually. - l.i('User denied camera permission.. Automatically continuing to manual code entry.', e); + l.i('User denied camera permission.. Automatically continuing to manual code entry.', error: e); } else { - l.e('Unknown PlatformException. Automatically continuing to manual code entry.', e, stackTrace); + l.e('Unknown PlatformException. Automatically continuing to manual code entry.', + error: e, stackTrace: stackTrace); } } catch (e, stackTrace) { - l.w('Error during barcode scanning. Automatically continuing to manual code entry.', e, stackTrace); + l.w('Error during barcode scanning. Automatically continuing to manual code entry.', + error: e, stackTrace: stackTrace); } } diff --git a/lib/widgets/entry_field.dart b/lib/widgets/entry_field.dart index f4b7544..66bdbbb 100644 --- a/lib/widgets/entry_field.dart +++ b/lib/widgets/entry_field.dart @@ -548,7 +548,7 @@ class _OtpEntryFieldState extends _EntryTextFieldState { _errorMessage = null; }); } on FormatException catch (e, stackTrace) { - l.e('Error while decoding otpauth url.', e, stackTrace); + l.e('Error while decoding otpauth url.', error: e, stackTrace: stackTrace); setState(() { _currentOtp = ''; _errorMessage = 'Error generating token $e'; diff --git a/lib/widgets/import_export.dart b/lib/widgets/import_export.dart index 6e08c55..53da47e 100644 --- a/lib/widgets/import_export.dart +++ b/lib/widgets/import_export.dart @@ -345,7 +345,7 @@ class _ImportExportWidgetState extends State { duration: Duration(seconds: 3), )); } on Exception catch (e, st) { - l.e('Export failed: $e', st); + l.e('Export failed', error: e, stackTrace: st); if (e is PlatformException) { if (e.code == 'read_external_storage_denied' && context.mounted) { alertUserToPermissionsProblem(context, 'export'); @@ -401,7 +401,7 @@ class _ImportExportWidgetState extends State { duration: Duration(seconds: 3), )); } on Exception catch (e, st) { - l.e('Export failed: $e', st); + l.e('Export failed', error: e, stackTrace: st); if (e is PlatformException) { if (e.code == 'read_external_storage_denied' && context.mounted) { alertUserToPermissionsProblem(context, 'export'); diff --git a/lib/widgets/kee_vault_app.dart b/lib/widgets/kee_vault_app.dart index b53a419..09e0202 100644 --- a/lib/widgets/kee_vault_app.dart +++ b/lib/widgets/kee_vault_app.dart @@ -106,7 +106,7 @@ class KeeVaultAppState extends State with WidgetsBindingObserver, T @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); - l.v('App State: $state'); + l.t('App State: $state'); } StreamSubscription? _receiveIntentSubscription; diff --git a/lib/widgets/vault.dart b/lib/widgets/vault.dart index 847c75f..ebf1477 100644 --- a/lib/widgets/vault.dart +++ b/lib/widgets/vault.dart @@ -83,7 +83,7 @@ class _VaultWidgetState extends State with WidgetsBindingObserver { Future autofillMergeIfRequired({required bool onlyIfAttemptAlreadyDue}) async { if (KeeVaultPlatform.isIOS) { - l.v('checking if autofill merge required. $onlyIfAttemptAlreadyDue'); + l.t('checking if autofill merge required. $onlyIfAttemptAlreadyDue'); final user = BlocProvider.of(context).currentUserIfKnown; await BlocProvider.of(context).autofillMerge(user, onlyIfAttemptAlreadyDue: onlyIfAttemptAlreadyDue); } diff --git a/pubspec.lock b/pubspec.lock index 58a1583..b64f44d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,18 +13,18 @@ packages: dependency: "direct main" description: name: animations - sha256: fe8a6bdca435f718bb1dc8a11661b2c22504c6da40ef934cee8327ed77934164 + sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70 url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" archive: - dependency: transitive + dependency: "direct main" description: name: archive - sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" url: "https://pub.dev" source: hosted - version: "3.3.7" + version: "3.4.9" argon2_ffi: dependency: "direct main" description: @@ -144,10 +144,10 @@ packages: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: "direct main" description: @@ -192,26 +192,26 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" dependency_validator: dependency: "direct dev" description: name: dependency_validator - sha256: "08349175533ed0bd06eb9b6043cde66c45b2bfc7ebc222a7542cdb1324f1bf03" + sha256: f727a5627aa405965fab4aef4f468e50a9b632ba0737fd2f98c932fec6d712b9 url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.3" device_info_plus: - dependency: transitive + dependency: "direct main" description: name: device_info_plus - sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" + sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" url: "https://pub.dev" source: hosted - version: "9.0.3" + version: "9.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -224,10 +224,10 @@ packages: dependency: "direct main" description: name: dio - sha256: ce75a1b40947fea0a0e16ce73337122a86762e38b982e1ccb909daa3b9bc4197 + sha256: "417e2a6f9d83ab396ec38ff4ea5da6c254da71e4db765ad737a42af6930140b7" url: "https://pub.dev" source: hosted - version: "5.3.2" + version: "5.3.3" email_validator: dependency: "direct main" description: @@ -264,10 +264,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030 + sha256: "903dd4ba13eae7cef64acc480e91bf54c3ddd23b5b90b639c170f3911e489620" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "6.0.0" fixnum: dependency: transitive description: @@ -294,10 +294,10 @@ packages: description: path: "." ref: master - resolved-ref: "7d4e54bc430b22a87d4a30d8fb0f70d5ac243197" + resolved-ref: "18d34e9565085d369dd9fa204fa8eb30f9c324a5" url: "https://github.com/kee-org/flutter_autofill_service.git" source: git - version: "0.17.1" + version: "0.18.0" flutter_bloc: dependency: "direct main" description: @@ -343,10 +343,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" flutter_localizations: dependency: "direct main" description: flutter @@ -356,10 +356,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: ecff62b3b893f2f665de7e4ad3de89f738941fcfcaaba8ee601e749efafa4698 + sha256: d93394f22f73e810bda59e11ebe83329c5511d6460b6b7509c4e1f3c92d6d625 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.5" flutter_persistent_queue: dependency: "direct main" description: @@ -424,10 +424,10 @@ packages: dependency: "direct main" description: name: font_awesome_flutter - sha256: "5fb789145cae1f4c3245c58b3f8fb287d055c26323879eab57a7bf0cfd1e45f3" + sha256: "52671aea66da73b58d42ec6d0912b727a42248dd9a7c76d6c20f275783c48c08" url: "https://pub.dev" source: hosted - version: "10.5.0" + version: "10.6.0" glob: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: image - sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" url: "https://pub.dev" source: hosted - version: "4.0.17" + version: "4.1.3" intl: dependency: transitive description: @@ -521,10 +521,10 @@ packages: description: path: "." ref: master - resolved-ref: dfcc160f82d4d43bf6ed0c693ecd103dd60bc520 + resolved-ref: d10492914d8128b418c48edf73eb4be593304832 url: "https://github.com/kee-org/kdbx.dart.git" source: git - version: "0.5.18+0" + version: "0.5.20+0" lints: dependency: transitive description: @@ -546,19 +546,10 @@ packages: dependency: "direct main" description: name: logger - sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" url: "https://pub.dev" source: hosted - version: "1.4.0" - logger_flutter: - dependency: "direct main" - description: - path: "." - ref: master - resolved-ref: be1a63f9b4e01db60f621fd6e732a0b40d81bef8 - url: "https://github.com/kee-org/logger_flutter.git" - source: git - version: "1.1.0+6" + version: "2.0.2+1" logging: dependency: "direct main" description: @@ -595,10 +586,10 @@ packages: dependency: "direct main" description: name: matomo_tracker - sha256: "851d45c900a24f093cadf71a92c16e5d878701ecb0833fe435284e19023d187f" + sha256: de5c08e80b566f8d1f0edf47f5704fe855fbef52377acb2796d28c2a3e36ea41 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.1" meta: dependency: "direct main" description: @@ -643,10 +634,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -667,10 +658,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "909b84830485dbcd0308edf6f7368bc8fd76afa26a270420f34cabea2a6467a0" + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path_provider_android: dependency: transitive description: @@ -723,18 +714,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" + sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" url: "https://pub.dev" source: hosted - version: "10.4.3" + version: "11.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e url: "https://pub.dev" source: hosted - version: "10.3.3" + version: "11.1.0" permission_handler_apple: dependency: transitive description: @@ -747,10 +738,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" url: "https://pub.dev" source: hosted - version: "3.11.3" + version: "3.12.0" permission_handler_windows: dependency: transitive description: @@ -763,18 +754,18 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.1" platform: dependency: "direct main" description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.3" plugin_platform_interface: dependency: transitive description: @@ -875,26 +866,26 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11" + sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.2.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" + sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" shared_preferences_android: dependency: transitive description: @@ -989,14 +980,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - supercharged_dart: - dependency: transitive - description: - name: supercharged_dart - sha256: cb95edda32eacd27664089700a750120be41daa84aa6cd2aeded46227c16b867 - url: "https://pub.dev" - source: hosted - version: "2.1.1" synchronized: dependency: transitive description: @@ -1065,66 +1048,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba url: "https://pub.dev" source: hosted - version: "6.1.12" + version: "6.2.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025" + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" url: "https://pub.dev" source: hosted - version: "6.0.38" + version: "6.2.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.2.0" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.1.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.0.18" + version: "2.2.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.1.0" uuid: dependency: "direct main" description: @@ -1177,10 +1160,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.4.2" yaml: dependency: transitive description: @@ -1198,5 +1181,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 69fb0d8..b344901 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.4+53 +version: 1.2.0+54 environment: sdk: '>=3.0.0 <4.0.0' @@ -23,24 +23,20 @@ environment: dependencies: flutter: sdk: flutter - logger: 1.4.0 - logger_flutter: - git: - url: https://github.com/kee-org/logger_flutter.git - ref: master - logging: ^1.1.1 + logger: 2.0.2+1 + logging: ^1.2.0 kdbx: git: url: https://github.com/kee-org/kdbx.dart.git ref: master - path_provider: ^2.1.0 + path_provider: ^2.1.1 biometric_storage: git: url: https://github.com/kee-org/biometric_storage.git ref: keevault - package_info_plus: ^4.1.0 - shared_preferences: ^2.2.0 + package_info_plus: ^4.2.0 + shared_preferences: ^2.2.2 flutter_settings_screens: 0.3.3-null-safety+2 argon2_ffi: git: @@ -53,7 +49,7 @@ dependencies: bloc: ^8.1.2 flutter_bloc: ^8.1.3 fluro: ^2.0.5 - dio: ^5.3.2 + dio: ^5.3.3 crypto_keys: ^0.3.0+1 pointycastle: ^3.7.3 srp: @@ -61,10 +57,10 @@ dependencies: url: https://github.com/kee-org/srp.dart.git ref: master animate_icons: ^2.0.0 - animations: ^2.0.7 - font_awesome_flutter: ^10.5.0 + animations: ^2.0.8 + font_awesome_flutter: ^10.6.0 public_suffix: ^3.0.0 - url_launcher: ^6.1.12 + url_launcher: ^6.2.1 otp: ^3.1.4 tuple: ^2.0.2 collection: ^1.17.2 @@ -81,7 +77,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.5 + cupertino_icons: ^1.0.6 flutter_persistent_queue: git: @@ -90,14 +86,14 @@ dependencies: flutter_localizations: sdk: flutter - matomo_tracker: ^4.0.0 - file_picker: ^5.5.0 + matomo_tracker: ^4.1.1 + file_picker: ^6.0.0 zxcvbn: ^1.0.0 flutter_rating_bar: ^4.0.1 mime: ^1.0.4 - share_plus: ^7.1.0 + share_plus: ^7.2.1 path: 1.8.3 #flutter sdk always lags behind so upgrade this only when needed - permission_handler: ^10.4.3 + permission_handler: ^11.0.1 flutter_file_dialog: ^3.0.2 base32: ^2.1.3 barcode_scan2: @@ -106,36 +102,39 @@ dependencies: ref: main flutter_speed_dial: ^7.0.0 receive_intent: ^0.2.4 - platform: ^3.1.0 + platform: ^3.1.3 email_validator: ^2.1.17 convert: ^3.1.1 meta: ^1.9.1 argon2_ffi_base: ^1.1.1 uuid: ^3.0.7 clock: ^1.1.1 - flutter_inapp_purchase: ^5.4.2 + flutter_inapp_purchase: 5.4.2 sensitive_clipboard: ^1.0.0 rate_my_app: 1.1.4 # fix until at least > 2.0.0 + archive: ^3.4.6 + device_info_plus: ^9.1.0 dev_dependencies: flutter_test: sdk: flutter flutter_launcher_icons: "^0.13.1" - flutter_lints: ^2.0.2 - dependency_validator: ^3.2.2 - flutter_native_splash: ^2.3.2 + flutter_lints: ^2.0.3 + dependency_validator: ^3.2.3 + flutter_native_splash: ^2.3.5 #patrol: ^2.2.3 dependency_overrides: # flutter_inapp_purchase declares incompatibility with this version and other deps declare # incompatibility with earlier versions. Seems to work fine if we just use the latest version. http: 1.1.0 - # logger_flutter: - # path: ../logger_flutter/ + collection: 1.18.0 + # kdbx: + # path: ../kdbx.dart/ # biometric_storage: # path: ../biometric_storage/ - # flutter_chips_input: - # path: ../flutter_chips_input/ + # flutter_autofill_service: + # path: ../flutter_autofill_service/ # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From f649c1f249650020374621c658fec69da8a395de Mon Sep 17 00:00:00 2001 From: luckyrat Date: Thu, 9 Nov 2023 18:40:30 +0000 Subject: [PATCH 2/3] Upgrade log console with: Share feature (instead of just copy) Delete feature to remove all application logs Various UI improvements Feature to enable logging to a file from Autofill service/extension --- .../CredentialProviderViewController.swift | 60 +- lib/config/route_handlers.dart | 5 + lib/config/routes.dart | 2 + lib/cubit/autofill_cubit.dart | 30 + lib/generated/intl/messages_en.dart | 2 +- lib/generated/l10n.dart | 4 +- lib/l10n/intl_en.arb | 2 +- lib/logging/kee_log_printer.dart | 91 +++ lib/logging/log_console.dart | 666 ++++++++++++++++++ lib/logging/logger.dart | 14 +- lib/logging/prettier_console_output.dart | 23 - lib/widgets/dialog_utils.dart | 9 +- lib/widgets/help.dart | 10 +- 13 files changed, 880 insertions(+), 38 deletions(-) create mode 100644 lib/logging/kee_log_printer.dart create mode 100644 lib/logging/log_console.dart delete mode 100644 lib/logging/prettier_console_output.dart diff --git a/ios/KeeVaultAutofill/CredentialProviderViewController.swift b/ios/KeeVaultAutofill/CredentialProviderViewController.swift index 418da2f..5b33540 100644 --- a/ios/KeeVaultAutofill/CredentialProviderViewController.swift +++ b/ios/KeeVaultAutofill/CredentialProviderViewController.swift @@ -4,6 +4,8 @@ import Punycode import Foundation import LocalAuthentication import KdbxSwift +import Logging +import Puppy class CredentialProviderViewController: ASCredentialProviderViewController { @@ -22,6 +24,11 @@ class CredentialProviderViewController: ASCredentialProviderViewController { var key: ByteArray? var keyStatus: OSStatus? let iOSBugWorkaroundAuthenticationDelay = 0.25 + var initialised = false + + var logFormat = LogFormatter() + var oslog: OSLogger? + var puppy: Puppy? override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, @@ -31,12 +38,56 @@ class CredentialProviderViewController: ASCredentialProviderViewController { } override func viewDidLoad() { + + // This appears to be the earliest point in the autofill lifecycle and it may be + // called on a re-used or newly instantiated instance of this view controller + if (!initialised) { + oslog = OSLogger("com.keevault.keevault.oslog", logFormat: logFormat) + puppy = Puppy() + LoggingSystem.bootstrap { + var handler = MyPuppyLogHandler(label: $0, puppy: self.puppy!) + handler.logLevel = .trace + return handler + } + initialised = true + } + mainController.selectionDelegate = self mainController.domainParser = self.domainParser sharedGroupName = Bundle.main.infoDictionary!["KeeVaultSharedDefaultGroupName"] as? String sharedDefaults = UserDefaults(suiteName: sharedGroupName) mainController.sharedDefaults = sharedDefaults + + //TODO:f: maybe observe user change in case that helps with sign-out / registration workflow? + //sharedDefaults?.addObserver(<#T##observer: NSObject##NSObject#>, forKeyPath: <#T##String#>, context: <#T##UnsafeMutableRawPointer?#>) + // UserDefaults.standard.addObserver(self, forKeyPath: "myKey", options: NSKeyValueObservingOptions.new, context: nil) + userId = getUserIdFromSharedSettings() + let debugEnabled = getDebugEnabledFromSharedSettings() + + // May be some more efficient way of inspecting current loggers? + puppy?.removeAll() + puppy!.add(oslog!) + var fileOutputEnabled = false + if (debugEnabled) { + let documentsDirectory = FileManager().containerURL(forSecurityApplicationGroupIdentifier: sharedGroupName!) + let fileURL = documentsDirectory!.appendingPathComponent("logs/autofill-log.txt").absoluteURL + let rotationConfig = RotationConfig(suffixExtension: .numbering, + maxFileSize: 3 * 1024 * 1024, + maxArchivedFilesCount: 10) + do { + let fileRotation = try FileRotationLogger("com.keevault.keevault.filerotation", + logFormat: logFormat, + fileURL: fileURL, + rotationConfig: rotationConfig) + puppy!.add(fileRotation) + fileOutputEnabled = true + } catch { + print("Failed to enable file logging") + } + } + Logger.reloadConfig() + Logger.appLog.info("Logger started", metadata: ["public:debugEnabled": "\(debugEnabled)", "public:fileOutputEnabled": "\(fileOutputEnabled)", "userId": "\(userId ?? "not set")"]) } /* @@ -53,9 +104,9 @@ class CredentialProviderViewController: ASCredentialProviderViewController { // Not all apps have a serviceIdentifier! DispatchQueue.main.asyncAfter(deadline: .now() + iOSBugWorkaroundAuthenticationDelay) { [self] in - (key, keyStatus) = getKeyForUser(userId: userId) if key == nil { + Logger.appLog.error("User key not found or expired", metadata: ["public:keyStatus": "\(keyStatus ?? 0)", "userId": "\(userId ?? "not set")"]) var message = "Your access key needs to be refreshed before you can AutoFill Kee Vault entries. Click OK and then sign in to the main Kee Vault app. You can then use AutoFill until your chosen expiry time is next reached." message += keyStatus != nil ? " Technical error code: \(String(describing: keyStatus))" : "" mainController.initWithAuthError(message: message) @@ -88,11 +139,12 @@ class CredentialProviderViewController: ASCredentialProviderViewController { seachDomains.append(si.identifier) } } - + Logger.appLog.debug("service identifiers parsed") let dbFileManager = DatabaseFileManager(status: Set(), preTransformedKeyMaterial: key!, userId: userId!, sharedGroupName: sharedGroupName!, sharedDefaults: sharedDefaults!) let dbFile = dbFileManager.loadFromFile() let db = dbFile.database var entries: [Entry] = [] + Logger.appLog.debug("preparing data for main VC") db.root?.collectAllEntries(to: &entries) mainController.searchDomains = seachDomains mainController.entries = entries @@ -105,6 +157,10 @@ class CredentialProviderViewController: ASCredentialProviderViewController { return sharedDefaults?.string(forKey: "userId") } + private func getDebugEnabledFromSharedSettings() -> Bool { + return sharedDefaults?.bool(forKey: "debugEnabled") ?? false + } + // userId will be nil if user has disabled biometrics (e.g. during integration testing) private func getKeyForUser(userId: String?) -> (ByteArray?, OSStatus?) { guard userId != nil else { return (nil,nil) } diff --git a/lib/config/route_handlers.dart b/lib/config/route_handlers.dart index bdccffb..b5be090 100644 --- a/lib/config/route_handlers.dart +++ b/lib/config/route_handlers.dart @@ -1,5 +1,6 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; +import 'package:keevault/logging/log_console.dart'; import 'package:keevault/widgets/account_create.dart'; import 'package:keevault/widgets/change_subscription.dart'; import 'package:keevault/widgets/help.dart'; @@ -41,6 +42,10 @@ var helpHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { + return LogConsole(); +}); + var changePasswordHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { return const ChangePasswordWidget(); }); diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 6595c46..a5cc8c6 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -11,6 +11,7 @@ class Routes { static String importExport = '/import_export'; static String settings = '/settings'; static String help = '/help'; + static String logger = '/logger'; static String changePassword = '/change_password'; static String changeEmailPrefs = '/change_email_prefs'; static String changeSubscription = '/change_subscription'; @@ -28,6 +29,7 @@ class Routes { router.define(settings, handler: settingsHandler); router.define(passwordPresetManager, handler: passwordPresetManagerHandler); router.define(help, handler: helpHandler); + router.define(logger, handler: loggerHandler); router.define(changePassword, handler: changePasswordHandler); router.define(changeEmailPrefs, handler: changeEmailPrefsHandler); router.define(changeSubscription, handler: changeSubscriptionHandler); diff --git a/lib/cubit/autofill_cubit.dart b/lib/cubit/autofill_cubit.dart index 1bc89be..670ecc2 100644 --- a/lib/cubit/autofill_cubit.dart +++ b/lib/cubit/autofill_cubit.dart @@ -89,6 +89,36 @@ class AutofillCubit extends Cubit { await AutofillService().setPreferences(AutofillPreferences( enableDebug: prefs.enableDebug, enableSaving: value, + enableIMERequests: prefs.enableIMERequests, + )); + } + + Future setDebugEnabledPreference(value) async { + if (KeeVaultPlatform.isIOS) { + final iosAutofillEnableDebug = + await _autoFillMethodChannel.invokeMethod('setDebugEnabled', { + 'debugEnabled': value, + }) ?? + false; + if (!iosAutofillEnableDebug) { + l.e('Failed to set debug status for autofill service'); + } + } else { + final prefs = await AutofillService().preferences; + await AutofillService().setPreferences(AutofillPreferences( + enableDebug: value, + enableSaving: prefs.enableSaving, + enableIMERequests: prefs.enableIMERequests, + )); + } + } + + Future setIMEIntegrationPreference(value) async { + final prefs = await AutofillService().preferences; + await AutofillService().setPreferences(AutofillPreferences( + enableDebug: prefs.enableDebug, + enableSaving: prefs.enableSaving, + enableIMERequests: value, )); } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 7ff182f..c45c5a6 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -437,7 +437,7 @@ class MessageLookup extends MessageLookupByLibrary { "openInBrowser": MessageLookupByLibrary.simpleMessage("Open in browser"), "openLogConsole": - MessageLookupByLibrary.simpleMessage("Open log console"), + MessageLookupByLibrary.simpleMessage("Share/view logs"), "openSettings": MessageLookupByLibrary.simpleMessage("Open settings"), "openUrl": MessageLookupByLibrary.simpleMessage("URL"), "openWebApp": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 9796dcd..c670a74 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1890,10 +1890,10 @@ class S { ); } - /// `Open log console` + /// `Share/view logs` String get openLogConsole { return Intl.message( - 'Open log console', + 'Share/view logs', name: 'openLogConsole', desc: '', args: [], diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 568a711..e12555d 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -183,7 +183,7 @@ "rememberVaultPassword": "Remember your Vault password?", "biometricsStoreDescription": "Access Kee Vault faster by protecting your password with biometrics or your device {PINname}", "hexadecimal": "Hexadecimal", - "openLogConsole": "Open log console", + "openLogConsole": "Share/view logs", "autofilling": "Autofilling", "permissionReasonAttachFile": "attach a file", "permissionReasonScanBarcodes": "quickly scan QR codes (barcodes)", diff --git a/lib/logging/kee_log_printer.dart b/lib/logging/kee_log_printer.dart new file mode 100644 index 0000000..12a5d80 --- /dev/null +++ b/lib/logging/kee_log_printer.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:logger/logger.dart'; + +/// Outputs simple log messages: +/// ``` +/// [E] Log message ERROR: Error info +/// ``` +class KeeLogPrinter extends LogPrinter { + static final levelPrefixes = { + Level.trace: '[T]', + Level.debug: '[D]', + Level.info: '[I]', + Level.warning: '[W]', + Level.error: '[E]', + Level.fatal: '[F]', + }; + + static final levelColors = { + Level.trace: AnsiColor.fg(AnsiColor.grey(0.5)), + Level.debug: const AnsiColor.none(), + Level.info: const AnsiColor.fg(12), + Level.warning: const AnsiColor.fg(208), + Level.error: const AnsiColor.fg(196), + Level.fatal: const AnsiColor.fg(199), + }; + + final bool printTime; + final bool colors; + final int stackTraceBeginIndex = 0; + + KeeLogPrinter({this.printTime = false, this.colors = true}); + + @override + List log(LogEvent event) { + var messageStr = _stringifyMessage(event.message); + var errorStr = event.error != null ? ' | ERROR: ${event.error}' : ''; + var timeStr = printTime ? event.time.toIso8601String() : ''; + var stackStr = event.stackTrace != null ? ' | STACK: \n${formatStackTrace(event.stackTrace, 100)}' : ''; + return ['${_labelFor(event.level)} $timeStr $messageStr$errorStr$stackStr']; + } + + String _labelFor(Level level) { + var prefix = levelPrefixes[level]!; + var color = levelColors[level]!; + + return colors ? color(prefix) : prefix; + } + + // Handles any object that is causing JsonEncoder() problems + Object toEncodableFallback(dynamic object) { + return object.toString(); + } + + String _stringifyMessage(dynamic message) { + final finalMessage = message is Function ? message() : message; + if (finalMessage is Map || finalMessage is Iterable) { + var encoder = JsonEncoder.withIndent(null, toEncodableFallback); + return encoder.convert(finalMessage); + } else { + return finalMessage.toString(); + } + } + + String? formatStackTrace(StackTrace? stackTrace, int? methodCount) { + List lines = stackTrace + .toString() + .split('\n') + .where( + (line) => line.isNotEmpty, + ) + .toList(); + List formatted = []; + + int stackTraceLength = (methodCount != null ? min(lines.length, methodCount) : lines.length); + for (int count = 0; count < stackTraceLength; count++) { + var line = lines[count]; + if (count < stackTraceBeginIndex) { + continue; + } + formatted.add('#$count ${line.replaceFirst(RegExp(r'#\d+\s+'), '')}'); + } + + if (formatted.isEmpty) { + return null; + } else { + return formatted.join('\n'); + } + } +} diff --git a/lib/logging/log_console.dart b/lib/logging/log_console.dart new file mode 100644 index 0000000..d1c4f7d --- /dev/null +++ b/lib/logging/log_console.dart @@ -0,0 +1,666 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:io'; +import 'package:archive/archive_io.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_settings_screens/flutter_settings_screens.dart'; +import 'package:logger/logger.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:path/path.dart' as path; +import 'package:url_launcher/link.dart'; + +import '../config/app.dart'; +import '../config/platform.dart'; +import '../cubit/autofill_cubit.dart'; +import 'logger.dart'; + +ListQueue _outputEventBuffer = ListQueue(); +int _bufferSize = 20; +bool _initialized = false; + +class LogConsole extends StatefulWidget { + LogConsole({super.key}) : assert(_initialized, 'Please call LogConsole.init() first.'); + + static void init({int bufferSize = 20}) { + if (_initialized) return; + + _bufferSize = bufferSize; + _initialized = true; + + Logger.addOutputListener((event) { + if (_outputEventBuffer.length == bufferSize) { + _outputEventBuffer.removeFirst(); + } + _outputEventBuffer.add(event); + }); + } + + @override + LogConsoleState createState() => LogConsoleState(); +} + +class RenderedEvent { + final int id; + final Level level; + final TextSpan span; + final String lowerCaseText; + + RenderedEvent(this.id, this.level, this.span, this.lowerCaseText); +} + +class LogConsoleState extends State { + late OutputCallback _callback; + + final ListQueue _renderedBuffer = ListQueue(); + List _filteredBuffer = []; + + final _scrollController = ScrollController(); + final _filterController = TextEditingController(); + bool _autofillDebugEnabled = false; + + Level _filterLevel = Level.debug; + double _logFontSize = 12; + + var _currentId = 0; + bool _scrollListenerEnabled = true; + + bool _followBottom = true; + bool sharing = false; + + @override + void initState() { + super.initState(); + + _callback = (e) { + if (_renderedBuffer.length == _bufferSize) { + _renderedBuffer.removeFirst(); + } + _renderedBuffer.add(_renderEvent(e)); + _refreshFilter(); + }; + + _autofillDebugEnabled = Settings.getValue('autofillServiceDebugEnabled') ?? false; + + Logger.addOutputListener(_callback); + + _scrollController.addListener(() { + if (!_scrollListenerEnabled) return; + var scrolledToBottom = _scrollController.offset >= _scrollController.position.maxScrollExtent; + setState(() { + _followBottom = scrolledToBottom; + }); + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + _renderedBuffer.clear(); + for (var event in _outputEventBuffer) { + _renderedBuffer.add(_renderEvent(event)); + } + _refreshFilter(); + } + + void _refreshFilter() { + var newFilteredBuffer = _renderedBuffer.where((it) { + var logLevelMatches = it.level.index >= _filterLevel.index; + if (!logLevelMatches) { + return false; + } else if (_filterController.text.isNotEmpty) { + var filterText = _filterController.text.toLowerCase(); + return it.lowerCaseText.contains(filterText); + } else { + return true; + } + }).toList(); + setState(() { + _filteredBuffer = newFilteredBuffer; + }); + + if (_followBottom) { + Future.delayed(Duration.zero, _scrollToBottom); + } + } + + @override + Widget build(BuildContext context) { + final dark = Theme.of(context).brightness == Brightness.dark; + return Scaffold( + key: widget.key, + appBar: AppBar(title: const Text('Application logs')), + body: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildTopBar(dark), + Expanded( + child: _buildLogContent(dark), + ), + _buildBottomBar(dark), + ], + ), + ), + floatingActionButton: AnimatedOpacity( + opacity: _followBottom ? 0 : 1, + duration: const Duration(milliseconds: 150), + child: Padding( + padding: const EdgeInsets.only(bottom: 60), + child: FloatingActionButton( + mini: true, + clipBehavior: Clip.antiAlias, + onPressed: _scrollToBottom, + child: Icon( + Icons.arrow_downward, + color: dark ? Colors.white : Colors.lightBlue[900], + ), + ), + ), + ), + ); + } + + Widget _buildLogContent(bool dark) { + return Container( + color: dark ? Colors.black : Colors.grey[150], + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: 1600, + child: ListView.builder( + shrinkWrap: true, + controller: _scrollController, + itemBuilder: (context, index) { + var logEntry = _filteredBuffer[index]; + return Text.rich( + logEntry.span, + key: Key(logEntry.id.toString()), + style: TextStyle(fontSize: _logFontSize), + ); + }, + itemCount: _filteredBuffer.length, + ), + ), + ), + ); + } + + Widget _buildTopBar(bool dark) { + return LogBar( + dark: dark, + child: Column( + children: [ + const Row(children: [ + Expanded( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'Sharing technical logs can help to fix problems. Share to yourself (Cloud storage/email) and then read the instructions that appear.'), + ), + ), + ]), + Row(children: [ + Expanded( + child: ListTile( + title: Text('Enable Autofill logging'), + subtitle: Text( + 'Keep this option disabled unless you are actively investigating a problem with Autofill into other apps/websites. The preview below never shows Autofill logs.'), + leading: Switch( + value: _autofillDebugEnabled, + onChanged: (bool? value) async { + if (value != null) { + final afc = BlocProvider.of(context); + await Settings.setValue('autofillServiceDebugEnabled', value); + await afc.setDebugEnabledPreference(value); + setState(() { + _autofillDebugEnabled = value; + }); + } + }, + ), + visualDensity: VisualDensity.compact, + titleAlignment: ListTileTitleAlignment.top, + isThreeLine: true, + onTap: () async { + final afc = BlocProvider.of(context); + await Settings.setValue('autofillServiceDebugEnabled', !_autofillDebugEnabled); + await afc.setDebugEnabledPreference(!_autofillDebugEnabled); + setState(() { + _autofillDebugEnabled = !_autofillDebugEnabled; + }); + }, + ), + ), + ]), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: OutlinedButton.icon( + icon: const Icon( + Icons.delete, + ), + label: const Text('Delete'), + onPressed: clearLogs, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: OutlinedButton.icon( + icon: sharing + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + strokeWidth: 3, + ), + ) + : const Icon(Icons.share), + label: const Text('Share'), + onPressed: sharing ? null : startShareLogs, + ), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.zoom_in), + onPressed: () { + setState(() { + _logFontSize++; + }); + }, + ), + IconButton( + icon: const Icon(Icons.zoom_out), + onPressed: () { + setState(() { + _logFontSize--; + }); + }, + ), + ], + ), + ], + ), + ); + } + + Future clearLogs() async { + //TODO:f: track async loading progress + await deleteAutofillLogs(); + _renderedBuffer.clear(); + _outputEventBuffer.clear(); + _refreshFilter(); + setState(() {}); + } + + void startShareLogs() async { + final box = context.findRenderObject() as RenderBox?; + + setState(() { + sharing = true; + }); + try { + final future1 = getAutofillLogs(); + final future2 = getDiagnosticInfo(); + + final encoder = ZipEncoder(); + final archive = Archive(); + + var content = _outputEventBuffer.map((e) => e.lines.join('\n')).join('\n'); + final logFile = ArchiveFile.string('log.txt', content); + archive.addFile(logFile); + + final diagnosticInfo = await future2; + final infoFile = ArchiveFile.string('diagnostics.txt', diagnosticInfo); + archive.addFile(infoFile); + + final autofillLogFiles = await future1; + for (var f in autofillLogFiles) { + archive.addFile(f); + } + + // encoder generates a uint8List so no idea why it returns as a list of ints + final encoded = encoder.encode(archive) as Uint8List; + + final tempDir = await getTemporaryDirectory(); + final file = File('${tempDir.path}/keevault_logs.zip'); + await file.create(recursive: true); + await file.writeAsBytes(encoded, flush: true); + try { + final xFile = XFile(file.path, mimeType: 'application/zip'); + final shareResult = await Share.shareXFiles( + [xFile], + subject: 'Kee Vault logs', + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + if (shareResult.status == ShareResultStatus.success) { + await showDialog( + barrierDismissible: false, + context: AppConfig.navigatorKey.currentContext!, + routeSettings: RouteSettings(name: '/dialog/alert/sharesuccess'), + builder: (context) { + return AlertDialog( + insetPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), + scrollable: true, + title: Text('Finished sharing to your chosen destination'), + content: getShareInstructions(), + actions: [ + TextButton( + child: Text('DONE'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }); + } + } finally { + await file.delete(); + } + } on Exception catch (e, st) { + l.e('Error preparing or sharing log files... oh the irony!', error: e, stackTrace: st); + } finally { + setState(() { + sharing = false; + }); + } + } + + Future deleteAutofillLogs() async { + final rootDir = await getStorageDirectory(); + final directory = Directory('${rootDir.path}/logs'); + if (!await directory.exists()) { + return; + } + final allFiles = directory.listSync(); + var fileList = allFiles.whereType().where((item) => item.path.contains('autofill-')).toList(growable: false); + + if (fileList.isEmpty) { + return; + } + await Future.wait([ + for (var file in fileList) file.delete(), + ]); + } + + Future> getAutofillLogs() async { + final rootDir = await getStorageDirectory(); + final directory = Directory('${rootDir.path}/logs'); + if (!await directory.exists()) { + return []; + } + final allFiles = directory.listSync(); + List archiveFiles = []; + var fileList = allFiles + .whereType() + .map((item) => item.path) + .where((item) => item.contains('autofill-')) + .toList(growable: false); + + if (fileList.isEmpty) { + return []; + } + var statResults = await Future.wait([ + for (var path in fileList) FileStat.stat(path), + ]); + + var dates = { + for (var i = 0; i < fileList.length; i += 1) fileList[i]: statResults[i].changed, + }; + + fileList.sort((a, b) => dates[a]!.compareTo(dates[b]!)); + + final files = fileList.map((e) => File(e)).take(5).toList(); + + for (var file in files) { + var filename = path.relative(file.path, from: directory.path); + final fileStream = InputFileStream(file.path); + final af = ArchiveFile.stream(filename, file.lengthSync(), fileStream); + af.lastModTime = dates[file.path]!.millisecondsSinceEpoch ~/ 1000; + archiveFiles.add(af); + } + return archiveFiles; + } + +//TODO:f: deduplicate + getStorageDirectory() async { + const autoFillMethodChannel = MethodChannel('com.keevault.keevault/autofill'); + if (KeeVaultPlatform.isIOS) { + final path = await autoFillMethodChannel.invokeMethod('getAppGroupDirectory'); + return Directory(path); + } + final directory = await getApplicationSupportDirectory(); + return directory; + } + + Widget _buildBottomBar(bool dark) { + return LogBar( + dark: dark, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: TextField( + style: const TextStyle(fontSize: 20), + controller: _filterController, + onChanged: (s) => _refreshFilter(), + decoration: const InputDecoration( + isDense: true, + labelText: 'Filter log output', + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(width: 20), + DropdownButton( + value: _filterLevel, + items: const [ + DropdownMenuItem( + value: Level.trace, + child: Text('TRACE'), + ), + DropdownMenuItem( + value: Level.debug, + child: Text('DEBUG'), + ), + DropdownMenuItem( + value: Level.info, + child: Text('INFO'), + ), + DropdownMenuItem( + value: Level.warning, + child: Text('WARNING'), + ), + DropdownMenuItem( + value: Level.error, + child: Text('ERROR'), + ), + DropdownMenuItem( + value: Level.fatal, + child: Text('FATAL'), + ) + ], + onChanged: (value) { + _filterLevel = value ?? Level.info; + _refreshFilter(); + }, + ) + ], + ), + ), + ); + } + + void _scrollToBottom() async { + _scrollListenerEnabled = false; + + setState(() { + _followBottom = true; + }); + + var scrollPosition = _scrollController.position; + await _scrollController.animateTo( + scrollPosition.maxScrollExtent, + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + ); + + _scrollListenerEnabled = true; + } + + RenderedEvent _renderEvent(OutputEvent event) { + var text = event.lines.join('\n'); + return RenderedEvent( + _currentId++, + event.level, + TextSpan(children: event.lines.map((line) => createSpan(line, event.level)).toList()), + text.toLowerCase(), + ); + } + + @override + void dispose() { + Logger.removeOutputListener(_callback); + super.dispose(); + } + + TextSpan createSpan(String text, Level level) { + return TextSpan( + text: text, + style: TextStyle( + color: colorForLevel(level), + ), + ); + } + + colorForLevel(Level level) { + final dark = Theme.of(context).brightness == Brightness.dark; + switch (level) { + case Level.fatal: + return dark ? Colors.pink[300] : Colors.pink[700]; + case Level.error: + return dark ? Colors.red[300] : Colors.red[700]; + case Level.warning: + return dark ? Colors.orange[300] : Colors.orange[700]; + case Level.info: + return dark ? Colors.blue[300] : Colors.blue[700]; + case Level.debug: + return dark ? Colors.green[300] : Colors.green[700]; + default: + return dark ? Colors.grey[300] : Colors.grey[700]; + } + } + + Future getDiagnosticInfo() async { + final packageInfo = await PackageInfo.fromPlatform(); + final deviceInfoPlugin = DeviceInfoPlugin(); + final deviceInfo = await deviceInfoPlugin.deviceInfo; + + return '''Diagnostic data: + +Platform info: +${packageInfo.data} + +Device info: +${deviceInfo.data} +'''; + } + + Widget getShareInstructions() { + final theme = Theme.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 16), + child: Text( + 'We need to be able to associate these logs with a description of the issue you are experiencing so please:'), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 0), + child: Link( + uri: Uri.parse('https://forum.kee.pm/'), + target: LinkTarget.blank, + builder: (context, followLink) { + return InkWell( + onTap: followLink, + child: Text( + '1) Log in to the community forum', + style: theme.textTheme.titleMedium!.copyWith( + color: theme.brightness == Brightness.light ? theme.primaryColor : Colors.white, + fontWeight: FontWeight.w800), + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + '2) Post to a relevant existing topic or start a new one describing why you are sharing these logs', + style: theme.textTheme.titleMedium!), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Link( + uri: Uri.parse('https://forum.kee.pm/new-message?username=luckyrat&title=Kee%20Vault%20logs'), + target: LinkTarget.blank, + builder: (context, followLink) { + return InkWell( + onTap: followLink, + child: Text( + '3) Send a new private message to luckyrat', + style: theme.textTheme.titleMedium!.copyWith( + color: theme.brightness == Brightness.light ? theme.primaryColor : Colors.white, + fontWeight: FontWeight.w800), + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 8.0, 0, 8.0), + child: Text( + 'a) Click on the Upload button in the message box toolbar, find the Zip file, upload it and then send the message', + style: theme.textTheme.bodyMedium), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0.0, 0, 8.0), + child: Text( + 'b) We will use your username to match the topic you were contributing to but feel free to provide more details, especially if you have multiple ongoing conversation topics', + style: theme.textTheme.bodyMedium), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + 'We have been extra careful to avoid including any personal information in the application logs but the nature of unexpected application problems is that occasionally unexpected things happen! If we do ever come across anything potentially sensitive we will delete it as soon as possible and notify you. We also encourage you to search through the contents of the files in the zip archive if you desire additional peace of mind before uploading the file.'), + ), + ], + ); + } +} + +class LogBar extends StatelessWidget { + final bool dark; + final Widget child; + + const LogBar({super.key, required this.dark, required this.child}); + + @override + Widget build(BuildContext context) { + return child; + } +} diff --git a/lib/logging/logger.dart b/lib/logging/logger.dart index fb3176a..6c66b90 100644 --- a/lib/logging/logger.dart +++ b/lib/logging/logger.dart @@ -1,24 +1,32 @@ import 'package:logger/logger.dart'; import 'package:logging/logging.dart' as logging; +import 'kee_log_printer.dart'; +import 'log_console.dart'; + class KeeVaultLogOutput extends LogOutput { @override void output(OutputEvent event) { + // Avoid printing to console in production (UI console / share feature can be used instead) assert(() { // ignore: avoid_print event.lines.forEach(print); return true; }()); - LogConsole.add(event, bufferSize: 1000); } } Logger getLogger() { - return Logger( - printer: SimplePrinter(printTime: false, colors: false), + // Maybe output to a file in future? Need to work out where to safely log it, + // and refactor log startup to be async and ensure writing from multiple + // isolates doesn't corrupt the log file or crash. + final lg = Logger( + printer: KeeLogPrinter(printTime: true, colors: false), output: KeeVaultLogOutput(), filter: ProductionFilter(), ); + LogConsole.init(bufferSize: 10000); + return lg; } final l = getLogger(); diff --git a/lib/logging/prettier_console_output.dart b/lib/logging/prettier_console_output.dart deleted file mode 100644 index 6c117c8..0000000 --- a/lib/logging/prettier_console_output.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:developer'; - -import 'package:logger/logger.dart'; -import 'package:logging/logging.dart' as dart_log; - -class PrettierConsoleOutput extends LogOutput { - final _lvlMapping = { - Level.nothing: dart_log.Level.ALL.value, - Level.verbose: dart_log.Level.FINE.value, - Level.debug: dart_log.Level.CONFIG.value, - Level.info: dart_log.Level.INFO.value, - Level.warning: dart_log.Level.WARNING.value, - Level.error: dart_log.Level.SEVERE.value, - Level.wtf: dart_log.Level.SHOUT.value, - }; - - @override - void output(OutputEvent event) { - for (var line in event.lines) { - log(line, level: _lvlMapping[event.level]!); - } - } -} diff --git a/lib/widgets/dialog_utils.dart b/lib/widgets/dialog_utils.dart index dfedea3..22d0ad1 100644 --- a/lib/widgets/dialog_utils.dart +++ b/lib/widgets/dialog_utils.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:keevault/logging/logger.dart'; -import 'package:logger_flutter/logger_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; import '../config/app.dart'; +import '../config/routes.dart'; import '../extension_methods.dart'; import 'package:keevault/generated/l10n.dart'; @@ -68,9 +68,10 @@ class DialogUtils { actions: [ TextButton( child: Text(S.of(context).openLogConsole), - onPressed: () async { - await LogConsole.open(context); - }, + onPressed: () async => await AppConfig.router.navigateTo( + AppConfig.navigatorKey.currentContext!, + Routes.logger, + ), ), TextButton( child: Text(materialLoc.okButtonLabel), diff --git a/lib/widgets/help.dart b/lib/widgets/help.dart index db32950..d340700 100644 --- a/lib/widgets/help.dart +++ b/lib/widgets/help.dart @@ -2,10 +2,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:keevault/widgets/bottom.dart'; -import 'package:logger_flutter/logger_flutter.dart'; import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import '../config/app.dart'; +import '../config/routes.dart'; import '../generated/l10n.dart'; import 'coloured_safe_area_widget.dart'; @@ -116,7 +117,12 @@ class _HelpWidgetState extends State with TraceableClientMixin { Padding( padding: const EdgeInsets.all(16.0), child: OutlinedButton( - onPressed: () async => await LogConsole.open(context), child: Text('Show log console')), + onPressed: () async => await AppConfig.router.navigateTo( + AppConfig.navigatorKey.currentContext!, + Routes.logger, + ), + child: Text('Share / view logs'), + ), ), ], ), From f9f85221df818ecd8f4744c4f36ec546fdc0775c Mon Sep 17 00:00:00 2001 From: luckyrat Date: Thu, 9 Nov 2023 18:41:15 +0000 Subject: [PATCH 3/3] Enable Keyboard Autofill for recent Android versions --- android/app/src/main/AndroidManifest.xml | 3 ++ lib/widgets/settings.dart | 36 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f3f6fb1..85bd7cf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -60,6 +60,9 @@ + { buildSignature: 'Unknown', ); + // ignore: unused_field + BaseDeviceInfo? _deviceInfo; + AndroidDeviceInfo? _androidDeviceInfo; + @override void initState() { super.initState(); unawaited(_initPackageInfo()); + unawaited(_initDeviceInfo()); } Future _initPackageInfo() async { @@ -459,6 +466,18 @@ class _AutofillStatusWidgetState extends State { } } + Future _initDeviceInfo() async { + final deviceInfoPlugin = DeviceInfoPlugin(); + final deviceInfo = await deviceInfoPlugin.deviceInfo; + final androidInfo = Platform.isAndroid ? await deviceInfoPlugin.androidInfo : null; + if (mounted) { + setState(() { + _deviceInfo = deviceInfo; + _androidDeviceInfo = androidInfo; + }); + } + } + @override Widget build(BuildContext context) { final str = S.of(context); @@ -513,6 +532,23 @@ class _AutofillStatusWidgetState extends State { }, ), ), + Visibility( + visible: widget.isEnabled && KeeVaultPlatform.isAndroid && (_androidDeviceInfo?.version.sdkInt ?? 0) >= 31, + child: SwitchSettingsTile( + settingKey: 'autofillServiceEnableIMEIntegration', + title: 'Show in keyboard', + subtitle: 'experimental feature', + defaultValue: true, + onChange: (value) async { + // We assume the autofill preference's SharedPreferences feature + // is in sync to begin with and always specify what the user has + // requested from our own preference so in the worst case, the + // user will have to toggle the switch a couple of times to + // resync and fix any broken behaviour. + await BlocProvider.of(context).setIMEIntegrationPreference(value); + }, + ), + ), ]), ), Divider(