diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 2cc18d8..f2da30e 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -5,7 +5,7 @@ on: [push] jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v1 @@ -35,9 +35,9 @@ jobs: fail-fast: false matrix: buildcommand: [linux] - os: [ubuntu-20.04] + os: [ubuntu-22.04] exclude: - - os: ubuntu-20.04 + - os: ubuntu-22.04 buildcommand: linux include: # No macos build for now, have to configure code signing.. @@ -61,10 +61,11 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' # or: 'dev' or 'beta' - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v3 if: matrix.buildcommand == 'appbundle' with: - java-version: '12.x' + java-version: '17' + distribution: 'oracle' - name: Patch for linux build if: matrix.buildcommand == 'linux' run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index c495508..0b57819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,31 @@ -## unreleased +## 5.0.1 + +* Add option for iOS/MacOS to allow non-biometric authentication (`darwinBiometricOnly`) #101 + * Improve [canAuthenticate] to differentiate between no available biometry and no available + user code. +* Bump dart sdk requirement to `3.2`. + +## 5.0.0+4 + +* Add topics to pubspec.yaml + +## 5.0.0+3 + +* Android: Upgrade AGP, fix building with AGP 8 +* Android: Depend on slf4j-api. + +## 5.0.0+1 + +* MacOS: fix building on MacOS + +## 5.0.0 * Allow overriding of `promptInfo` during `read`/`write` thanks @luckyrat * Android: (POTENTIALLY BREAKING): Completely removed deprecated old file backend based on `androidx.security`. This was deprecated since version 3.0.0 and users should have been migrated on every read or write. (this is only internally, does not change anything of the API). +* Update dependencies. ## 4.1.3 diff --git a/README.md b/README.md index ebd775b..d41886c 100644 --- a/README.md +++ b/README.md @@ -30,29 +30,33 @@ makes heavy use of this plugin. * `android/build.gradle`: `ext.kotlin_version = '1.4.31'` * MainActivity must extend FlutterFragmentActivity * Theme for the main activity must use `Theme.AppCompat` thme. - (Otherwise there will be crases on Android < 29) + (Otherwise there will be crashes on Android < 29) For example: - **AndroidManifest.xml**: + **android/app/src/main/AndroidManifest.xml**: ```xml + [...] + + ``` - **xml/styles.xml**: + **android/app/src/main/res/values/styles.xml**: ```xml - + + + + ``` ##### Resources @@ -65,7 +69,7 @@ makes heavy use of this plugin. https://developer.apple.com/documentation/localauthentication/logging_a_user_into_your_app_with_face_id_or_touch_id * include the NSFaceIDUsageDescription key in your app’s Info.plist file -* Requires at least iOS 9 +* Supports all iOS versions supported by Flutter. (ie. iOS 12) **Known Issue**: since iOS 15 the simulator seem to no longer support local authentication: https://developer.apple.com/forums/thread/685773 @@ -76,7 +80,7 @@ https://developer.apple.com/documentation/localauthentication/logging_a_user_int * enable keychain sharing and signing. (not sure why this is required. but without it You will probably see an error like: > SecurityError, Error while writing data: -34018: A required entitlement isn't present. -* Requires at least Mac OS 10.12 +* Supports all MacOS Versions supported by Flutter (ie. >= MacOS 10.14) ### Usage diff --git a/analysis_options.yaml b/analysis_options.yaml index 87c4286..0ba1255 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,12 +1,6 @@ -# Defines a default set of lint rules enforced for -# projects at Google. For details and rationale, -# see https://github.com/dart-lang/pedantic#enabled-lints. -include: package:lints/recommended.yaml +include: package:flutter_lints/flutter.yaml analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: false errors: # treat missing required parameters as a warning (not a hint) missing_required_param: warning @@ -17,159 +11,9 @@ analyzer: exclude: - lib/generated_plugin_registrant.dart - example/lib/generated_plugin_registrant.dart + language: + strict-casts: true + strict-raw-types: true linter: rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # http://dart-lang.github.io/linter/lints/ - - # HP mostly in sync with https://github.com/flutter/flutter/blob/master/analysis_options.yaml - - - always_declare_return_types - - always_put_control_body_on_new_line - # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - - always_require_non_null_named_parameters - #- always_specify_types - - annotate_overrides - # - avoid_annotating_with_dynamic # not yet tested - # - avoid_as - - avoid_bool_literals_in_conditional_expressions - # - avoid_catches_without_on_clauses # not yet tested - # - avoid_catching_errors # not yet tested - # - avoid_classes_with_only_static_members # not yet tested - # - avoid_double_and_int_checks # only useful when targeting JS runtime - - avoid_empty_else - - avoid_field_initializers_in_const_classes - - avoid_function_literals_in_foreach_calls - # - avoid_implementing_value_types # not yet tested - - avoid_init_to_null - # - avoid_js_rounded_ints # only useful when targeting JS runtime - - avoid_null_checks_in_equality_operators - # - avoid_positional_boolean_parameters # not yet tested - # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - # - avoid_returning_null # not yet tested - # - avoid_returning_null_for_future # not yet tested - - avoid_returning_null_for_void - # - avoid_returning_this # not yet tested - # - avoid_setters_without_getters # not yet tested - # - avoid_shadowing_type_parameters # not yet tested - # - avoid_single_cascade_in_expression_statements # not yet tested - - avoid_slow_async_io - - avoid_types_as_parameter_names - # - avoid_types_on_closure_parameters # not yet tested - - avoid_unused_constructor_parameters - - avoid_void_async - - await_only_futures - - camel_case_types - - cancel_subscriptions - # - cascade_invocations # not yet tested - # - close_sinks # not reliable enough - # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 - # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - - curly_braces_in_flow_control_structures - # - diagnostic_describe_all_properties # not yet tested - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - # - file_names # not yet tested - # - flutter_style_todos TODO(HP) - - hash_and_equals - - implementation_imports - # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 - - iterable_contains_unrelated_type - # - join_return_with_assignment # not yet tested - - library_names - - library_prefixes - # - lines_longer_than_80_chars # not yet tested - - list_remove_unrelated_type - # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 - - no_adjacent_strings_in_list - - no_duplicate_case_values - - non_constant_identifier_names - # - null_closures # not yet tested - # - omit_local_variable_types # opposite of always_specify_types - # - one_member_abstracts # too many false positives - # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - # - prefer_asserts_with_message # not yet tested - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - # - prefer_constructors_over_static_methods # not yet tested - - prefer_contains - # - prefer_double_quotes # opposite of prefer_single_quotes - - prefer_equal_for_default_values - # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - - prefer_final_fields - # - prefer_final_in_for_each # not yet tested - - prefer_final_locals - # - prefer_for_elements_to_map_fromIterable # not yet tested - - prefer_foreach - # - prefer_function_declarations_over_variables # not yet tested - - prefer_generic_function_type_aliases - # - prefer_if_elements_to_conditional_expressions # not yet tested - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - # - prefer_int_literals # not yet tested - # - prefer_interpolation_to_compose_strings # not yet tested - - prefer_is_empty - - prefer_is_not_empty - - prefer_iterable_whereType - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - prefer_void_to_null - # - provide_deprecation_message # not yet tested - # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml - - recursive_getters - - slash_for_doc_comments - # - sort_child_properties_last # not yet tested - - sort_constructors_first - #- sort_pub_dependencies - - sort_unnamed_constructors_first - - test_types_in_equals - - throw_in_finally - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures # https://github.com/flutter/flutter/issues/5793 - # - unnecessary_await_in_return # not yet tested - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498 - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_overrides - #- unnecessary_parenthesis HP: I like parenthesis :-) - - unnecessary_statements - - unnecessary_this - - unrelated_type_equality_checks - # - unsafe_html # not yet tested - - use_full_hex_values_for_flutter_colors - # - use_function_type_syntax_for_parameters # not yet tested - - use_rethrow_when_possible - # - use_setters_to_change_properties # not yet tested - # - use_string_buffers # https://github.com/dart-lang/linter/pull/664 - # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - valid_regexps - # - void_checks # not yet tested - diff --git a/android/build.gradle b/android/build.gradle index 6b5233c..08f50d5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,14 +2,14 @@ group 'design.codeux.biometric_storage' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.7.0' + ext.kotlin_version = '1.8.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' + classpath 'com.android.tools.build:gradle:8.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -25,8 +25,13 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +kotlin { + jvmToolchain(17) +} + android { - compileSdkVersion 31 + namespace "design.codeux.biometric_storage" + compileSdk 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -42,12 +47,12 @@ android { } dependencies { - def biometric_version = "1.2.0-alpha04" + def biometric_version = "1.2.0-alpha05" - api "androidx.core:core-ktx:1.8.0" - api "androidx.fragment:fragment-ktx:1.5.0" + api "androidx.core:core-ktx:1.10.1" + api "androidx.fragment:fragment-ktx:1.6.1" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.slf4j:slf4j-api:2.0.7" implementation "androidx.biometric:biometric:$biometric_version" - implementation "io.github.microutils:kotlin-logging:1.7.6" + implementation "io.github.oshai:kotlin-logging-jvm:5.0.1" } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 608cbe4..4f4e4ed 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStorageFile.kt b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStorageFile.kt index 1f15074..0ed6e6c 100644 --- a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStorageFile.kt +++ b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStorageFile.kt @@ -4,7 +4,7 @@ import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.security.keystore.KeyProperties -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File import java.io.IOException import javax.crypto.Cipher diff --git a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt index 7fc720b..da8d3c4 100644 --- a/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt +++ b/android/src/main/kotlin/design/codeux/biometric_storage/BiometricStoragePlugin.kt @@ -16,7 +16,7 @@ import io.flutter.embedding.engine.plugins.activity.* import io.flutter.plugin.common.* import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.PrintWriter import java.io.StringWriter import java.util.concurrent.ExecutorService @@ -47,6 +47,7 @@ enum class CanAuthenticateResponse(val code: Int) { ErrorNoBiometricEnrolled(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED), ErrorNoHardware(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE), ErrorStatusUnknown(BiometricManager.BIOMETRIC_STATUS_UNKNOWN), + ErrorPasscodeNotSet(-99), ; override fun toString(): String { @@ -160,7 +161,7 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { errorInfo.message.toString(), errorInfo.errorDetails ) - logger.error("AuthError: $errorInfo") + logger.error { "AuthError: $errorInfo" } } @@ -222,7 +223,7 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { } } - val options = call.argument>("options")?.let { it -> + val options = call.argument>("options")?.let { InitOptions( androidAuthenticationValidityDuration = (it["androidAuthenticationValidityDurationSeconds"] as Int?)?.seconds, authenticationRequired = it["authenticationRequired"] as Boolean, @@ -319,6 +320,12 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { } private fun canAuthenticate(): CanAuthenticateResponse { + val credentialsResponse = biometricManager.canAuthenticate(DEVICE_CREDENTIAL) + logger.debug { "canAuthenticate for DEVICE_CREDENTIAL: $credentialsResponse" } + if (credentialsResponse == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { + return CanAuthenticateResponse.Success + } + val response = biometricManager.canAuthenticate( BIOMETRIC_STRONG or BIOMETRIC_WEAK ) @@ -340,7 +347,7 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { @WorkerThread onSuccess: (cipher: Cipher?) -> Unit, onError: ErrorCallback ) { - logger.trace("authenticate()") + logger.trace {"authenticate()" } val activity = attachedActivity ?: return run { logger.error { "We are not attached to an activity." } onError( @@ -353,7 +360,7 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { val prompt = BiometricPrompt(activity, executor, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - logger.trace("onAuthenticationError($errorCode, $errString)") + logger.trace { "onAuthenticationError($errorCode, $errString)" } ui(onError) { onError( AuthenticationErrorInfo( @@ -367,12 +374,12 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler { @WorkerThread override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - logger.trace("onAuthenticationSucceeded($result)") + logger.trace { "onAuthenticationSucceeded($result)" } worker(onError) { onSuccess(result.cryptoObject?.cipher) } } override fun onAuthenticationFailed() { - logger.trace("onAuthenticationFailed()") + logger.trace { "onAuthenticationFailed()" } // this just means the user was not recognised, but the O/S will handle feedback so we don't have to } }) diff --git a/android/src/main/kotlin/design/codeux/biometric_storage/CryptographyManager.kt b/android/src/main/kotlin/design/codeux/biometric_storage/CryptographyManager.kt index d6acd63..2a3ff11 100644 --- a/android/src/main/kotlin/design/codeux/biometric_storage/CryptographyManager.kt +++ b/android/src/main/kotlin/design/codeux/biometric_storage/CryptographyManager.kt @@ -20,7 +20,7 @@ package design.codeux.biometric_storage import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File import java.nio.charset.Charset import java.security.KeyStore diff --git a/doc/screenshot_ios.png b/doc/screenshot_ios.png new file mode 100644 index 0000000..08fee0c Binary files /dev/null and b/doc/screenshot_ios.png differ diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index eb5242b..6b26b94 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -25,10 +25,19 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +kotlin { + jvmToolchain(17) +} + android { compileSdkVersion 33 ndkVersion "21.1.6352462" + compileOptions { + sourceCompatibility = 17 + targetCompatibility = 17 + } + sourceSets { main.java.srcDirs += 'src/main/kotlin' } @@ -62,6 +71,7 @@ android { lint { disable 'InvalidPackage' } + namespace 'design.codeux.biometric_storage_example' } flutter { @@ -69,11 +79,10 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'org.slf4j:slf4j-api:1.7.30' - implementation 'com.github.tony19:logback-android:2.0.0' - implementation "io.github.microutils:kotlin-logging:1.7.6" - implementation "androidx.appcompat:appcompat:1.4.1" + implementation 'org.slf4j:slf4j-api:2.0.7' + implementation 'com.github.tony19:logback-android:3.0.0' + implementation "io.github.oshai:kotlin-logging-jvm:5.0.1" + implementation "androidx.appcompat:appcompat:1.6.1" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.4.0' diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 9811082..f880684 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index f133692..deb061e 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/example/android/build.gradle b/example/android/build.gradle index 48a5fe9..e28edae 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.7.0' + ext.kotlin_version = '1.8.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:8.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index d172c47..19c4088 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,6 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 7a3602a..ba7c3aa 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf..8c6e561 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 12.0 diff --git a/example/ios/Flutter/Flutter.podspec b/example/ios/Flutter/Flutter.podspec index 663d5b2..98e1633 100644 --- a/example/ios/Flutter/Flutter.podspec +++ b/example/ios/Flutter/Flutter.podspec @@ -1,17 +1,17 @@ # -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# This is a generated file; do not edit or check into version control. +# This podspec is NOT to be published. It is only used as a local source! +# This is a generated file; do not edit or check into version control. # Pod::Spec.new do |s| s.name = 'Flutter' s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } + s.summary = 'A UI toolkit for beautiful and fast apps.' + s.homepage = 'https://flutter.dev' + s.license = { :type => 'BSD' } s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '9.0' + s.ios.deployment_target = '12.0' # Framework linking is handled by Flutter tooling, not CocoaPods. # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. s.vendored_frameworks = 'path/to/nothing' diff --git a/example/ios/Podfile b/example/ios/Podfile index 1e8c3c9..279576f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d85d057..e9c8ef2 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -15,8 +15,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: biometric_storage: 1400f1382af3a4cc2bf05340e13c3d8de873ceb9 - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.11.3 +COCOAPODS: 1.15.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index d14a571..1772d3e 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -163,7 +163,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -208,10 +208,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -244,6 +246,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -349,7 +352,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -432,7 +435,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -481,7 +484,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6..b52b2e6 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + diff --git a/example/macos/Podfile b/example/macos/Podfile index dade8df..049abe2 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.11' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index a2027d1..d8315c7 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -15,8 +15,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: biometric_storage: 43caa6e7ef00e8e19c074216e7e1786dacda9e76 - FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 -PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.11.2 +COCOAPODS: 1.15.0 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index bce5b8b..1141e0b 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -255,6 +255,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -404,7 +405,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -489,7 +490,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -536,7 +537,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index af91872..8574ee2 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =2.17.0 <3.0.0" + dart: ">=3.2.0 <4.0.0" flutter: ">=2.8.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b13d3e4..b9578db 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,24 +4,20 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.15.0-259.16.beta <3.0.0' + sdk: '>=3.2.0 <4.0.0' dependencies: biometric_storage: path: ../ flutter: sdk: flutter - logging: ^1.0.0 - logging_appenders: ^1.0.0 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 + logging: ^1.2.0 + logging_appenders: ^1.1.0 dev_dependencies: flutter_test: sdk: flutter - lints: ^2.0.0 + flutter_lints: ^3.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/lib/src/biometric_storage.dart b/lib/src/biometric_storage.dart index e554871..cfeeb18 100644 --- a/lib/src/biometric_storage.dart +++ b/lib/src/biometric_storage.dart @@ -18,6 +18,9 @@ enum CanAuthenticateResponse { errorNoBiometricEnrolled, errorNoHardware, + /// Passcode is not set (iOS/MacOS) or no user credentials (on macos). + errorPasscodeNotSet, + /// Used on android if the status is unknown. /// https://developer.android.com/reference/androidx/biometric/BiometricManager#BIOMETRIC_STATUS_UNKNOWN statusUnknown, @@ -31,6 +34,7 @@ const _canAuthenticateMapping = { 'ErrorHwUnavailable': CanAuthenticateResponse.errorHwUnavailable, 'ErrorNoBiometricEnrolled': CanAuthenticateResponse.errorNoBiometricEnrolled, 'ErrorNoHardware': CanAuthenticateResponse.errorNoHardware, + 'ErrorPasscodeNotSet': CanAuthenticateResponse.errorPasscodeNotSet, 'ErrorUnknown': CanAuthenticateResponse.unsupported, 'ErrorStatusUnknown': CanAuthenticateResponse.statusUnknown, }; @@ -86,6 +90,7 @@ class StorageFileInitOptions { this.authenticationValidityDurationSeconds = -1, this.authenticationRequired = true, this.androidBiometricOnly = true, + this.darwinBiometricOnly = true, }) : androidAuthenticationValidityDuration = androidAuthenticationValidityDuration ?? (authenticationValidityDurationSeconds <= 0 @@ -134,6 +139,11 @@ class StorageFileInitOptions { /// https://github.com/authpass/biometric_storage/issues/12#issuecomment-902508609 final bool androidBiometricOnly; + /// Only for iOS and macOS: + /// Uses `.biometryCurrentSet` if true, `.userPresence` otherwise. + /// https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/1392879-userpresence + final bool darwinBiometricOnly; + Map toJson() => { 'authenticationValidityDurationSeconds': authenticationValidityDurationSeconds, @@ -145,6 +155,7 @@ class StorageFileInitOptions { iosTouchIDAuthenticationForceReuseContextDuration?.inSeconds, 'authenticationRequired': authenticationRequired, 'androidBiometricOnly': androidBiometricOnly, + 'darwinBiometricOnly': darwinBiometricOnly, }; } diff --git a/lib/src/biometric_storage_win32.dart b/lib/src/biometric_storage_win32.dart index bc7ae24..84d807a 100644 --- a/lib/src/biometric_storage_win32.dart +++ b/lib/src/biometric_storage_win32.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:ffi'; -import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:logging/logging.dart'; @@ -103,7 +102,7 @@ class Win32BiometricStoragePlugin extends BiometricStorage { PromptInfo promptInfo, ) async { _logger.fine('write()'); - final examplePassword = utf8.encode(content) as Uint8List; + final examplePassword = utf8.encode(content); final blob = examplePassword.allocatePointer(); final namePointer = TEXT(name); final userNamePointer = TEXT('flutter.biometric_storage'); diff --git a/macos/Classes/BiometricStorageImpl.swift b/macos/Classes/BiometricStorageImpl.swift index a99a96a..80ad834 100644 --- a/macos/Classes/BiometricStorageImpl.swift +++ b/macos/Classes/BiometricStorageImpl.swift @@ -17,10 +17,12 @@ class InitOptions { iosTouchIDAuthenticationAllowableReuseDuration = params["iosTouchIDAuthenticationAllowableReuseDurationSeconds"] as? Int iosTouchIDAuthenticationForceReuseContextDuration = params["iosTouchIDAuthenticationForceReuseContextDurationSeconds"] as? Int authenticationRequired = params["authenticationRequired"] as? Bool + darwinBiometricOnly = params["darwinBiometricOnly"] as? Bool } let iosTouchIDAuthenticationAllowableReuseDuration: Int? let iosTouchIDAuthenticationForceReuseContextDuration: Int? let authenticationRequired: Bool! + let darwinBiometricOnly: Bool! } class IOSPromptInfo { @@ -138,7 +140,9 @@ class BiometricStorageImpl { case .touchIDNotAvailable: result("ErrorHwUnavailable") break; - case .passcodeNotSet: fallthrough + case .passcodeNotSet: + result("ErrorPasscodeNotSet") + break; case .touchIDNotEnrolled: result("ErrorNoBiometricEnrolled") break; @@ -203,7 +207,7 @@ class BiometricStorageFile { guard let access = accessControl(result) else { return nil } - if #available(iOS 13.0, *) { + if #available(iOS 13.0, macOS 10.15, *) { query[kSecUseDataProtectionKeychain as String] = true } query[kSecAttrAccessControl as String] = access @@ -214,10 +218,14 @@ class BiometricStorageFile { private func accessControl(_ result: @escaping StorageCallback) -> SecAccessControl? { let accessControlFlags: SecAccessControlCreateFlags - if #available(iOS 11.3, *) { - accessControlFlags = .biometryCurrentSet + if initOptions.darwinBiometricOnly { + if #available(iOS 11.3, *) { + accessControlFlags = .biometryCurrentSet + } else { + accessControlFlags = .touchIDCurrentSet + } } else { - accessControlFlags = .touchIDCurrentSet + accessControlFlags = .userPresence } // access = SecAccessControlCreateWithFlags(nil, diff --git a/pubspec.yaml b/pubspec.yaml index c4be32a..1e8f409 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,11 +2,11 @@ name: biometric_storage description: | Secure Storage: Encrypted data store optionally secured by biometric lock with support for iOS, Android, MacOS. Partial support for Linux, Windows and web (localStorage). -version: 4.1.3 +version: 5.0.1 homepage: https://github.com/authpass/biometric_storage/ environment: - sdk: '>=2.15.0 <3.0.0' + sdk: '>=3.2.0 <4.0.0' flutter: ">=2.8.0" dependencies: @@ -18,12 +18,12 @@ dependencies: plugin_platform_interface: ^2.0.0 ffi: '>=1.0.0 <3.0.0' - win32: '>=2.0.0 <4.0.0' + win32: '>=2.0.0 <6.0.0' dev_dependencies: flutter_test: sdk: flutter - lints: ^2.0.0 + flutter_lints: ^3.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -51,3 +51,13 @@ flutter: web: pluginClass: BiometricStoragePluginWeb fileName: src/biometric_storage_web.dart + +topics: + - biometrics + - encryption + - storage + - security + - secure-storage +screenshots: + - description: 'Face ID on iPhone' + path: doc/screenshot_ios.png diff --git a/test/biometric_storage_test.dart b/test/biometric_storage_test.dart index 52cb1d4..085e8d0 100644 --- a/test/biometric_storage_test.dart +++ b/test/biometric_storage_test.dart @@ -8,7 +8,8 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { if (methodCall.method == 'canAuthenticate') { return 'ErrorUnknown'; } @@ -17,7 +18,8 @@ void main() { }); tearDown(() { - channel.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); }); test('canAuthenticate', () async {