Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 5.1.1-dev.2

* Add flag `forceBiometricAuthentication` for `BiometricStorage.read`to enforce biometric prompt in any case. Only for iOS and android.


## 5.1.1-dev.1

* Improve `canAuthenticate` to include `InitOptions` to decide for which authenticaiton type to check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler {
const val PARAM_NAME = "name"
const val PARAM_WRITE_CONTENT = "content"
const val PARAM_ANDROID_PROMPT_INFO = "androidPromptInfo"

const val PARAM_FORCE_BIOMETRIC_AUTHENTICATION = "forceBiometricAuthentication"
}

private val executor: ExecutorService by lazy { Executors.newSingleThreadExecutor() }
Expand Down Expand Up @@ -167,7 +167,9 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler {
@UiThread
fun BiometricStorageFile.withAuth(
mode: CipherMode,
@WorkerThread cb: BiometricStorageFile.(cipher: Cipher?) -> Unit
forceBiometricAuthentication: Boolean = false,
@WorkerThread cb: BiometricStorageFile.(cipher: Cipher?) -> Unit,

) {
if (!options.authenticationRequired) {
return cb(null)
Expand All @@ -190,7 +192,8 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler {
cipherForMode()
}

if (cipher == null) {
// if forceBiometricAuthentication is true, show prompt in any case.
if (cipher == null && !forceBiometricAuthentication) {
// if we have no cipher, just try the callback and see if the
// user requires authentication.
try {
Expand Down Expand Up @@ -249,7 +252,9 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler {

"read" -> withStorage {
if (exists()) {
withAuth(CipherMode.Decrypt) {
withAuth(CipherMode.Decrypt,
forceBiometricAuthentication = requiredArgument<Boolean>(PARAM_FORCE_BIOMETRIC_AUTHENTICATION),
) {
val ret = readFile(
it,
)
Expand All @@ -269,7 +274,9 @@ class BiometricStoragePlugin : FlutterPlugin, ActivityAware, MethodCallHandler {
}

"write" -> withStorage {
withAuth(CipherMode.Encrypt) {
withAuth(CipherMode.Encrypt,
forceBiometricAuthentication = requiredArgument<Boolean>(PARAM_FORCE_BIOMETRIC_AUTHENTICATION),
) {
writeFile(it, requiredArgument(PARAM_WRITE_CONTENT))
ui(resultError) { result.success(true) }
}
Expand Down
41 changes: 39 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ class StorageActions extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
return Wrap(
children: <Widget>[
ElevatedButton(
child: const Text('read'),
Expand All @@ -277,6 +276,25 @@ class StorageActions extends StatelessWidget {
}
},
),
ElevatedButton(
child: const Text('read with force'),
onPressed: () async {
_logger.fine(
'reading with forceBiometricAuthentication from ${storageFile.name}');
try {
final result = await storageFile.read(
forceBiometricAuthentication: true,
);
_logger.fine('read: {$result}');
} on AuthException catch (e) {
if (e.code == AuthExceptionCode.userCanceled) {
_logger.info('User canceled.');
return;
}
rethrow;
}
},
),
ElevatedButton(
child: const Text('write'),
onPressed: () async {
Expand All @@ -294,6 +312,25 @@ class StorageActions extends StatelessWidget {
}
},
),
ElevatedButton(
child: const Text('write with forceBiometricAuthentication'),
onPressed: () async {
_logger.fine('Going to write with force...');
try {
await storageFile.write(
' [${DateTime.now()}] ${writeController.text}',
forceBiometricAuthentication: true,
);
_logger.info('Written content.');
} on AuthException catch (e) {
if (e.code == AuthExceptionCode.userCanceled) {
_logger.info('User canceled.');
return;
}
rethrow;
}
},
),
ElevatedButton(
child: const Text('delete'),
onPressed: () async {
Expand Down
61 changes: 49 additions & 12 deletions lib/src/biometric_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -272,24 +272,37 @@ abstract class BiometricStorage extends PlatformInterface {
PromptInfo promptInfo = PromptInfo.defaultValues,
});

/// Reads the content of a secure storage file identified by [name].
///
/// Returns the stored string content or null.
/// If [forceBiometricAuthentication] is set to true, show biometric prompt
/// even auth validity duration is not expired yet.
/// Only works on ios and android.
@protected
Future<String?> read(
String name,
PromptInfo promptInfo,
);
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
});

@protected
Future<bool?> delete(
String name,
PromptInfo promptInfo,
);

/// Write [content] for [name].
///
/// If [forceBiometricAuthentication] is set to true, show biometric prompt
/// even auth validity duration is not expired yet.
/// Only works on ios and android.
@protected
Future<void> write(
String name,
String content,
PromptInfo promptInfo,
);
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
});
}

class MethodChannelBiometricStorage extends BiometricStorage {
Expand Down Expand Up @@ -396,10 +409,12 @@ class MethodChannelBiometricStorage extends BiometricStorage {
@override
Future<String?> read(
String name,
PromptInfo promptInfo,
) =>
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
}) =>
_transformErrors(_channel.invokeMethod<String>('read', <String, dynamic>{
'name': name,
'forceBiometricAuthentication': forceBiometricAuthentication,
..._promptInfoForCurrentPlatform(promptInfo),
}));

Expand All @@ -417,11 +432,13 @@ class MethodChannelBiometricStorage extends BiometricStorage {
Future<void> write(
String name,
String content,
PromptInfo promptInfo,
) =>
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
}) =>
_transformErrors(_channel.invokeMethod('write', <String, dynamic>{
'name': name,
'content': content,
'forceBiometricAuthentication': forceBiometricAuthentication,
..._promptInfoForCurrentPlatform(promptInfo),
}));

Expand Down Expand Up @@ -491,12 +508,32 @@ class BiometricStorageFile {

/// read from the secure file and returns the content.
/// Will return `null` if file does not exist.
Future<String?> read({PromptInfo? promptInfo}) =>
_plugin.read(name, promptInfo ?? defaultPromptInfo);
///
/// If [forceBiometricAuthentication] is true, show biometric prompt in any case.
Future<String?> read({
PromptInfo? promptInfo,
bool forceBiometricAuthentication = false,
}) =>
_plugin.read(
name,
promptInfo ?? defaultPromptInfo,
forceBiometricAuthentication: forceBiometricAuthentication,
);

/// Write content of this file. Previous value will be overwritten.
Future<void> write(String content, {PromptInfo? promptInfo}) =>
_plugin.write(name, content, promptInfo ?? defaultPromptInfo);
///
/// If [forceBiometricAuthentication] is true, show biometric prompt in any case.
Future<void> write(
String content, {
PromptInfo? promptInfo,
bool forceBiometricAuthentication = false,
}) =>
_plugin.write(
name,
content,
promptInfo ?? defaultPromptInfo,
forceBiometricAuthentication: forceBiometricAuthentication,
);

/// Delete the content of this storage.
Future<void> delete({PromptInfo? promptInfo}) =>
Expand Down
10 changes: 6 additions & 4 deletions lib/src/biometric_storage_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,19 @@ class BiometricStoragePluginWeb extends BiometricStorage {
@override
Future<String?> read(
String name,
PromptInfo promptInfo,
) async {
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
}) async {
return web.window.localStorage.getItem(name);
}

@override
Future<void> write(
String name,
String content,
PromptInfo promptInfo,
) async {
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
}) async {
web.window.localStorage.setItem(name, content);
}
}
10 changes: 6 additions & 4 deletions lib/src/biometric_storage_win32.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ class Win32BiometricStoragePlugin extends BiometricStorage {
@override
Future<String?> read(
String name,
PromptInfo promptInfo,
) async {
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
}) async {
_logger.finer('read($name)');
final credPointer = calloc<Pointer<CREDENTIAL>>();
final namePointer = TEXT(name);
Expand Down Expand Up @@ -102,8 +103,9 @@ class Win32BiometricStoragePlugin extends BiometricStorage {
Future<void> write(
String name,
String content,
PromptInfo promptInfo,
) async {
PromptInfo promptInfo, {
bool forceBiometricAuthentication = false,
}) async {
_logger.fine('write()');
final examplePassword = utf8.encode(content);
final blob = examplePassword.allocatePointer();
Expand Down
Loading