From 1d10ad4cf2f3de8c12f3e9a26858ab1bdb8f9e11 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:41:28 +0200 Subject: [PATCH] 388-2-person-login (#395) * 2 person login * migrate app to gradles plugin dsl * 2 person login * migrate app to gradles plugin dsl * migrate app to gradles plugin dsl * migrate app to gradle plugin dsl * migrate app to gradles plugin dsl * Update flutter_build.yml * migrate app to gradles plugin dsl * 2 person login * 2 person login * removed debug prints * 2 person login * fixed some imports * 2 person login * 2 person login * 2 person login * Update test-integration.yaml * improved logging * removed wrong log * fixed minor issue * 2 person login * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml * Update test-integration.yaml --- .github/workflows/test-integration.yaml | 19 +- lib/l10n/app_cs.arb | 6 +- lib/l10n/app_de.arb | 6 +- lib/l10n/app_en.arb | 4 + lib/l10n/app_es.arb | 6 +- lib/l10n/app_fr.arb | 6 +- lib/l10n/app_localizations.dart | 6 + lib/l10n/app_localizations_cs.dart | 3 + lib/l10n/app_localizations_de.dart | 3 + lib/l10n/app_localizations_en.dart | 3 + lib/l10n/app_localizations_es.dart | 3 + lib/l10n/app_localizations_fr.dart | 3 + lib/l10n/app_localizations_nl.dart | 3 + lib/l10n/app_localizations_pl.dart | 3 + lib/l10n/app_nl.arb | 6 +- lib/l10n/app_pl.arb | 6 +- lib/mains/main_customizer.dart | 22 +- lib/model/extensions/sortable_list.dart | 15 - lib/model/push_request.dart | 20 +- lib/model/push_request.g.dart | 7 +- .../google_authenticator_qr_processor.dart | 4 +- .../aegis_import_file_processor.dart | 18 +- ...thenticator_pro_import_file_processor.dart | 22 +- .../free_otp_plus_import_file_processor.dart | 14 +- .../two_fas_import_file_processor.dart | 6 +- lib/repo/secure_token_repository.dart | 15 +- .../push_request_notifier.dart | 65 +-- lib/state_notifiers/token_notifier.dart | 10 +- lib/utils/crypto_utils.dart | 2 +- .../application_customization.dart | 2 +- .../customization/theme_customization.dart | 37 +- lib/utils/home_widget_utils.dart | 2 +- lib/utils/identifiers.dart | 1 + lib/utils/image_converter.dart | 3 +- lib/utils/logger.dart | 6 +- lib/utils/network_utils.dart | 12 +- lib/utils/push_provider.dart | 15 +- lib/utils/rsa_utils.dart | 12 +- lib/utils/utils.dart | 11 +- .../labeled_dropdown_button.dart | 2 +- lib/views/feedback_view/feedback_view.dart | 2 +- .../pages/import_start_page.dart | 12 +- lib/views/main_view/main_view.dart | 2 +- .../main_view_widgets/no_token_screen.dart | 4 +- .../actions/edit_push_token_action.dart | 4 +- .../rollout_failed_widget.dart | 4 +- .../qr_scanner_view/qr_scanner_view.dart | 8 +- .../dialogs/select_export_type_dialog.dart | 4 +- .../dialogs/select_tokens_dialog.dart | 4 +- .../settings_group_error_log.dart | 2 +- .../settings_group_general.dart | 8 +- .../settings_group_import_export_tokens.dart | 4 +- .../settings_group_language.dart | 80 ++-- .../settings_group_push_token.dart | 6 +- .../settings_groups/settings_group_theme.dart | 6 +- .../settings_view_widgets/logging_menu.dart | 2 +- .../update_firebase_token_dialog.dart | 8 +- lib/views/splash_screen/splash_screen.dart | 2 +- .../dialog_widgets/patch_notes_dialog.dart | 2 +- .../dialog_widgets/push_request_dialog.dart | 401 ++++++++++++------ .../dialog_widgets/two_step_dialog.dart | 8 +- lib/widgets/press_button.dart | 15 +- pubspec.yaml | 2 +- 63 files changed, 610 insertions(+), 389 deletions(-) diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml index f8dd73a3b..80161dd5d 100644 --- a/.github/workflows/test-integration.yaml +++ b/.github/workflows/test-integration.yaml @@ -10,41 +10,40 @@ jobs: timeout-minutes: 60 strategy: matrix: - api-level: [21, 30, 34] # [minSdk, most used, newest] + api-level: [26] # [ most used ] steps: - name: Delete unnecessary tools 🔧 uses: jlumbroso/free-disk-space@v1.3.1 with: android: false # Don't remove Android tools - tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY" + tool-cache: false # Don't remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY" dotnet: true # rm -rf /usr/share/dotnet haskell: true # rm -rf /opt/ghc... swap-storage: true # rm -f /mnt/swapfile (4GiB) docker-images: false # Takes 16s, enable if needed in the future large-packages: false # includes google-cloud-sdk and it's slow - + - name: Checkout uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 - with: distribution: 'zulu' java-version: 17 - - uses: subosito/flutter-action@v2 + - name: Setup Flutter + uses: subosito/flutter-action@v2 with: flutter-version: '3.22.0' channel: 'stable' - + - run: flutter pub get + # Run integration test - name: Run Integration Tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} arch: x86_64 - disable-animations: true - disk-size: 6000M - heap-size: 600M - script: flutter test integration_test --flavor netknights \ No newline at end of file + profile: Nexus 6 + script: flutter test integration_test --flavor netknights diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 5a6caa7d6..620cfb3f4 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -635,5 +635,9 @@ "scanThisQrWithNewDevice": "Naskenujte tento QR kód svým novým zařízením pro import žetonu.", "oneMore": "Ještě jeden", "done": "Hotovo", - "confirmPassword": "Potvrďte heslo" + "confirmPassword": "Potvrďte heslo", + "sendPushRequestResponseFailed": "Odpověď se nepodařilo odeslat.", + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + } } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9b32055d6..6d9010d09 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -614,5 +614,9 @@ "scanThisQrWithNewDevice": "Scannen Sie diesen QR-Code mit Ihrem neuen Gerät, um das Token zu importieren.", "oneMore": "Noch eins", "done": "Fertig", - "confirmPassword": "Passwort bestätigen" + "confirmPassword": "Passwort bestätigen", + "sendPushRequestResponseFailed": "Senden der Antwort fehlgeschlagen.", + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + } } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2937cd9e0..649e7d69b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -657,5 +657,9 @@ "example": "5" } } + }, + "sendPushRequestResponseFailed": "Failed to send the response.", + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." } } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 702a7a056..639a39492 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -631,5 +631,9 @@ "scanThisQrWithNewDevice": "Escanee este código QR con su nuevo dispositivo para importar el token.", "oneMore": "Uno más", "done": "Hecho", - "confirmPassword": "Confirmar contraseña" + "confirmPassword": "Confirmar contraseña", + "sendPushRequestResponseFailed": "No se ha podido enviar la respuesta.", + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + } } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index c6962a79d..8768c3253 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -636,5 +636,9 @@ "scanThisQrWithNewDevice": "Scannez ce code QR avec votre nouvel appareil pour importer le jeton.", "oneMore": "Encore un", "done": "Terminé", - "confirmPassword": "Confirmer le mot de passe" + "confirmPassword": "Confirmer le mot de passe", + "sendPushRequestResponseFailed": "Échec de l'envoi de la réponse.", + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + } } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index f608e8c83..3f4f45888 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1668,6 +1668,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'The {name} [{value}] is not supported by this version of the app.'** String unsupported(Object name, Object value); + + /// Error message when the response to a push request could not be sent. + /// + /// In en, this message translates to: + /// **'Failed to send the response.'** + String get sendPushRequestResponseFailed; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/app_localizations_cs.dart b/lib/l10n/app_localizations_cs.dart index f5ec56d65..301116cf4 100644 --- a/lib/l10n/app_localizations_cs.dart +++ b/lib/l10n/app_localizations_cs.dart @@ -875,4 +875,7 @@ class AppLocalizationsCs extends AppLocalizations { String unsupported(Object name, Object value) { return 'The $name [$value] is not supported by this version of the app.'; } + + @override + String get sendPushRequestResponseFailed => 'Odpověď se nepodařilo odeslat.'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 5109fb4d8..d41c7b09e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -875,4 +875,7 @@ class AppLocalizationsDe extends AppLocalizations { String unsupported(Object name, Object value) { return 'The $name [$value] is not supported by this version of the app.'; } + + @override + String get sendPushRequestResponseFailed => 'Senden der Antwort fehlgeschlagen.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index ca4f6163b..be05f09b7 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -875,4 +875,7 @@ class AppLocalizationsEn extends AppLocalizations { String unsupported(Object name, Object value) { return 'The $name [$value] is not supported by this version of the app.'; } + + @override + String get sendPushRequestResponseFailed => 'Failed to send the response.'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 046d5d766..9df760cb9 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -875,4 +875,7 @@ class AppLocalizationsEs extends AppLocalizations { String unsupported(Object name, Object value) { return 'The $name [$value] is not supported by this version of the app.'; } + + @override + String get sendPushRequestResponseFailed => 'No se ha podido enviar la respuesta.'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 0b7c478da..b40fa1a6a 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -875,4 +875,7 @@ class AppLocalizationsFr extends AppLocalizations { String unsupported(Object name, Object value) { return 'The $name [$value] is not supported by this version of the app.'; } + + @override + String get sendPushRequestResponseFailed => 'Échec de l\'envoi de la réponse.'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 372e68eb5..f238a1273 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -875,4 +875,7 @@ class AppLocalizationsNl extends AppLocalizations { String unsupported(Object name, Object value) { return 'The $name [$value] is not supported by this version of the app.'; } + + @override + String get sendPushRequestResponseFailed => 'Het verzenden van het antwoord is mislukt. '; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index e9d999ede..6ef9aabba 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -875,4 +875,7 @@ class AppLocalizationsPl extends AppLocalizations { String unsupported(Object name, Object value) { return 'The $name [$value] is not supported by this version of the app.'; } + + @override + String get sendPushRequestResponseFailed => 'Nie udało się wysłać odpowiedzi.'; } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index f335e4b29..8333b1480 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -632,5 +632,9 @@ "scanThisQrWithNewDevice": "Scan deze QR-code met uw nieuwe apparaat om de token te importeren.", "oneMore": "Nog een", "done": "Klaar", - "confirmPassword": "Wachtwoord bevestigen" + "confirmPassword": "Wachtwoord bevestigen", + "sendPushRequestResponseFailed": "Het verzenden van het antwoord is mislukt. ", + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + } } \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 04681aa25..5c9ead6bc 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -629,5 +629,9 @@ "scanThisQrWithNewDevice": "Zeskanuj ten kod QR za pomocą nowego urządzenia, aby zaimportować token.", "oneMore": "Jeszcze jeden", "done": "Gotowe", - "confirmPassword": "Potwierdź hasło" + "confirmPassword": "Potwierdź hasło", + "sendPushRequestResponseFailed": "Nie udało się wysłać odpowiedzi.", + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + } } \ No newline at end of file diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index f11979325..7e31c8ce6 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -22,19 +22,19 @@ import 'package:easy_dynamic_theme/easy_dynamic_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:privacyidea_authenticator/views/add_token_manually_view/add_token_manually_view.dart'; -import 'package:privacyidea_authenticator/views/license_view/license_view.dart'; -import 'package:privacyidea_authenticator/views/main_view/main_view.dart'; -import 'package:privacyidea_authenticator/views/qr_scanner_view/qr_scanner_view.dart'; -import 'package:privacyidea_authenticator/views/settings_view/settings_view.dart'; -import 'package:privacyidea_authenticator/views/splash_screen/splash_screen.dart'; -import 'package:privacyidea_authenticator/widgets/app_wrapper.dart'; +import '../l10n/app_localizations.dart'; import '../model/enums/app_feature.dart'; +import '../utils/globals.dart'; +import '../utils/logger.dart'; +import '../utils/riverpod_providers.dart'; +import '../views/add_token_manually_view/add_token_manually_view.dart'; +import '../views/license_view/license_view.dart'; +import '../views/main_view/main_view.dart'; +import '../views/qr_scanner_view/qr_scanner_view.dart'; +import '../views/settings_view/settings_view.dart'; +import '../views/splash_screen/splash_screen.dart'; +import '../widgets/app_wrapper.dart'; void main() async { Logger.init( diff --git a/lib/model/extensions/sortable_list.dart b/lib/model/extensions/sortable_list.dart index 1c32c896b..eee5374b5 100644 --- a/lib/model/extensions/sortable_list.dart +++ b/lib/model/extensions/sortable_list.dart @@ -4,11 +4,6 @@ extension SortableList on List { List get sorted { var list = List.from(this); list.sort((a, b) => a.compareTo(b)); - print('-----------------------------------'); - list.forEach((element) { - print('sorted: ${element.runtimeType} Sortindex: ${element.sortIndex}'); - }); - print('-----------------------------------'); return list; } @@ -25,11 +20,6 @@ extension SortableList on List { list[i] = list[i].copyWith(sortIndex: highestIndex) as T; } } - print('-----------------------------------'); - list.forEach((element) { - print('fillNullIndices: ${element.runtimeType} Sortindex: ${element.sortIndex}'); - }); - print('-----------------------------------'); return list; } @@ -47,11 +37,6 @@ extension SortableList on List { : list.length; list.insert(newIndex, movedItem); list = list.withCurrentSortIndexSet(); - print('-----------------------------------'); - list.forEach((element) { - print('moveBetween: ${element.runtimeType} Sortindex: ${element.sortIndex}'); - }); - print('-----------------------------------'); return list; } diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index 6c7fa0dee..4a711c5ce 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -24,6 +24,8 @@ class PushRequest { final String serial; final String signature; final bool? accepted; + final List? possibleAnswers; + final String? selectedAnswer; const PushRequest({ required this.title, @@ -36,6 +38,8 @@ class PushRequest { this.serial = '', this.signature = '', this.accepted, + this.possibleAnswers, + this.selectedAnswer, }); PushRequest copyWith({ @@ -49,6 +53,8 @@ class PushRequest { String? serial, String? signature, bool? accepted, + List Function()? answers, + String? Function()? selectedAnswer, }) { return PushRequest( title: title ?? this.title, @@ -61,6 +67,8 @@ class PushRequest { serial: serial ?? this.serial, signature: signature ?? this.signature, accepted: accepted ?? this.accepted, + possibleAnswers: answers != null ? answers() : this.possibleAnswers, + selectedAnswer: selectedAnswer != null ? selectedAnswer() : this.selectedAnswer, ); } @@ -75,7 +83,8 @@ class PushRequest { return 'PushRequest{title: $title, question: $question, ' 'id: $id, uri: $uri, _nonce: $nonce, sslVerify: $sslVerify, ' 'expirationDate: $expirationDate, serial: $serial, ' - 'signature: $signature, accepted: $accepted}'; + 'signature: $signature, accepted: $accepted, ' + 'answers: $possibleAnswers, selectedAnswer: $selectedAnswer}'; } factory PushRequest.fromJson(Map json) => _$PushRequestFromJson(json); @@ -98,6 +107,7 @@ class PushRequest { serial: data[PUSH_REQUEST_SERIAL], expirationDate: DateTime.now().add(const Duration(minutes: 2)), signature: data[PUSH_REQUEST_SIGNATURE], + possibleAnswers: data[PUSH_REQUEST_ANSWERS] != null ? (data[PUSH_REQUEST_ANSWERS] as String).split(',') : null, ); } @@ -127,16 +137,22 @@ class PushRequest { if (data[PUSH_REQUEST_SIGNATURE] is! String) { throw ArgumentError('Push request signature is ${data[PUSH_REQUEST_SIGNATURE].runtimeType}. Expected String.'); } + if (data[PUSH_REQUEST_ANSWERS] is! String?) { + throw ArgumentError('Push request answers is ${data[PUSH_REQUEST_ANSWERS].runtimeType}. Expected List or null.'); + } } Future verifySignature(PushToken token, {LegacyUtils legacyUtils = const LegacyUtils(), RsaUtils rsaUtils = const RsaUtils()}) async { + //5NV6KJCFCLNQURT2ZTBRHHGY6FDXOCOR|http://192.168.178.22:5000/ttype/push|PIPU0000E793|Pick a Number!|privacyIDEA|0|["A", "B", "C"] Logger.info('Adding push request to token', name: 'push_request_notifier.dart#newRequest'); String signedData = '$nonce|' '$uri|' '$serial|' '$question|' '$title|' - '${sslVerify ? '1' : '0'}'; + '${sslVerify ? '1' : '0'}' + '${possibleAnswers != null ? '|${possibleAnswers!.join(",")}' : ''}'; + Logger.warning('Signed data: $signedData', name: 'push_request_notifier.dart#newRequest'); // Re-add url and sslverify to android legacy tokens: if (token.url == null) { diff --git a/lib/model/push_request.g.dart b/lib/model/push_request.g.dart index 5d2b4d409..1d6977b67 100644 --- a/lib/model/push_request.g.dart +++ b/lib/model/push_request.g.dart @@ -17,10 +17,11 @@ PushRequest _$PushRequestFromJson(Map json) => PushRequest( serial: json['serial'] as String? ?? '', signature: json['signature'] as String? ?? '', accepted: json['accepted'] as bool?, + possibleAnswers: (json['answers'] as List?)?.map((e) => e as String).toList(), + selectedAnswer: (json['selectedAnswer'] as String?), ); -Map _$PushRequestToJson(PushRequest instance) => - { +Map _$PushRequestToJson(PushRequest instance) => { 'title': instance.title, 'question': instance.question, 'id': instance.id, @@ -31,4 +32,6 @@ Map _$PushRequestToJson(PushRequest instance) => 'serial': instance.serial, 'signature': instance.signature, 'accepted': instance.accepted, + 'answers': instance.possibleAnswers, + 'selectedAnswer': instance.selectedAnswer, }; diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart index b0ee10ba6..0ac271a78 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart @@ -5,8 +5,8 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:base32/base32.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../../model/extensions/enums/token_origin_source_type.dart'; +import '../../../utils/logger.dart'; import '../../../model/enums/token_origin_source_type.dart'; import '../../../model/processor_result.dart'; diff --git a/lib/processors/token_import_file_processor/aegis_import_file_processor.dart b/lib/processors/token_import_file_processor/aegis_import_file_processor.dart index f8a093ba5..44943e9b2 100644 --- a/lib/processors/token_import_file_processor/aegis_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/aegis_import_file_processor.dart @@ -8,20 +8,20 @@ import 'package:cryptography/cryptography.dart' as crypto; import 'package:encrypt/encrypt.dart'; import 'package:file_selector/file_selector.dart'; import 'package:pointycastle/export.dart'; -import 'package:privacyidea_authenticator/model/enums/encodings.dart'; -import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/enums/token_types.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/token_import_origins.dart'; import '../../l10n/app_localizations.dart'; +import '../../model/enums/encodings.dart'; +import '../../model/enums/token_origin_source_type.dart'; +import '../../model/enums/token_types.dart'; +import '../../model/extensions/enums/encodings_extension.dart'; +import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/processor_result.dart'; +import '../../model/tokens/token.dart'; import '../../utils/errors.dart'; import '../../utils/globals.dart'; +import '../../utils/identifiers.dart'; +import '../../utils/logger.dart'; +import '../../utils/token_import_origins.dart'; import '../../utils/utils.dart'; import 'token_import_file_processor_interface.dart'; import 'two_fas_import_file_processor.dart'; diff --git a/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart b/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart index 5ddbe17c8..92ed3bfde 100644 --- a/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart @@ -4,17 +4,17 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; -import 'package:privacyidea_authenticator/model/enums/encodings.dart'; -import 'package:privacyidea_authenticator/model/enums/token_types.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; -import 'package:privacyidea_authenticator/processors/token_import_file_processor/two_fas_import_file_processor.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/token_import_origins.dart'; +import '../../model/enums/algorithms.dart'; +import '../../model/enums/encodings.dart'; +import '../../model/enums/token_types.dart'; +import '../../model/extensions/enums/encodings_extension.dart'; +import '../../model/extensions/enums/token_origin_source_type.dart'; +import '../../model/tokens/token.dart'; +import '../../processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; +import '../../processors/token_import_file_processor/two_fas_import_file_processor.dart'; +import '../../utils/identifiers.dart'; +import '../../utils/logger.dart'; +import '../../utils/token_import_origins.dart'; import '../../l10n/app_localizations.dart'; import '../../model/encryption/aes_encrypted.dart'; diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index ef3c9a6ef..c99d786ad 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -4,16 +4,16 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/processor_result.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../l10n/app_localizations.dart'; +import '../../model/enums/token_origin_source_type.dart'; +import '../../model/extensions/enums/token_origin_source_type.dart'; +import '../../model/processor_result.dart'; +import '../../model/tokens/token.dart'; import '../../utils/errors.dart'; +import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; +import '../../utils/logger.dart'; import '../../utils/token_import_origins.dart'; import '../scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart'; import 'token_import_file_processor_interface.dart'; diff --git a/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart b/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart index 2b09f42f4..046fd69ac 100644 --- a/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart @@ -4,20 +4,20 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/utils/token_import_origins.dart'; import '../../l10n/app_localizations.dart'; import '../../model/encryption/aes_encrypted.dart'; import '../../model/enums/encodings.dart'; import '../../model/enums/token_origin_source_type.dart'; +import '../../model/extensions/enums/encodings_extension.dart'; +import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/processor_result.dart'; import '../../model/tokens/token.dart'; import '../../utils/errors.dart'; import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/token_import_origins.dart'; import 'token_import_file_processor_interface.dart'; class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index eb878bfe7..073ca5145 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -27,14 +27,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/token_repository.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:privacyidea_authenticator/utils/view_utils.dart'; +import '../interfaces/repo/token_repository.dart'; +import '../l10n/app_localizations.dart'; +import '../model/tokens/token.dart'; import '../utils/identifiers.dart'; +import '../utils/logger.dart'; +import '../utils/riverpod_providers.dart'; +import '../utils/view_utils.dart'; import '../views/settings_view/settings_view_widgets/send_error_dialog.dart'; import '../widgets/dialog_widgets/default_dialog.dart'; import '../widgets/dialog_widgets/default_dialog_button.dart'; @@ -100,9 +100,6 @@ class SecureTokenRepository implements TokenRepository { } if (valueJson == null || !valueJson.containsKey('type')) { - Logger.warning( - 'Could not deserialize token from secure storage. Value: $value\nserializedToken = $valueJson\ncontainsKey(type) = ${valueJson?.containsKey('type')} ', - name: 'secure_token_repository.dart#loadAllTokens'); // If valueJson is null or does not contain a type, it can't be a token. Skip it. continue; } diff --git a/lib/state_notifiers/push_request_notifier.dart b/lib/state_notifiers/push_request_notifier.dart index 67eb6e5f1..0510025bd 100644 --- a/lib/state_notifiers/push_request_notifier.dart +++ b/lib/state_notifiers/push_request_notifier.dart @@ -23,20 +23,21 @@ import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/model/push_request.dart'; -import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/network_utils.dart'; -import 'package:privacyidea_authenticator/utils/push_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; +import '../interfaces/repo/push_request_repository.dart'; +import '../l10n/app_localizations.dart'; +import '../model/push_request.dart'; import '../model/states/push_request_state.dart'; +import '../model/tokens/push_token.dart'; import '../repo/secure_push_request_repository.dart'; import '../utils/custom_int_buffer.dart'; +import '../utils/globals.dart'; +import '../utils/logger.dart'; +import '../utils/network_utils.dart'; +import '../utils/push_provider.dart'; +import '../utils/riverpod_providers.dart'; +import '../utils/rsa_utils.dart'; +import '../utils/utils.dart'; class PushRequestNotifier extends StateNotifier { late final Future initState; @@ -217,15 +218,15 @@ class PushRequestNotifier extends StateNotifier { /// Accepts a push request and returns true if successful, false if not. /// An accepted push request is removed from the state. /// It should be still in the CustomIntBuffer of the state. - Future accept(PushToken pushToken, PushRequest pushRequest) async { + Future accept(PushToken pushToken, PushRequest pushRequest, {String? selectedAnswer}) async { if (pushRequest.accepted != null) { - Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#decline'); + Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#accept'); return false; } - Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline'); + Logger.info('Accept push request.', name: 'push_request_notifier.dart#accept'); final updated = await _updatePushRequest(pushRequest, (p0) async { - final updated = p0.copyWith(accepted: true); + final updated = p0.copyWith(accepted: true, selectedAnswer: () => selectedAnswer); final success = await _handleReaction(pushRequest: updated, token: pushToken); if (!success) { return p0; @@ -244,7 +245,7 @@ class PushRequestNotifier extends StateNotifier { } Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline'); final updated = await _updatePushRequest(pushRequest, (p0) async { - final updated = p0.copyWith(accepted: false); + final updated = p0.copyWith(accepted: false, selectedAnswer: () => null); final success = await _handleReaction(pushRequest: updated, token: pushToken); if (!success) { return p0; @@ -318,28 +319,35 @@ class PushRequestNotifier extends StateNotifier { Future _handleReaction({required PushRequest pushRequest, required PushToken token}) async { if (pushRequest.accepted == null) return false; Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea', name: 'token_widgets.dart#handleReaction'); - // signature ::= {nonce}|{serial}[|decline] - String msg = '${pushRequest.nonce}|${token.serial}'; - if (pushRequest.accepted! == false) { - msg += '|decline'; - } - String? signature = await _rsaUtils.trySignWithToken(token, msg); - if (signature == null) { - return false; - } // POST https://privacyideaserver/validate/check // nonce= // serial= // signature= // decline=1 (optional) + // presence_answer= (optional) final Map body = { 'nonce': pushRequest.nonce, 'serial': token.serial, - 'signature': signature, }; + // signature ::= {nonce}|{serial}[|decline] + String msg = '${pushRequest.nonce}|${token.serial}'; if (pushRequest.accepted! == false) { - body["decline"] = "1"; + body['decline'] = '1'; + msg += '|decline'; } + if (pushRequest.possibleAnswers != null && pushRequest.selectedAnswer != null) { + body['presence_answer'] = pushRequest.selectedAnswer!; + msg += '|${pushRequest.selectedAnswer!}'; + } + Logger.warning('Signature message: $msg', name: 'token_widgets.dart#handleReaction'); + String? signature = await _rsaUtils.trySignWithToken(token, msg); + if (signature == null) { + Logger.warning('Failed to sign push request response.', name: 'token_widgets.dart#handleReaction'); + return false; + } + + body['signature'] = signature; + Response response; try { response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); @@ -354,6 +362,11 @@ class PushRequestNotifier extends StateNotifier { } } if (response.statusCode != 200) { + final appLocalizations = AppLocalizations.of(await globalContext)!; + globalRef?.read(statusMessageProvider.notifier).state = ( + '${appLocalizations.sendPushRequestResponseFailed}\n${appLocalizations.statusCode(response.statusCode)}', + tryJsonDecode(response.body)?["result"]?["error"]?["message"], + ); Logger.warning('Sending push request response failed.', name: 'token_widgets.dart#handleReaction'); return false; } diff --git a/lib/state_notifiers/token_notifier.dart b/lib/state_notifiers/token_notifier.dart index d48ff3387..7be86f3e2 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/state_notifiers/token_notifier.dart @@ -476,19 +476,20 @@ class TokenNotifier extends StateNotifier { // TODO What to do with poll only tokens if google-services is used? Logger.warning('SSLVerify: ${pushToken.sslVerify}', name: 'token_notifier.dart#rolloutPushToken'); + final fbToken = await _firebaseUtils.getFBToken(); Response response = await _ioClient.doPost( sslVerify: pushToken.sslVerify, url: pushToken.url!, body: { 'enrollment_credential': pushToken.enrollmentCredentials, 'serial': pushToken.serial, - 'fbtoken': await _firebaseUtils.getFBToken(), + 'fbtoken': fbToken, 'pubkey': _rsaUtils.serializeRSAPublicKeyPKCS8(pushToken.rsaPublicTokenKey!), }, ); if (response.statusCode == 200) { - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponse)); + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponse, fbToken: fbToken)); if (pushToken == null) { Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); return false; @@ -620,7 +621,10 @@ class TokenNotifier extends StateNotifier { continue; } Response response = await _ioClient.doPost( - sslVerify: p.sslVerify, url: p.url!, body: {'new_fb_token': firebaseToken, 'serial': p.serial, 'timestamp': timestamp, 'signature': signature}); + url: p.url!, + body: {'new_fb_token': firebaseToken, 'serial': p.serial, 'timestamp': timestamp, 'signature': signature}, + sslVerify: p.sslVerify, + ); if (response.statusCode == 200) { Logger.info('Updating firebase token for push token succeeded!', name: 'push_provider.dart#updateFirebaseToken'); _updateToken(p, (p0) => p0.copyWith(fbToken: firebaseToken)); diff --git a/lib/utils/crypto_utils.dart b/lib/utils/crypto_utils.dart index 5915edd25..0d708768b 100644 --- a/lib/utils/crypto_utils.dart +++ b/lib/utils/crypto_utils.dart @@ -25,9 +25,9 @@ import 'dart:math' as math; import 'package:base32/base32.dart'; import 'package:flutter/foundation.dart'; import 'package:pointycastle/export.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import '../model/enums/encodings.dart'; +import '../model/extensions/enums/encodings_extension.dart'; Future pbkdf2({required Uint8List salt, required int iterations, required int keyLength, required Uint8List password}) async { ArgumentError.checkNotNull(salt); diff --git a/lib/utils/customization/application_customization.dart b/lib/utils/customization/application_customization.dart index ee57f2f16..b16fb062f 100644 --- a/lib/utils/customization/application_customization.dart +++ b/lib/utils/customization/application_customization.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/utils/customization/theme_customization.dart'; +import '../../../utils/customization/theme_customization.dart'; import '../../model/enums/app_feature.dart'; diff --git a/lib/utils/customization/theme_customization.dart b/lib/utils/customization/theme_customization.dart index 93818c883..ba22d818a 100644 --- a/lib/utils/customization/theme_customization.dart +++ b/lib/utils/customization/theme_customization.dart @@ -1,8 +1,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/utils/customization/action_theme.dart'; -import 'package:privacyidea_authenticator/utils/customization/extended_text_theme.dart'; + +import '../../../utils/customization/action_theme.dart'; +import '../../../utils/customization/extended_text_theme.dart'; class ThemeCustomization { static const ThemeCustomization defaultLightTheme = ThemeCustomization.defaultLightWith(); @@ -265,23 +266,25 @@ class ThemeCustomization { primaryColor: primaryColor, canvasColor: backgroundColor, textTheme: const TextTheme().copyWith( - bodyLarge: TextStyle(color: foregroundColor), - bodyMedium: TextStyle(color: foregroundColor), - titleMedium: TextStyle(color: foregroundColor), - titleSmall: TextStyle(color: foregroundColor), + bodyLarge: TextStyle(color: foregroundColor, fontSize: 18), + bodyMedium: TextStyle(color: foregroundColor, fontSize: 16), + bodySmall: TextStyle(color: subtitleColor, fontSize: 14), + titleLarge: TextStyle(color: primaryColor), + titleMedium: TextStyle(color: primaryColor), + titleSmall: TextStyle(color: primaryColor), displayLarge: TextStyle(color: foregroundColor), displayMedium: TextStyle(color: foregroundColor), displaySmall: TextStyle(color: foregroundColor), - headlineMedium: TextStyle(color: foregroundColor), - headlineSmall: TextStyle(color: foregroundColor), - titleLarge: TextStyle(color: primaryColor), - bodySmall: TextStyle(color: subtitleColor), + headlineMedium: TextStyle(color: primaryColor), + headlineLarge: TextStyle(color: primaryColor), + headlineSmall: TextStyle(color: primaryColor), labelLarge: TextStyle(color: foregroundColor), + labelMedium: TextStyle(color: foregroundColor), labelSmall: TextStyle(color: foregroundColor), ), iconButtonTheme: IconButtonThemeData( style: ButtonStyle( - foregroundColor: MaterialStateProperty.all(foregroundColor), + foregroundColor: WidgetStateProperty.all(foregroundColor), ), ), elevatedButtonTheme: ElevatedButtonThemeData( @@ -324,7 +327,7 @@ class ThemeCustomization { navigationBarTheme: const NavigationBarThemeData().copyWith( backgroundColor: navigationBarColor, shadowColor: shadowColor, - iconTheme: MaterialStatePropertyAll(IconThemeData(color: navigationBarIconColor)), + iconTheme: WidgetStatePropertyAll(IconThemeData(color: navigationBarIconColor)), elevation: 3, ), floatingActionButtonTheme: FloatingActionButtonThemeData( @@ -361,8 +364,8 @@ class ThemeCustomization { errorContainer: deleteColor, ), checkboxTheme: CheckboxThemeData( - checkColor: MaterialStateProperty.resolveWith((_) => onPrimary), - fillColor: MaterialStateProperty.resolveWith((Set states) { + checkColor: WidgetStateProperty.resolveWith((_) => onPrimary), + fillColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.disabled)) { return null; } @@ -373,7 +376,7 @@ class ThemeCustomization { }), ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { + fillColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.disabled)) { return null; } @@ -384,7 +387,7 @@ class ThemeCustomization { }), ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.disabled)) { return null; } @@ -393,7 +396,7 @@ class ThemeCustomization { } return null; }), - trackColor: MaterialStateProperty.resolveWith((Set states) { + trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.disabled)) { return null; } diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index 0319e0653..a979c05c4 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -7,7 +7,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:home_widget/home_widget.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/utils/customization/theme_customization.dart'; +import '../utils/customization/theme_customization.dart'; import '../interfaces/repo/token_folder_repository.dart'; import '../interfaces/repo/token_repository.dart'; diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index e56679f64..3b3811ddc 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -63,6 +63,7 @@ const String PUSH_REQUEST_QUESTION = 'question'; // 4. const String PUSH_REQUEST_TITLE = 'title'; // 5. const String PUSH_REQUEST_SSL_VERIFY = 'sslverify'; // 6. const String PUSH_REQUEST_SIGNATURE = 'signature'; // 7. +const String PUSH_REQUEST_ANSWERS = 'require_presence'; // 8. const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; diff --git a/lib/utils/image_converter.dart b/lib/utils/image_converter.dart index c4181945e..aefbb1bcb 100644 --- a/lib/utils/image_converter.dart +++ b/lib/utils/image_converter.dart @@ -5,7 +5,8 @@ import 'dart:ui'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:image/image.dart' as imglib; -import 'package:privacyidea_authenticator/utils/logger.dart'; + +import '../utils/logger.dart'; class ImageConverter { final imglib.Image image; diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index c5e75431e..53a40e504 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -11,10 +11,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logger/logger.dart' as printer; import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/app_info_utils.dart'; -import 'package:privacyidea_authenticator/utils/pi_mailer.dart'; +import '../l10n/app_localizations.dart'; +import '../utils/app_info_utils.dart'; +import '../utils/pi_mailer.dart'; import '../views/settings_view/settings_view_widgets/send_error_dialog.dart'; import 'globals.dart'; import 'riverpod_providers.dart'; diff --git a/lib/utils/network_utils.dart b/lib/utils/network_utils.dart index 9d63c4103..c2ecf9768 100644 --- a/lib/utils/network_utils.dart +++ b/lib/utils/network_utils.dart @@ -25,11 +25,12 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:privacyidea_authenticator/utils/view_utils.dart'; + +import '../l10n/app_localizations.dart'; +import '../utils/globals.dart'; +import '../utils/logger.dart'; +import '../utils/riverpod_providers.dart'; +import '../utils/view_utils.dart'; class PrivacyIdeaIOClient { const PrivacyIdeaIOClient(); @@ -124,6 +125,7 @@ class PrivacyIdeaIOClient { 'Received unexpected response', name: 'utils.dart#doPost', error: 'Status code: ${response.statusCode}' '\nPosted body: $body' '\nResponse: ${response.body}\n', + stackTrace: StackTrace.current, ); } ioClient.close(); diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index 2e62c5473..fe7fc07cf 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -26,13 +26,13 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:http/http.dart'; import 'package:pi_authenticator_legacy/pi_authenticator_legacy.dart'; -import 'package:privacyidea_authenticator/repo/secure_push_request_repository.dart'; -import 'package:privacyidea_authenticator/utils/pi_notifications.dart'; import '../l10n/app_localizations.dart'; import '../model/push_request.dart'; import '../model/tokens/push_token.dart'; +import '../repo/secure_push_request_repository.dart'; import '../repo/secure_token_repository.dart'; +import '../utils/pi_notifications.dart'; import 'firebase_utils.dart'; import 'globals.dart'; import 'logger.dart'; @@ -144,7 +144,7 @@ class PushProvider { try { data = _getAndValidateDataFromRemoteMessage(remoteMessage); } on ArgumentError catch (_) { - Logger.info('Try requesting the challenge by polling.', name: 'push_provider.dart#_foregroundHandler'); + Logger.info('Failed to parse push request data. Trying to poll for challenges.', name: 'push_provider.dart#_foregroundHandler'); await pollForChallenges(isManually: true); return; } @@ -152,8 +152,12 @@ class PushProvider { try { return _handleIncomingRequestForeground(data); } catch (e, s) { - final errorMessage = AppLocalizations.of(globalNavigatorKey.currentContext!)!.unexpectedError; - Logger.error(errorMessage, name: 'push_provider.dart#_foregroundHandler', error: e, stackTrace: s); + Logger.error( + AppLocalizations.of(globalNavigatorKey.currentContext!)!.unexpectedError, + name: 'push_provider.dart#_foregroundHandler', + error: e, + stackTrace: s, + ); } } @@ -198,6 +202,7 @@ class PushProvider { Future _handleIncomingRequestForeground(Map data) async { Logger.info('Incoming push challenge.', name: 'push_provider.dart#_handleIncomingRequestForeground'); PushRequest pushRequest = PushRequest.fromMessageData(data); + Logger.info("PushRequest.possibleAnswers: ${pushRequest.possibleAnswers}", name: 'push_provider.dart#_handleIncomingRequestForeground'); Logger.info('Parsing data of push request succeeded.', name: 'push_provider.dart#_handleIncomingRequestForeground'); final pushToken = globalRef?.read(tokenProvider).getTokenBySerial(pushRequest.serial); if (pushToken == null) { diff --git a/lib/utils/rsa_utils.dart b/lib/utils/rsa_utils.dart index 270215dcb..5ca0105aa 100644 --- a/lib/utils/rsa_utils.dart +++ b/lib/utils/rsa_utils.dart @@ -25,12 +25,12 @@ import 'package:base32/base32.dart'; import 'package:flutter/foundation.dart'; import 'package:pi_authenticator_legacy/pi_authenticator_legacy.dart'; import 'package:pointycastle/export.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; -import 'package:privacyidea_authenticator/utils/crypto_utils.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; +import '../l10n/app_localizations.dart'; +import '../model/tokens/push_token.dart'; +import '../utils/crypto_utils.dart'; +import '../utils/identifiers.dart'; +import '../utils/logger.dart'; +import '../utils/riverpod_providers.dart'; import 'globals.dart'; diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 740912db2..60f21fdb5 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -26,7 +26,8 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; + +import '../utils/logger.dart'; /// Inserts [char] at the position [pos] in the given String ([str]), /// and returns the resulting String. @@ -120,3 +121,11 @@ bool doesThrow(Function() f) { return true; } } + +dynamic tryJsonDecode(String json) { + try { + return jsonDecode(json); + } catch (_) { + return null; + } +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart index 10a387e23..822d58eb7 100644 --- a/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart @@ -50,7 +50,7 @@ class _LabeledDropdownButtonState extends State> { child: Text( '${value is Enum ? value.name : value}' '${widget.postFix}', - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), diff --git a/lib/views/feedback_view/feedback_view.dart b/lib/views/feedback_view/feedback_view.dart index 8faeb446c..03f57451e 100644 --- a/lib/views/feedback_view/feedback_view.dart +++ b/lib/views/feedback_view/feedback_view.dart @@ -73,7 +73,7 @@ class _FeedbackViewState extends State { children: [ Text( AppLocalizations.of(context)!.feedbackDescription, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.justify, ), const SizedBox(height: 16), diff --git a/lib/views/import_tokens_view/pages/import_start_page.dart b/lib/views/import_tokens_view/pages/import_start_page.dart index c993a764f..c2f93973e 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -5,18 +5,18 @@ import 'dart:isolate'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:image/image.dart' as img; -import 'package:privacyidea_authenticator/model/enums/token_import_type.dart'; -import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_import_type_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/processor_result.dart'; -import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; import 'package:zxing2/qrcode.dart'; import '../../../l10n/app_localizations.dart'; +import '../../../model/enums/token_import_type.dart'; +import '../../../model/enums/token_origin_source_type.dart'; +import '../../../model/extensions/enums/token_import_type_extension.dart'; +import '../../../model/extensions/enums/token_origin_source_type.dart'; +import '../../../model/processor_result.dart'; import '../../../model/token_import/token_import_source.dart'; import '../../../model/tokens/token.dart'; import '../../../processors/mixins/token_import_processor.dart'; +import '../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; import '../../../processors/token_import_file_processor/token_import_file_processor_interface.dart'; import '../../../utils/logger.dart'; import '../../qr_scanner_view/qr_scanner_view.dart'; diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index ab1007102..d7c7a862a 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -15,7 +15,7 @@ import 'main_view_widgets/main_view_navigation_bar.dart'; import 'main_view_widgets/main_view_tokens_list.dart'; import 'main_view_widgets/main_view_tokens_list_filtered.dart'; -export 'package:privacyidea_authenticator/views/main_view/main_view.dart'; +export '../../views/main_view/main_view.dart'; class MainView extends ConsumerStatefulView { static const routeName = '/mainView'; diff --git a/lib/views/main_view/main_view_widgets/no_token_screen.dart b/lib/views/main_view/main_view_widgets/no_token_screen.dart index ba0635593..3f97a6e42 100644 --- a/lib/views/main_view/main_view_widgets/no_token_screen.dart +++ b/lib/views/main_view/main_view_widgets/no_token_screen.dart @@ -22,7 +22,7 @@ class NoTokenScreen extends StatelessWidget { ), Text( AppLocalizations.of(context)!.noResultText1, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), @@ -32,7 +32,7 @@ class NoTokenScreen extends StatelessWidget { ), Text( AppLocalizations.of(context)!.noResultText2, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ) diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart index ee6f49281..7e1df7177 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart @@ -139,7 +139,7 @@ class EditPushTokenAction extends TokenAction { ExpansionTile( title: Text( AppLocalizations.of(context)!.publicKey, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), @@ -154,7 +154,7 @@ class EditPushTokenAction extends TokenAction { ExpansionTile( title: Text( AppLocalizations.of(context)!.firebaseToken, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart index 1d8109319..d02c17569 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart @@ -26,7 +26,7 @@ class RolloutFailedWidget extends StatelessWidget { child: FittedBox( child: Text( token.rolloutState.rolloutMsg(localizations), - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), ), @@ -50,7 +50,7 @@ class RolloutFailedWidget extends StatelessWidget { SizedBox( width: width * 0.35, child: PressButton( - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.errorContainer)), + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.errorContainer)), onPressed: () => _showDialog(), child: Text( localizations.delete, diff --git a/lib/views/qr_scanner_view/qr_scanner_view.dart b/lib/views/qr_scanner_view/qr_scanner_view.dart index 63edc62dd..8596f32a9 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view.dart @@ -20,12 +20,12 @@ import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/views/view_interface.dart'; -import 'package:privacyidea_authenticator/widgets/dialog_widgets/default_dialog.dart'; -import 'package:privacyidea_authenticator/widgets/dialog_widgets/default_dialog_button.dart'; +import '../../l10n/app_localizations.dart'; import '../../utils/logger.dart'; +import '../../views/view_interface.dart'; +import '../../widgets/dialog_widgets/default_dialog.dart'; +import '../../widgets/dialog_widgets/default_dialog_button.dart'; import 'qr_scanner_view_widgets/qr_scanner_widget.dart'; class QRScannerView extends StatefulView { diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart index 556e37fb0..52eed0795 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart @@ -17,12 +17,12 @@ class SelectExportTypeDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ SettingsListTileButton( - title: Text(AppLocalizations.of(context)!.toFile, style: Theme.of(context).textTheme.titleMedium), + title: Text(AppLocalizations.of(context)!.toFile, style: Theme.of(context).textTheme.bodyMedium), onPressed: () async => _selectTokensDialog(context), icon: const Icon(Icons.file_present, size: 24), ), SettingsListTileButton( - title: Text(AppLocalizations.of(context)!.asQrCode, style: Theme.of(context).textTheme.titleMedium), + title: Text(AppLocalizations.of(context)!.asQrCode, style: Theme.of(context).textTheme.bodyMedium), onPressed: () async => _selectTokenDialog(context), icon: const Icon(Icons.qr_code, size: 24)), ], diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index 4351536f6..a236a0a14 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -33,7 +33,7 @@ class _SelectTokensDialogState extends ConsumerState { ? Text( AppLocalizations.of(context)!.noTokensToExport, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).colorScheme.secondary), + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.secondary), ) : Column( mainAxisSize: MainAxisSize.min, @@ -76,7 +76,7 @@ class _SelectTokensDialogState extends ConsumerState { child: TextButton( style: _selectedTokens.contains(token) ? ButtonStyle( - backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.secondary.withAlpha(80)), + backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.secondary.withAlpha(80)), ) : null, onPressed: () async { diff --git a/lib/views/settings_view/settings_groups/settings_group_error_log.dart b/lib/views/settings_view/settings_groups/settings_group_error_log.dart index 8d7e1a9f3..9536bb269 100644 --- a/lib/views/settings_view/settings_groups/settings_group_error_log.dart +++ b/lib/views/settings_view/settings_groups/settings_group_error_log.dart @@ -14,7 +14,7 @@ class SettingsGroupErrorLog extends StatelessWidget { ListTile( title: Text( AppLocalizations.of(context)!.logMenu, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), diff --git a/lib/views/settings_view/settings_groups/settings_group_general.dart b/lib/views/settings_view/settings_groups/settings_group_general.dart index bf37cf33d..c879ec942 100644 --- a/lib/views/settings_view/settings_groups/settings_group_general.dart +++ b/lib/views/settings_view/settings_groups/settings_group_general.dart @@ -26,7 +26,7 @@ class SettingsGroupGeneral extends StatelessWidget { }, title: Text( AppLocalizations.of(context)!.privacyPolicy, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), @@ -37,7 +37,7 @@ class SettingsGroupGeneral extends StatelessWidget { }, title: Text( AppLocalizations.of(context)!.licensesAndVersion, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), @@ -47,7 +47,7 @@ class SettingsGroupGeneral extends StatelessWidget { title: Text( AppLocalizations.of(context)!.thisAppIsOpenSource, //'This Application is a Open Source Project. Visit us on GitHub.', - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, maxLines: 2, ), icon: const Icon(SimpleIcons.github), @@ -58,7 +58,7 @@ class SettingsGroupGeneral extends StatelessWidget { }, title: Text( 'Feedback', - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), diff --git a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart index b2ae2b802..bf0d655d3 100644 --- a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart +++ b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart @@ -24,7 +24,7 @@ class _SettingsGroupImportExportTokensState extends ConsumerState SettingsGroup( - title: AppLocalizations.of(context)!.language, - children: [ - SwitchListTile( - title: Text( - AppLocalizations.of(context)!.useDeviceLocaleTitle, - style: Theme.of(context).textTheme.titleMedium, - ), - subtitle: Text( - AppLocalizations.of(context)!.useDeviceLocaleDescription, - overflow: TextOverflow.fade, - ), - value: ref.watch(settingsProvider).useSystemLocale, - onChanged: (value) => ref.read(settingsProvider.notifier).setUseSystemLocale(value)), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: DropdownButton( - disabledHint: Text( - '${ref.watch(settingsProvider).currentLocale}', - style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.grey), - overflow: TextOverflow.fade, - softWrap: false, - ), - isExpanded: true, - value: ref.watch(settingsProvider).currentLocale, - items: AppLocalizations.supportedLocales.map>((Locale itemLocale) { - return DropdownMenuItem( - value: itemLocale, - child: Text( - '$itemLocale', - overflow: TextOverflow.fade, - softWrap: false, - ), - ); - }).toList(), - onChanged: ref.watch(settingsProvider).useSystemLocale ? null : (value) => ref.read(settingsProvider.notifier).setLocalePreference(value!), + Widget build(BuildContext context, WidgetRef ref) { + final localizations = AppLocalizations.of(context)!; + return SettingsGroup( + title: localizations.language, + children: [ + SwitchListTile( + title: Text( + localizations.useDeviceLocaleTitle, + style: Theme.of(context).textTheme.bodyMedium, ), + subtitle: Text( + localizations.useDeviceLocaleDescription, + overflow: TextOverflow.fade, + ), + value: ref.watch(settingsProvider).useSystemLocale, + onChanged: (value) => ref.read(settingsProvider.notifier).setUseSystemLocale(value)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: DropdownButton( + disabledHint: Text( + '${ref.watch(settingsProvider).currentLocale}', + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey), + overflow: TextOverflow.fade, + softWrap: false, + ), + isExpanded: true, + value: ref.watch(settingsProvider).currentLocale, + items: AppLocalizations.supportedLocales.map>((Locale itemLocale) { + return DropdownMenuItem( + value: itemLocale, + child: Text( + '$itemLocale', + overflow: TextOverflow.fade, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: ref.watch(settingsProvider).useSystemLocale ? Colors.grey : null), + softWrap: false, + ), + ); + }).toList(), + onChanged: ref.watch(settingsProvider).useSystemLocale ? null : (value) => ref.read(settingsProvider.notifier).setLocalePreference(value!), ), - ], - ); + ), + ], + ); + } } diff --git a/lib/views/settings_view/settings_groups/settings_group_push_token.dart b/lib/views/settings_view/settings_groups/settings_group_push_token.dart index 4ca61c79f..c3979df0f 100644 --- a/lib/views/settings_view/settings_groups/settings_group_push_token.dart +++ b/lib/views/settings_view/settings_groups/settings_group_push_token.dart @@ -24,7 +24,7 @@ class SettingsGroupPushToken extends ConsumerWidget { ListTile( title: Text( AppLocalizations.of(context)!.synchronizePushTokens, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, ), subtitle: Text( AppLocalizations.of(context)!.synchronizesTokensWithServer, @@ -54,7 +54,7 @@ class SettingsGroupPushToken extends ConsumerWidget { children: [ TextSpan( text: AppLocalizations.of(context)!.enablePolling, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, ), // Add clickable icon to inform user of unsupported push tokens (for polling) WidgetSpan( @@ -89,7 +89,7 @@ class SettingsGroupPushToken extends ConsumerWidget { children: [ TextSpan( text: AppLocalizations.of(context)!.hidePushTokens, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, ), ], ), diff --git a/lib/views/settings_view/settings_groups/settings_group_theme.dart b/lib/views/settings_view/settings_groups/settings_group_theme.dart index aca058ea2..77d32c44d 100644 --- a/lib/views/settings_view/settings_groups/settings_group_theme.dart +++ b/lib/views/settings_view/settings_groups/settings_group_theme.dart @@ -15,7 +15,7 @@ class SettingsGroupTheme extends StatelessWidget { RadioListTile( title: Text( AppLocalizations.of(context)!.lightTheme, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), @@ -30,7 +30,7 @@ class SettingsGroupTheme extends StatelessWidget { RadioListTile( title: Text( AppLocalizations.of(context)!.darkTheme, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), @@ -45,7 +45,7 @@ class SettingsGroupTheme extends StatelessWidget { RadioListTile( title: Text( AppLocalizations.of(context)!.systemTheme, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, ), value: ThemeMode.system, groupValue: EasyDynamicTheme.of(context).themeMode, diff --git a/lib/views/settings_view/settings_view_widgets/logging_menu.dart b/lib/views/settings_view/settings_view_widgets/logging_menu.dart index 00e7924f6..466f4f08a 100644 --- a/lib/views/settings_view/settings_view_widgets/logging_menu.dart +++ b/lib/views/settings_view/settings_view_widgets/logging_menu.dart @@ -30,7 +30,7 @@ class LoggingMenu extends ConsumerWidget { ListTile( title: Text( AppLocalizations.of(context)!.verboseLogging, - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), contentPadding: const EdgeInsets.all(0), diff --git a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart index 5419b74dd..20f2bb288 100644 --- a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart @@ -20,13 +20,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:privacyidea_authenticator/utils/view_utils.dart'; +import '../../../l10n/app_localizations.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/globals.dart'; +import '../../../utils/logger.dart'; +import '../../../utils/riverpod_providers.dart'; +import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; class UpdateFirebaseTokenDialog extends ConsumerStatefulWidget { diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index c0b2545c5..c13ad8981 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../model/enums/app_feature.dart'; -import '../../utils/customization/application_customization.dart'; import '../../utils/app_info_utils.dart'; +import '../../utils/customization/application_customization.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; import '../../utils/riverpod_providers.dart'; diff --git a/lib/widgets/dialog_widgets/patch_notes_dialog.dart b/lib/widgets/dialog_widgets/patch_notes_dialog.dart index 288bbc726..8283e9f55 100644 --- a/lib/widgets/dialog_widgets/patch_notes_dialog.dart +++ b/lib/widgets/dialog_widgets/patch_notes_dialog.dart @@ -39,7 +39,7 @@ class PatchNotesDialog extends StatelessWidget { children: [ Text( '${localizations.version}: ${version.toString()}', - style: Theme.of(context).textTheme.titleMedium, + style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 16), ...newNotes[version]!.entries.map( diff --git a/lib/widgets/dialog_widgets/push_request_dialog.dart b/lib/widgets/dialog_widgets/push_request_dialog.dart index f3a88453a..dc0c66ebc 100644 --- a/lib/widgets/dialog_widgets/push_request_dialog.dart +++ b/lib/widgets/dialog_widgets/push_request_dialog.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -7,6 +9,7 @@ import '../../model/push_request.dart'; import '../../model/tokens/push_token.dart'; import '../../utils/globals.dart'; import '../../utils/lock_auth.dart'; +import '../../utils/logger.dart'; import '../../utils/riverpod_providers.dart'; import '../press_button.dart'; import 'default_dialog.dart'; @@ -21,9 +24,14 @@ class PushRequestDialog extends ConsumerStatefulWidget { } class _PushRequestDialogState extends ConsumerState { - static const titleScale = 1.35; - static const questionScale = 1.1; double get lineHeight => Theme.of(context).textTheme.titleLarge?.fontSize ?? 16; + double get spacerHeight => lineHeight * 0.5; + + static WidgetStateProperty buttonShape(BuildContext context) => WidgetStateProperty.all( + Theme.of(context).elevatedButtonTheme.style?.shape?.resolve({})?.copyWith( + side: BorderSide(color: Theme.of(context).colorScheme.onPrimary, width: 2.5), + ), + ); bool isHandled = false; bool dialogIsOpen = false; @@ -44,60 +52,119 @@ class _PushRequestDialogState extends ConsumerState { @override Widget build(BuildContext context) { - final lineHeight = this.lineHeight; + final spacerHeight = this.spacerHeight; final question = widget.pushRequest.question; + final token = ref.watch(tokenProvider).getTokenBySerial(widget.pushRequest.serial); + final localizations = AppLocalizations.of(context)!; if (token == null) { WidgetsBinding.instance.addPostFrameCallback((_) async { - if (mounted) { - ref.read(pushRequestProvider.notifier).remove(widget.pushRequest); - } + if (mounted) ref.read(pushRequestProvider.notifier).remove(widget.pushRequest); }); } + return isHandled || token == null ? const SizedBox() : Container( color: Colors.transparent, child: DefaultDialog( + scrollable: false, title: Text( - AppLocalizations.of(context)!.authenticationRequest, - style: Theme.of(context).textTheme.titleLarge!, + localizations.authenticationRequest, + style: Theme.of(context).textTheme.headlineMedium!, textAlign: TextAlign.center, - textScaler: const TextScaler.linear(titleScale), ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - AppLocalizations.of(context)!.requestInfo( + localizations.requestInfo( token.issuer, token.label, ), - style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: Theme.of(context).textTheme.titleMedium?.fontSize), - textScaler: const TextScaler.linear(questionScale), + style: Theme.of(context).textTheme.bodyLarge!, textAlign: TextAlign.center, ), - SizedBox(height: lineHeight), + SizedBox(height: spacerHeight), ...[ Text( question, - style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: Theme.of(context).textTheme.titleMedium?.fontSize), - textScaler: const TextScaler.linear(questionScale), + style: Theme.of(context).textTheme.bodyLarge!, textAlign: TextAlign.center, ), - SizedBox(height: lineHeight), + SizedBox(height: spacerHeight), ], + widget.pushRequest.possibleAnswers != null + ? Flexible( + child: SingleChildScrollView( + child: AnswerSelectionWidget( + onAnswerSelected: (selectedAnswer) async { + if (token.isLocked && await lockAuth(localizedReason: localizations.authToAcceptPushRequest) == false) { + return; + } + ref.read(pushRequestProvider.notifier).accept(token, widget.pushRequest, selectedAnswer: selectedAnswer).then((success) { + Logger.info('accept push request success: $success', name: 'push_request_dialog.dart#AnswerSelectionWidget'); + if (!success && mounted) setState(() => isHandled = false); + }); + if (mounted) setState(() => isHandled = true); + }, + possibleAnswers: widget.pushRequest.possibleAnswers!, + ), + ), + ) + : SizedBox( + // Accept button + child: PressButton( + style: ButtonStyle(shape: buttonShape(context)), + onPressed: () async { + if (token.isLocked && await lockAuth(localizedReason: localizations.authToAcceptPushRequest) == false) { + return; + } + ref.read(pushRequestProvider.notifier).accept(token, widget.pushRequest).then((success) { + Logger.info('accept push request success: $success', name: 'push_request_dialog.dart#AnswerSelectionWidget'); + if (!success && mounted) setState(() => isHandled = false); + }); + if (mounted) setState(() => isHandled = true); + }, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.symmetric(vertical: spacerHeight), + child: Text( + localizations.accept, + style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Theme.of(context).colorScheme.onPrimary), + textAlign: TextAlign.center, + maxLines: 1, + ), + ), + Icon( + Icons.check_outlined, + size: Theme.of(context).textTheme.headlineMedium?.fontSize ?? 16, + ), + ], + ), + ), + ), SizedBox( - // Accept button - height: lineHeight * titleScale * 2 + 16, + // Decline button child: PressButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.errorContainer, + ), + shape: buttonShape(context), + ), onPressed: () async { - if (token.isLocked && await lockAuth(localizedReason: AppLocalizations.of(context)!.authToAcceptPushRequest) == false) { + if (token.isLocked && await lockAuth(localizedReason: localizations.authToDeclinePushRequest) == false) { return; } - globalRef?.read(pushRequestProvider.notifier).accept(token, widget.pushRequest); - if (mounted) setState(() => isHandled = true); + dialogIsOpen = true; + await _showConfirmationDialog(token); + dialogIsOpen = false; }, child: Row( mainAxisSize: MainAxisSize.max, @@ -105,151 +172,207 @@ class _PushRequestDialogState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - AppLocalizations.of(context)!.accept, - style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), - textScaler: const TextScaler.linear(titleScale), + localizations.decline, + style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Theme.of(context).colorScheme.onPrimary), textAlign: TextAlign.center, - maxLines: 1, ), Icon( - Icons.check_outlined, - size: lineHeight * titleScale, + Icons.close_outlined, + size: Theme.of(context).textTheme.headlineMedium?.fontSize ?? 16, ), ], ), ), ), - SizedBox(height: lineHeight * 0.5), - SizedBox( - // Decline button - height: lineHeight * titleScale + 16, - child: PressButton( - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.errorContainer)), + SizedBox(height: spacerHeight), + ], + ), + ), + ); + } + + Future _showConfirmationDialog(PushToken pushToken) { + final localizations = AppLocalizations.of(context)!; + return showDialog( + useRootNavigator: false, + context: globalNavigatorKey.currentContext!, + builder: (BuildContext context) { + final spacerHeight = this.spacerHeight; + return DefaultDialog( + title: Text( + localizations.authenticationRequest, + style: Theme.of(context).textTheme.titleLarge!, + textAlign: TextAlign.center, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + localizations.requestTriggerdByUserQuestion, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: Theme.of(context).textTheme.titleMedium?.fontSize), + textAlign: TextAlign.center, + ), + SizedBox(height: spacerHeight), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Expanded(child: SizedBox()), + Expanded( + flex: 6, + child: PressButton( + style: ButtonStyle(shape: buttonShape(context)), onPressed: () async { - if (token.isLocked && await lockAuth(localizedReason: AppLocalizations.of(context)!.authToDeclinePushRequest) == false) { + if (pushToken.isLocked && await lockAuth(localizedReason: localizations.authToDeclinePushRequest) == false) { return; } - dialogIsOpen = true; - await _showConfirmationDialog(token); - dialogIsOpen = false; + + ref.read(pushRequestProvider.notifier).remove(widget.pushRequest).then((success) { + Logger.info('remove push request success: $success', name: 'push_request_dialog.dart#_showConfirmationDialog'); + if (!success && mounted) setState(() => isHandled = false); + }); + if (context.mounted) Navigator.of(context).pop(); + if (mounted) setState(() => isHandled = true); }, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ Text( - AppLocalizations.of(context)!.decline, - style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), - textScaler: const TextScaler.linear(titleScale), + localizations.yes, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), textAlign: TextAlign.center, ), - Icon(Icons.close_outlined, size: lineHeight * titleScale), + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + localizations.butDiscardIt, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onPrimary.mixWith(Colors.grey.shade800), + ), + textAlign: TextAlign.center, + softWrap: false, + ), + ), ], - )), - ), - ], - ), - ), - ); - } + ), + ), + ), + const Expanded(flex: 2, child: SizedBox()), + Expanded( + flex: 6, + child: PressButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.errorContainer), + shape: buttonShape(context), + ), + onPressed: () async { + //TODO: Notify issuer + if (pushToken.isLocked && await lockAuth(localizedReason: localizations.authToDeclinePushRequest) == false) { + return; + } - Future _showConfirmationDialog(PushToken pushToken) => showDialog( - useRootNavigator: false, - context: globalNavigatorKey.currentContext!, - builder: (BuildContext context) { - final lineHeight = this.lineHeight; - return DefaultDialog( - title: Text( - AppLocalizations.of(context)!.authenticationRequest, - style: Theme.of(context).textTheme.titleLarge!, - textAlign: TextAlign.center, - textScaler: const TextScaler.linear(titleScale), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - AppLocalizations.of(context)!.requestTriggerdByUserQuestion, - style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: Theme.of(context).textTheme.titleMedium?.fontSize), - textScaler: const TextScaler.linear(questionScale), - textAlign: TextAlign.center, - ), - SizedBox(height: lineHeight), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Expanded(child: SizedBox()), - Expanded( - flex: 6, - child: PressButton( - onPressed: () { - globalRef?.read(pushRequestProvider.notifier).decline(pushToken, widget.pushRequest); - Navigator.of(context).pop(); - if (mounted) setState(() => isHandled = true); - }, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.yes, - style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), - textScaler: const TextScaler.linear(titleScale), - textAlign: TextAlign.center, - ), - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - AppLocalizations.of(context)!.butDiscardIt, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onPrimary.mixWith(Colors.grey.shade800), - ), + ref.read(pushRequestProvider.notifier).decline(pushToken, widget.pushRequest).then((success) { + Logger.info('decline push request success: $success', name: 'push_request_dialog.dart#_showConfirmationDialog'); + if (!success && mounted) setState(() => isHandled = false); + }); + if (context.mounted) Navigator.of(context).pop(); + if (mounted) setState(() => isHandled = true); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + localizations.no, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), + textAlign: TextAlign.center, + ), + Text( + localizations.declineIt, + style: + Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.onPrimary.mixWith(Colors.grey.shade800)), textAlign: TextAlign.center, softWrap: false, ), - ), - ], + ], + ), ), ), - ), - const Expanded(flex: 2, child: SizedBox()), - Expanded( - flex: 6, - child: PressButton( - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.errorContainer)), - onPressed: () { - //TODO: Notify issuer - globalRef?.read(pushRequestProvider.notifier).decline(pushToken, widget.pushRequest); - Navigator.of(context).pop(); - if (mounted) setState(() => isHandled = true); - }, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.no, - style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), - textScaler: const TextScaler.linear(titleScale), - textAlign: TextAlign.center, - ), - Text( - AppLocalizations.of(context)!.declineIt, - style: - Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.onPrimary.mixWith(Colors.grey.shade800)), + const Expanded(child: SizedBox()), + ], + ), + ], + ), + ); + }); + } +} + +class AnswerSelectionWidget extends StatefulWidget { + final List possibleAnswers; + final void Function(T) onAnswerSelected; + const AnswerSelectionWidget({required this.possibleAnswers, super.key, required this.onAnswerSelected}); + + @override + State> createState() => _AnswerSelectionWidgetState(); +} + +class _AnswerSelectionWidgetState extends State> { + double get lineHeight => Theme.of(context).textTheme.titleLarge?.fontSize ?? 16; + double get spacerHeight => lineHeight * 0.5; + @override + Widget build(BuildContext context) { + final children = []; + final possibleAnswers = widget.possibleAnswers.toList(); + final totalAnswersNum = possibleAnswers.length; + const numPerRow = 3; + while (possibleAnswers.isNotEmpty) { + final maxThisRow = possibleAnswers.length == numPerRow + 1 ? min(possibleAnswers.length, (numPerRow / 2).ceil()) : min(possibleAnswers.length, numPerRow); + final answersThisRow = possibleAnswers.sublist(0, maxThisRow); + possibleAnswers.removeRange(0, maxThisRow); + final spacer = (maxThisRow != numPerRow && totalAnswersNum > maxThisRow) + ? Expanded(flex: (numPerRow - answersThisRow.length) * answersThisRow.length, child: const SizedBox()) + : const SizedBox(); + children.add( + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + spacer, + for (final possibleAnswer in answersThisRow) + Expanded( + flex: answersThisRow.length * 2, + child: PressButton( + style: ButtonStyle(shape: _PushRequestDialogState.buttonShape(context)), + onPressed: () => widget.onAnswerSelected(possibleAnswer), + child: Padding( + padding: EdgeInsets.symmetric(vertical: spacerHeight), + child: SizedBox( + height: lineHeight * 2, + child: Center( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + possibleAnswer.toString(), + style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: Theme.of(context).colorScheme.onPrimary), textAlign: TextAlign.center, - softWrap: false, ), - ], + ), ), ), ), - const Expanded(child: SizedBox()), - ], + ), ), - ], - ), - ); - }); + spacer, + ], + ), + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children, + ); + } } diff --git a/lib/widgets/dialog_widgets/two_step_dialog.dart b/lib/widgets/dialog_widgets/two_step_dialog.dart index d87b43d5e..bb7beae82 100644 --- a/lib/widgets/dialog_widgets/two_step_dialog.dart +++ b/lib/widgets/dialog_widgets/two_step_dialog.dart @@ -22,13 +22,13 @@ import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/crypto_utils.dart'; -import 'package:privacyidea_authenticator/utils/view_utils.dart'; -import 'package:privacyidea_authenticator/widgets/dialog_widgets/default_dialog.dart'; +import '../../l10n/app_localizations.dart'; +import '../../utils/crypto_utils.dart'; import '../../utils/logger.dart'; import '../../utils/utils.dart'; +import '../../utils/view_utils.dart'; +import '../../widgets/dialog_widgets/default_dialog.dart'; import '../widget_keys.dart'; class GenerateTwoStepDialog extends StatelessWidget { diff --git a/lib/widgets/press_button.dart b/lib/widgets/press_button.dart index a03dd7c7f..fb2dcbc5f 100644 --- a/lib/widgets/press_button.dart +++ b/lib/widgets/press_button.dart @@ -32,11 +32,12 @@ class _PressButtonState extends State { } @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: isPressable ? press : null, - style: widget.style?.merge(Theme.of(context).elevatedButtonTheme.style), - child: widget.child, - ); - } + Widget build(BuildContext context) => Padding( + padding: Theme.of(context).elevatedButtonTheme.style?.padding?.resolve({}) ?? const EdgeInsets.all(0), + child: ElevatedButton( + onPressed: isPressable ? press : null, + style: widget.style?.merge(Theme.of(context).elevatedButtonTheme.style) ?? Theme.of(context).elevatedButtonTheme.style, + child: widget.child, + ), + ); } diff --git a/pubspec.yaml b/pubspec.yaml index 344e31285..6a20b5fd2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: flutter_local_notifications: ^17.0.0 home_widget: ^0.6.0 image: ^4.1.6 - json_annotation: ^4.8.1 + json_annotation: ^4.9.0 local_auth: ^2.1.6 local_auth_android: ^1.0.37 local_auth_darwin: ^1.2.2