From 08033487331861c84bdecd442bd8347b58038dca Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 18 Oct 2023 17:12:54 +0545 Subject: [PATCH 01/14] feat: OIDC4VCI screening developer option shown #1986 --- .../helper_functions/helper_functions.dart | 97 ++++++++++++++++--- lib/dashboard/dashboard.dart | 1 + lib/dashboard/json_viewer/json_viewer.dart | 1 + .../json_viewer/view/json_viewer_page.dart | 59 +++++++++++ lib/dashboard/qr_code/qr_code.dart | 1 + .../cubit/qr_code_scan_cubit.dart | 21 ++-- .../qr_code/widget/developer_mode_dialog.dart | 76 +++++++++++++++ lib/dashboard/qr_code/widget/widget.dart | 1 + lib/l10n/arb/app_en.arb | 6 +- lib/l10n/untranslated.json | 20 +++- lib/splash/bloclisteners/blocklisteners.dart | 96 +++++++++++++++++- 11 files changed, 343 insertions(+), 36 deletions(-) create mode 100644 lib/dashboard/json_viewer/json_viewer.dart create mode 100644 lib/dashboard/json_viewer/view/json_viewer_page.dart create mode 100644 lib/dashboard/qr_code/widget/developer_mode_dialog.dart create mode 100644 lib/dashboard/qr_code/widget/widget.dart diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 23c73aa13..33d30801b 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -500,7 +500,14 @@ bool isSIOPV2OROIDC4VPUrl(Uri uri) { return isOpenIdUrl || isAuthorizeEndPoint || isSiopv2Url; } -Future getOIDC4VCTypeForIssuance({ +Future< + ( + OIDC4VCType?, + Map?, + Map?, + dynamic, + String?, + )> getIssuanceData({ required String url, required DioClient client, }) async { @@ -509,12 +516,13 @@ Future getOIDC4VCTypeForIssuance({ final keys = []; uri.queryParameters.forEach((key, value) => keys.add(key)); + dynamic credentialOfferJson; String? issuer; if (keys.contains('credential_offer') || keys.contains('credential_offer_uri')) { /// issuance case 2 - final dynamic credentialOfferJson = await getCredentialOfferJson( + credentialOfferJson = await getCredentialOfferJson( scannedResponse: uri.toString(), dioClient: client, ); @@ -529,7 +537,7 @@ Future getOIDC4VCTypeForIssuance({ } if (issuer == null) { - return null; + return (null, null, null, null, null); } final openidConfigurationResponse = await getOpenIdConfig( @@ -542,6 +550,7 @@ Future getOIDC4VCTypeForIssuance({ List? subjectSyntaxTypesSupported; String? tokenEndpoint; + Map? authorizationServerConfiguration; if (openidConfigurationResponse .containsKey('subject_syntax_types_supported')) { @@ -555,23 +564,23 @@ Future getOIDC4VCTypeForIssuance({ } if (authorizationServer != null) { - final openidConfigurationResponseSecond = await getOpenIdConfig( + authorizationServerConfiguration = await getOpenIdConfig( baseUrl: authorizationServer.toString(), client: client.dio, ); if (subjectSyntaxTypesSupported == null && - openidConfigurationResponseSecond + authorizationServerConfiguration .containsKey('subject_syntax_types_supported')) { subjectSyntaxTypesSupported = - openidConfigurationResponseSecond['subject_syntax_types_supported'] + authorizationServerConfiguration['subject_syntax_types_supported'] as List; } if (tokenEndpoint == null && - openidConfigurationResponseSecond.containsKey('token_endpoint')) { + authorizationServerConfiguration.containsKey('token_endpoint')) { tokenEndpoint = - openidConfigurationResponseSecond['token_endpoint'].toString(); + authorizationServerConfiguration['token_endpoint'].toString(); } } @@ -634,19 +643,51 @@ Future getOIDC4VCTypeForIssuance({ if (oidc4vcType == OIDC4VCType.DEFAULT || oidc4vcType == OIDC4VCType.EBSIV3) { if (credSupported['trust_framework'] == null) { - return OIDC4VCType.DEFAULT; + return ( + OIDC4VCType.DEFAULT, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + issuer + ); } if (credSupported['trust_framework']['name'] == 'ebsi') { - return OIDC4VCType.EBSIV3; + return ( + OIDC4VCType.EBSIV3, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + issuer + ); } else { - return OIDC4VCType.DEFAULT; + return ( + OIDC4VCType.DEFAULT, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + issuer + ); } } - return oidc4vcType; + + return ( + oidc4vcType, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + issuer + ); } } - return null; + + return ( + null, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + issuer + ); } Future isEBSIV3ForVerifiers({ @@ -910,3 +951,33 @@ bool hasVPToken(String responseType) { bool hasIDTokenOrVPToken(String responseType) { return responseType.contains('id_token') || responseType.contains('vp_token'); } + +String getFormattedStringOIDC4VCI({ + OIDC4VCType? oidc4vcType, + Map? openidConfigurationResponse, + Map? authorizationServerConfiguration, + dynamic credentialOfferJson, +}) { + return ''' +SCHEME : ${oidc4vcType!.offerPrefix} +\n +CREDENTIAL OFFER : +${credentialOfferJson != null ? const JsonEncoder.withIndent(' ').convert(credentialOfferJson) : 'None'} +\n +ENDPOINTS : + authorization server endpoint : ${openidConfigurationResponse?['authorization_server'] ?? 'None'} + token endpoint : ${openidConfigurationResponse?['token_endpoint'] ?? authorizationServerConfiguration?['token_endpoint'] ?? 'None'} + credential endpoint : ${openidConfigurationResponse?['credential_endpoint'] ?? 'None'} + deferred endpoint : ${openidConfigurationResponse?['deferred_endpoint'] ?? 'None'} + batch endpoint : ${openidConfigurationResponse?['batch_endpoint'] ?? 'None'} +\n +CREDENTIAL SUPPORTED : +${openidConfigurationResponse?['credentials_supported'] != null ? const JsonEncoder.withIndent(' ').convert(openidConfigurationResponse?['credentials_supported']) : 'None'} +\n +AUTHORIZATION SERVER CONFIGURATION : +${authorizationServerConfiguration != null ? const JsonEncoder.withIndent(' ').convert(authorizationServerConfiguration) : 'None'} +\n +CRDENTIAL ISSUER CONFIGURATION : +${openidConfigurationResponse != null ? const JsonEncoder.withIndent(' ').convert(openidConfigurationResponse) : 'None'} +'''; +} diff --git a/lib/dashboard/dashboard.dart b/lib/dashboard/dashboard.dart index 837604caa..e89cf643f 100644 --- a/lib/dashboard/dashboard.dart +++ b/lib/dashboard/dashboard.dart @@ -6,6 +6,7 @@ export 'discover/discover.dart'; export 'drawer/drawer.dart'; export 'general_information/general_information.dart'; export 'home/home.dart'; +export 'json_viewer/json_viewer.dart'; export 'missing_creentials/missing_credentials.dart'; export 'mnemonic_verification/mnemonic_verification.dart'; export 'profile/profile.dart'; diff --git a/lib/dashboard/json_viewer/json_viewer.dart b/lib/dashboard/json_viewer/json_viewer.dart new file mode 100644 index 000000000..c590b3b38 --- /dev/null +++ b/lib/dashboard/json_viewer/json_viewer.dart @@ -0,0 +1 @@ +export 'view/json_viewer_page.dart'; diff --git a/lib/dashboard/json_viewer/view/json_viewer_page.dart b/lib/dashboard/json_viewer/view/json_viewer_page.dart new file mode 100644 index 000000000..0c85250d0 --- /dev/null +++ b/lib/dashboard/json_viewer/view/json_viewer_page.dart @@ -0,0 +1,59 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; + +class JsonViewerPage extends StatelessWidget { + const JsonViewerPage({ + super.key, + required this.title, + required this.data, + }); + + final String title; + final String data; + + static Route route({ + required String title, + required String data, + }) => + MaterialPageRoute( + builder: (_) => JsonViewerPage( + title: title, + data: data, + ), + settings: const RouteSettings(name: '/JsonViewerPage'), + ); + + @override + Widget build(BuildContext context) { + return JsonViewerView( + title: title, + data: data, + ); + } +} + +class JsonViewerView extends StatelessWidget { + const JsonViewerView({ + super.key, + required this.title, + required this.data, + }); + + final String title; + final String data; + + @override + Widget build(BuildContext context) { + return BasePage( + title: title, + titleAlignment: Alignment.topCenter, + scrollView: true, + titleLeading: const BackLeadingButton(), + padding: const EdgeInsets.symmetric(horizontal: 10), + body: Text( + data, + style: Theme.of(context).textTheme.bodyMedium, + ), + ); + } +} diff --git a/lib/dashboard/qr_code/qr_code.dart b/lib/dashboard/qr_code/qr_code.dart index 302813ff0..b358bf3b1 100644 --- a/lib/dashboard/qr_code/qr_code.dart +++ b/lib/dashboard/qr_code/qr_code.dart @@ -1,2 +1,3 @@ export 'qr_code_scan/qr_code_scan.dart'; export 'qr_display/qr_display.dart'; +export 'widget/widget.dart'; diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index ea99cbe26..dff359281 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -226,17 +226,14 @@ class QRCodeScanCubit extends Cubit { late final dynamic data; try { - if (isOIDC4VCIUrl(state.uri!)) { + if (isOIDC4VCIUrl(state.uri!) && oidcType != null) { /// issuer side (oidc4VCI) - - if (oidcType != null) { - await startOIDC4VCCredentialIssuance( - scannedResponse: state.uri.toString(), - isEBSIV3: oidcType == OIDC4VCType.EBSIV3, - qrCodeScanCubit: qrCodeScanCubit, - ); - return; - } + await startOIDC4VCCredentialIssuance( + scannedResponse: state.uri.toString(), + isEBSIV3: oidcType == OIDC4VCType.EBSIV3, + qrCodeScanCubit: qrCodeScanCubit, + ); + return; } if (isSIOPV2OROIDC4VPUrl(state.uri!)) { @@ -749,8 +746,8 @@ class QRCodeScanCubit extends Cubit { try { emit(state.loading()); - final OIDC4VCType? currentOIIDC4VCTypeForIssuance = - await getOIDC4VCTypeForIssuance( + final (OIDC4VCType? currentOIIDC4VCTypeForIssuance, _, _, _, _) = + await getIssuanceData( url: credentialModel.pendingInfo!.url, client: client, ); diff --git a/lib/dashboard/qr_code/widget/developer_mode_dialog.dart b/lib/dashboard/qr_code/widget/developer_mode_dialog.dart new file mode 100644 index 000000000..a6471ea19 --- /dev/null +++ b/lib/dashboard/qr_code/widget/developer_mode_dialog.dart @@ -0,0 +1,76 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; + +class DeveloperModeDialog extends StatelessWidget { + const DeveloperModeDialog({ + super.key, + required this.onDisplay, + required this.onDownload, + required this.onSkip, + }); + + final VoidCallback onDisplay; + final VoidCallback onDownload; + final VoidCallback onSkip; + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.primary; + final background = Theme.of(context).colorScheme.popupBackground; + final textColor = Theme.of(context).colorScheme.dialogText; + + final l10n = context.l10n; + return AlertDialog( + backgroundColor: background, + surfaceTintColor: Colors.transparent, + contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(25)), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + IconStrings.cardReceive, + width: 50, + height: 50, + color: textColor, + ), + const SizedBox(height: 15), + MyElevatedButton( + text: l10n.displayConfiguration, + verticalSpacing: 14, + backgroundColor: color, + borderRadius: Sizes.smallRadius, + fontSize: 15, + elevation: 0, + onPressed: onDisplay, + ), + const SizedBox(height: Sizes.spaceSmall), + MyElevatedButton( + text: l10n.downloadConfiguration, + verticalSpacing: 14, + backgroundColor: color, + borderRadius: Sizes.smallRadius, + fontSize: 15, + elevation: 0, + onPressed: onDownload, + ), + const SizedBox(height: Sizes.spaceSmall), + MyElevatedButton( + text: l10n.skip, + verticalSpacing: 14, + backgroundColor: color, + borderRadius: Sizes.smallRadius, + fontSize: 15, + elevation: 0, + onPressed: onSkip, + ), + const SizedBox(height: 15), + ], + ), + ); + } +} diff --git a/lib/dashboard/qr_code/widget/widget.dart b/lib/dashboard/qr_code/widget/widget.dart new file mode 100644 index 000000000..7a458d82d --- /dev/null +++ b/lib/dashboard/qr_code/widget/widget.dart @@ -0,0 +1 @@ +export 'developer_mode_dialog.dart'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7bdd24b3b..fd1dc6f35 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -989,7 +989,9 @@ "type": "Type", "credentialExpired": "Credential Expired", "incorrectSignature": "Incorrect Signature", - "revokedOrSuspendedCredential": "Revoked or Suspended Credential" - + "revokedOrSuspendedCredential": "Revoked or Suspended Credential", + "displayConfiguration": "Display configuration", + "downloadConfiguration": "Download configuration", + "successfullyDownloaded": "Successfully Downloaded" } diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 7fa79dc4d..2f7ee5a42 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -895,7 +895,10 @@ "type", "credentialExpired", "incorrectSignature", - "revokedOrSuspendedCredential" + "revokedOrSuspendedCredential", + "displayConfiguration", + "downloadConfiguration", + "successfullyDownloaded" ], "es": [ @@ -1794,7 +1797,10 @@ "type", "credentialExpired", "incorrectSignature", - "revokedOrSuspendedCredential" + "revokedOrSuspendedCredential", + "displayConfiguration", + "downloadConfiguration", + "successfullyDownloaded" ], "fr": [ @@ -1996,7 +2002,10 @@ "type", "credentialExpired", "incorrectSignature", - "revokedOrSuspendedCredential" + "revokedOrSuspendedCredential", + "displayConfiguration", + "downloadConfiguration", + "successfullyDownloaded" ], "it": [ @@ -2895,6 +2904,9 @@ "type", "credentialExpired", "incorrectSignature", - "revokedOrSuspendedCredential" + "revokedOrSuspendedCredential", + "displayConfiguration", + "downloadConfiguration", + "successfullyDownloaded" ] } diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index e87db3a83..b18882de7 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:altme/app/app.dart'; import 'package:altme/connection_bridge/connection_bridge.dart'; @@ -15,6 +16,7 @@ import 'package:altme/splash/splash.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:beacon_flutter/beacon_flutter.dart'; import 'package:dio/dio.dart'; +import 'package:file_saver/file_saver.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -236,11 +238,95 @@ final qrCodeBlocListener = BlocListener( .startsWith(Parameters.authorizeEndPoint) || state.uri.toString().startsWith(Parameters.oidc4vcUniversalLink); - final OIDC4VCType? oidc4vcTypeForIssuance = - await getOIDC4VCTypeForIssuance( - url: state.uri.toString(), - client: DioClient('', Dio()), - ); + OIDC4VCType? oidc4vcTypeForIssuance; + + if (isOpenIDUrl || isFromDeeplink) { + final ( + OIDC4VCType? oidc4vcType, + Map? openidConfigurationResponse, + Map? authorizationServerConfiguration, + dynamic credentialOfferJson, + String? issuer, + ) = await getIssuanceData( + url: state.uri.toString(), + client: DioClient('', Dio()), + ); + + oidc4vcTypeForIssuance = oidc4vcType; + + /// if dev mode is ON show some dialog to show data + if (profileCubit.state.model.isDeveloperMode && + oidc4vcTypeForIssuance != null) { + final formattedData = getFormattedStringOIDC4VCI( + oidc4vcType: oidc4vcTypeForIssuance, + authorizationServerConfiguration: + authorizationServerConfiguration, + credentialOfferJson: credentialOfferJson, + openidConfigurationResponse: openidConfigurationResponse, + ); + LoadingView().hide(); + final bool moveAhead = await showDialog( + context: context, + builder: (_) { + return DeveloperModeDialog( + onDisplay: () async { + Navigator.of(context).pop(false); + await Navigator.of(context).push( + JsonViewerPage.route( + title: l10n.displayConfiguration, + data: formattedData, + ), + ); + return; + }, + onDownload: () async { + Navigator.of(context).pop(false); + final isPermissionStatusGranted = + await getStoragePermission(); + if (!isPermissionStatusGranted) { + throw ResponseMessage( + message: ResponseString + .STORAGE_PERMISSION_DENIED_MESSAGE, + ); + } + + final dateTime = getDateTimeWithoutSpace(); + final fileName = 'oidc4vci-data-$dateTime'; + + final fileSaver = FileSaver.instance; + + final fileBytes = + Uint8List.fromList(utf8.encode(formattedData)); + + final filePath = await fileSaver.saveAs( + name: fileName, + bytes: fileBytes, + ext: 'txt', + mimeType: MimeType.text, + ); + if (filePath != null && filePath.isEmpty) { + // + } else { + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + showDialog: false, + stringMessage: l10n.successfullyDownloaded, + ), + ); + } + }, + onSkip: () { + Navigator.of(context).pop(true); + }, + ); + }, + ) ?? + true; + + if (!moveAhead) return; + } + } if (showPrompt) { if (isOpenIDUrl || isFromDeeplink) { From 678b094e3d8c51474b02fec23feda8551336c19a Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 18 Oct 2023 17:18:42 +0545 Subject: [PATCH 02/14] version update --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index cd155c02f..a0811e07f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2513,10 +2513,10 @@ packages: dependency: "direct main" description: name: walletconnect_flutter_v2 - sha256: "7429c8edfcf830e931ecd9112656a52c5f9c83cb0fcc00a1afb4f10d2be1b6e7" + sha256: "7619022ecea01adc771aaaefb4cace48bc3e8645d5e5962d4859347c6a0ea777" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fb835d701..c79a7627c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.21.13+298 +version: 1.21.14+299 environment: sdk: ">=3.1.0 <4.0.0" From 402feb09139612a93671114f3c7bd265a9cc4d41 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 19 Oct 2023 15:51:27 +0200 Subject: [PATCH 03/14] Don't use first image from camera: On android it is the last image from previous camera usage --- .../qr_code/qr_code_scan/view/qr_code_scan_page.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart index a62e5b73f..a0a4226bf 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart @@ -31,6 +31,7 @@ class _QrCodeScanPageState extends State { } bool isScanned = false; + bool isFirstImage = true; var _cameraLensDirection = CameraLensDirection.back; @override @@ -70,6 +71,10 @@ class _QrCodeScanPageState extends State { if (barcodes.isEmpty) { return; } + if (isFirstImage) { + isFirstImage = false; + return; + } if (isScanned) return; isScanned = true; From 65dba577a4011a135604087c93a9f0b8f7e6fd31 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 19 Oct 2023 15:54:30 +0200 Subject: [PATCH 04/14] update comment --- .../qr_code/qr_code_scan/view/qr_code_scan_page.dart | 4 +++- pubspec.lock | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart index a0a4226bf..7dac2636c 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart @@ -71,6 +71,9 @@ class _QrCodeScanPageState extends State { if (barcodes.isEmpty) { return; } + + /// We are skiping the first image because android device get + /// last image from previous camera usage if (isFirstImage) { isFirstImage = false; return; @@ -91,5 +94,4 @@ class _QrCodeScanPageState extends State { } } - //qr is scanning twice diff --git a/pubspec.lock b/pubspec.lock index a0811e07f..eb17e2bd9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -779,10 +779,10 @@ packages: dependency: "direct main" description: name: file_saver - sha256: "591d25e750e3a4b654f7b0293abc2ed857242f82ca7334051b2a8ceeb369dac8" + sha256: "8ffd91ae9f543c5ebbfec71a814ee5aa9e21176d31335133308abf63f4c42e8a" url: "https://pub.dev" source: hosted - version: "0.2.8" + version: "0.2.9" file_selector_linux: dependency: transitive description: From 9cdb24f7945fc4ef6f3a4dfbdb6284c11984c77d Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 12:34:48 +0545 Subject: [PATCH 05/14] refactor: OIDC4VP error management for presentation definition #2029 --- .../cubit/qr_code_scan_cubit.dart | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index dff359281..70d4b98bb 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -875,6 +875,37 @@ class QRCodeScanCubit extends Cubit { ); } + if (presentationDefinition.inputDescriptors.isEmpty) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': + 'The input_descriptors is required in the presentation_definition' + ' object', + }, + ); + } + + if (presentationDefinition.format == null) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'Presentation definition is invalid', + }, + ); + } + + for (final descriptor in presentationDefinition.inputDescriptors) { + if (descriptor.constraints == null) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'Presentation definition is invalid', + }, + ); + } + } + final CredentialManifest credentialManifest = CredentialManifest( 'id', IssuedBy('', ''), From b3a208bbd01a40dac7c529f5a19583285e8ceacb Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 14:46:48 +0545 Subject: [PATCH 06/14] feat: Handle error OIDC4VP #2027 --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index 70d4b98bb..6355c1b50 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -531,7 +531,8 @@ class QRCodeScanCubit extends Cubit { } if (registration != null) { - queryJson['registration'] = registration; + queryJson['registration'] = + registration is Map ? jsonEncode(registration) : registration; } final String queryString = Uri(queryParameters: queryJson).query; @@ -553,6 +554,13 @@ class QRCodeScanCubit extends Cubit { final keys = []; state.uri?.queryParameters.forEach((key, value) => keys.add(key)); + if (keys.contains('claims')) { + /// claims is old standard + throw ResponseMessage( + message: ResponseString.RESPONSE_STRING_thisRequestIsNotSupported, + ); + } + if (!keys.contains('response_type')) { throw ResponseMessage( data: { From 37d9a22bf704160d69b46082aa47574450144166 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 16:31:51 +0545 Subject: [PATCH 07/14] feat: Show dev dialog before throwing error --- .../helper_functions/helper_functions.dart | 95 ++++++++----------- .../cubit/qr_code_scan_cubit.dart | 21 +++- lib/splash/bloclisteners/blocklisteners.dart | 18 ++-- 3 files changed, 70 insertions(+), 64 deletions(-) diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 33d30801b..9c33e8f03 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -500,14 +500,8 @@ bool isSIOPV2OROIDC4VPUrl(Uri uri) { return isOpenIdUrl || isAuthorizeEndPoint || isSiopv2Url; } -Future< - ( - OIDC4VCType?, - Map?, - Map?, - dynamic, - String?, - )> getIssuanceData({ +Future<(Map?, Map?, dynamic)> + getIssuanceData({ required String url, required DioClient client, }) async { @@ -537,7 +531,7 @@ Future< } if (issuer == null) { - return (null, null, null, null, null); + return (null, null, null); } final openidConfigurationResponse = await getOpenIdConfig( @@ -545,12 +539,35 @@ Future< client: client.dio, ); + final authorizationServer = + openidConfigurationResponse['authorization_server']; + + Map? authorizationServerConfiguration; + + if (authorizationServer != null) { + authorizationServerConfiguration = await getOpenIdConfig( + baseUrl: authorizationServer.toString(), + client: client.dio, + ); + } + + return ( + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + ); +} + +Future getOIDC4VCTypeForIssuance({ + required String url, + required Map openidConfigurationResponse, + required Map? authorizationServerConfiguration, +}) async { final authorizationServer = openidConfigurationResponse['authorization_server']; List? subjectSyntaxTypesSupported; String? tokenEndpoint; - Map? authorizationServerConfiguration; if (openidConfigurationResponse .containsKey('subject_syntax_types_supported')) { @@ -563,12 +580,7 @@ Future< tokenEndpoint = openidConfigurationResponse['token_endpoint'].toString(); } - if (authorizationServer != null) { - authorizationServerConfiguration = await getOpenIdConfig( - baseUrl: authorizationServer.toString(), - client: client.dio, - ); - + if (authorizationServer != null && authorizationServerConfiguration != null) { if (subjectSyntaxTypesSupported == null && authorizationServerConfiguration .containsKey('subject_syntax_types_supported')) { @@ -643,51 +655,21 @@ Future< if (oidc4vcType == OIDC4VCType.DEFAULT || oidc4vcType == OIDC4VCType.EBSIV3) { if (credSupported['trust_framework'] == null) { - return ( - OIDC4VCType.DEFAULT, - openidConfigurationResponse, - authorizationServerConfiguration, - credentialOfferJson, - issuer - ); + return OIDC4VCType.DEFAULT; } if (credSupported['trust_framework']['name'] == 'ebsi') { - return ( - OIDC4VCType.EBSIV3, - openidConfigurationResponse, - authorizationServerConfiguration, - credentialOfferJson, - issuer - ); + return OIDC4VCType.EBSIV3; } else { - return ( - OIDC4VCType.DEFAULT, - openidConfigurationResponse, - authorizationServerConfiguration, - credentialOfferJson, - issuer - ); + return OIDC4VCType.DEFAULT; } } - return ( - oidc4vcType, - openidConfigurationResponse, - authorizationServerConfiguration, - credentialOfferJson, - issuer - ); + return oidc4vcType; } } - return ( - null, - openidConfigurationResponse, - authorizationServerConfiguration, - credentialOfferJson, - issuer - ); + return null; } Future isEBSIV3ForVerifiers({ @@ -953,13 +935,13 @@ bool hasIDTokenOrVPToken(String responseType) { } String getFormattedStringOIDC4VCI({ - OIDC4VCType? oidc4vcType, + required String url, Map? openidConfigurationResponse, Map? authorizationServerConfiguration, dynamic credentialOfferJson, }) { return ''' -SCHEME : ${oidc4vcType!.offerPrefix} +SCHEME : ${getSchemeFromUrl(url)} \n CREDENTIAL OFFER : ${credentialOfferJson != null ? const JsonEncoder.withIndent(' ').convert(credentialOfferJson) : 'None'} @@ -972,7 +954,7 @@ ENDPOINTS : batch endpoint : ${openidConfigurationResponse?['batch_endpoint'] ?? 'None'} \n CREDENTIAL SUPPORTED : -${openidConfigurationResponse?['credentials_supported'] != null ? const JsonEncoder.withIndent(' ').convert(openidConfigurationResponse?['credentials_supported']) : 'None'} +${openidConfigurationResponse?['credentials_supported'] != null ? const JsonEncoder.withIndent(' ').convert(openidConfigurationResponse!['credentials_supported']) : 'None'} \n AUTHORIZATION SERVER CONFIGURATION : ${authorizationServerConfiguration != null ? const JsonEncoder.withIndent(' ').convert(authorizationServerConfiguration) : 'None'} @@ -981,3 +963,8 @@ CRDENTIAL ISSUER CONFIGURATION : ${openidConfigurationResponse != null ? const JsonEncoder.withIndent(' ').convert(openidConfigurationResponse) : 'None'} '''; } + +String getSchemeFromUrl(String url) { + final parts = url.split(':'); + return parts.length > 1 ? '${parts[0]}://' : ''; +} diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index 6355c1b50..0e1936f4a 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -754,12 +754,27 @@ class QRCodeScanCubit extends Cubit { try { emit(state.loading()); - final (OIDC4VCType? currentOIIDC4VCTypeForIssuance, _, _, _, _) = - await getIssuanceData( - url: credentialModel.pendingInfo!.url, + final url = credentialModel.pendingInfo!.url; + + final ( + Map? openidConfigurationResponse, + Map? authorizationServerConfiguration, + _, + ) = await getIssuanceData( + url: url, client: client, ); + OIDC4VCType? currentOIIDC4VCTypeForIssuance; + + if (openidConfigurationResponse != null) { + currentOIIDC4VCTypeForIssuance = await getOIDC4VCTypeForIssuance( + url: url, + openidConfigurationResponse: openidConfigurationResponse, + authorizationServerConfiguration: authorizationServerConfiguration, + ); + } + if (currentOIIDC4VCTypeForIssuance != null) { await getAndAddDefferedCredential( credentialModel: credentialModel, diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index b18882de7..bdb3623a2 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -242,23 +242,18 @@ final qrCodeBlocListener = BlocListener( if (isOpenIDUrl || isFromDeeplink) { final ( - OIDC4VCType? oidc4vcType, Map? openidConfigurationResponse, Map? authorizationServerConfiguration, dynamic credentialOfferJson, - String? issuer, ) = await getIssuanceData( url: state.uri.toString(), client: DioClient('', Dio()), ); - oidc4vcTypeForIssuance = oidc4vcType; - /// if dev mode is ON show some dialog to show data - if (profileCubit.state.model.isDeveloperMode && - oidc4vcTypeForIssuance != null) { + if (profileCubit.state.model.isDeveloperMode) { final formattedData = getFormattedStringOIDC4VCI( - oidc4vcType: oidc4vcTypeForIssuance, + url: state.uri.toString(), authorizationServerConfiguration: authorizationServerConfiguration, credentialOfferJson: credentialOfferJson, @@ -326,6 +321,15 @@ final qrCodeBlocListener = BlocListener( if (!moveAhead) return; } + + if (openidConfigurationResponse != null) { + oidc4vcTypeForIssuance = await getOIDC4VCTypeForIssuance( + url: state.uri.toString(), + openidConfigurationResponse: openidConfigurationResponse, + authorizationServerConfiguration: + authorizationServerConfiguration, + ); + } } if (showPrompt) { From 1011f2e2665eb470832bed5d24f8714bfbd3e67f Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 17:14:21 +0545 Subject: [PATCH 08/14] feat: Move developer mode to main drawer #1987 --- .../drawer/src/view/drawer_page.dart | 22 +++++++++++++ .../src/widgets/drawer_category_item.dart | 13 +++++--- .../drawer/src/widgets/drawer_item.dart | 5 +-- .../view/oidc4vc_settings_menu.dart | 31 +++---------------- .../widget/security_level_widget.dart | 3 -- .../detail/view/credentials_details_page.dart | 4 +-- .../widgets/credential_active_status.dart | 6 ++-- lib/l10n/arb/app_en.arb | 4 +-- packages/oidc4vc/lib/src/pkce_dart.dart | 5 ++- 9 files changed, 50 insertions(+), 43 deletions(-) diff --git a/lib/dashboard/drawer/src/view/drawer_page.dart b/lib/dashboard/drawer/src/view/drawer_page.dart index 2ed248ac8..b7b3a6ca5 100644 --- a/lib/dashboard/drawer/src/view/drawer_page.dart +++ b/lib/dashboard/drawer/src/view/drawer_page.dart @@ -3,6 +3,7 @@ import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class DrawerPage extends StatelessWidget { const DrawerPage({super.key}); @@ -72,6 +73,27 @@ class DrawerView extends StatelessWidget { Navigator.of(context).push(SSIMenu.route()); }, ), + const SizedBox(height: Sizes.spaceSmall), + DrawerCategoryItem( + title: l10n.developerMode, + subTitle: l10n.developerModeSubtitle, + trailing: SizedBox( + height: 25, + child: BlocBuilder( + builder: (context, state) { + return Switch( + onChanged: (value) async { + await context + .read() + .setDeveloperModeStatus(enabled: value); + }, + value: state.model.isDeveloperMode, + activeColor: Theme.of(context).colorScheme.primary, + ); + }, + ), + ), + ), // const SizedBox(height: Sizes.spaceSmall), // DrawerCategoryItem( // title: l10n.checkLinkedinProfile, diff --git a/lib/dashboard/drawer/src/widgets/drawer_category_item.dart b/lib/dashboard/drawer/src/widgets/drawer_category_item.dart index fe60db665..fe5194471 100644 --- a/lib/dashboard/drawer/src/widgets/drawer_category_item.dart +++ b/lib/dashboard/drawer/src/widgets/drawer_category_item.dart @@ -8,11 +8,13 @@ class DrawerCategoryItem extends StatelessWidget { required this.title, required this.subTitle, this.onClick, + this.trailing, }); final String title; final String subTitle; final VoidCallback? onClick; + final Widget? trailing; @override Widget build(BuildContext context) { @@ -49,15 +51,16 @@ class DrawerCategoryItem extends StatelessWidget { ], ), ), - Container( - width: Sizes.icon3x, - alignment: Alignment.center, - child: Icon( + if (trailing != null) + trailing! + else ...[ + const SizedBox(width: 16), + Icon( Icons.chevron_right, size: Sizes.icon2x, color: Theme.of(context).colorScheme.unSelectedLabel, ), - ), + ], ], ), ), diff --git a/lib/dashboard/drawer/src/widgets/drawer_item.dart b/lib/dashboard/drawer/src/widgets/drawer_item.dart index 503334230..68c6a7043 100644 --- a/lib/dashboard/drawer/src/widgets/drawer_item.dart +++ b/lib/dashboard/drawer/src/widgets/drawer_item.dart @@ -38,6 +38,7 @@ class DrawerItem extends StatelessWidget { children: [ Expanded( child: Column( + mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( @@ -61,7 +62,7 @@ class DrawerItem extends StatelessWidget { : null, ), ), - const SizedBox(height: 20), + const SizedBox(height: 10), ], ], ), @@ -72,7 +73,7 @@ class DrawerItem extends StatelessWidget { const SizedBox(width: 16), Icon( Icons.chevron_right, - size: 26, + size: Sizes.icon2x, color: isDisabled ? Theme.of(context).colorScheme.lightGrey : Theme.of(context).colorScheme.unSelectedLabel, diff --git a/lib/dashboard/drawer/ssi/oidc4vc_settngs/view/oidc4vc_settings_menu.dart b/lib/dashboard/drawer/ssi/oidc4vc_settngs/view/oidc4vc_settings_menu.dart index 72534725f..9b1dadd60 100644 --- a/lib/dashboard/drawer/ssi/oidc4vc_settngs/view/oidc4vc_settings_menu.dart +++ b/lib/dashboard/drawer/ssi/oidc4vc_settngs/view/oidc4vc_settings_menu.dart @@ -2,7 +2,6 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; class Oidc4vcSettingMenu extends StatelessWidget { const Oidc4vcSettingMenu({super.key}); @@ -33,33 +32,13 @@ class Oidc4vcSettingMenuView extends StatelessWidget { titleAlignment: Alignment.topCenter, padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceSmall), titleLeading: const BackLeadingButton(), - body: Column( + body: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SecurityLevelWidget(), - const SixOrForUserPinWidget(), - const DidKeyTypeWidget(), - const SubjectSyntaxTypeWidget(), - DrawerItem2( - title: l10n.developerMode, - subtitle: l10n.developerModeSubtitle, - trailing: SizedBox( - height: 25, - child: BlocBuilder( - builder: (context, state) { - return Switch( - onChanged: (value) async { - await context - .read() - .setDeveloperModeStatus(enabled: value); - }, - value: state.model.isDeveloperMode, - activeColor: Theme.of(context).colorScheme.primary, - ); - }, - ), - ), - ), + SecurityLevelWidget(), + SixOrForUserPinWidget(), + DidKeyTypeWidget(), + SubjectSyntaxTypeWidget(), ], ), ); diff --git a/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/security_level_widget.dart b/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/security_level_widget.dart index d96efe0a1..fd3a04654 100644 --- a/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/security_level_widget.dart +++ b/lib/dashboard/drawer/ssi/oidc4vc_settngs/widget/security_level_widget.dart @@ -1,8 +1,5 @@ -import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; -import 'package:altme/dashboard/profile/profile.dart'; import 'package:altme/l10n/l10n.dart'; -import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart b/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart index 6e7c91851..8503d674c 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart @@ -418,8 +418,8 @@ class _CredentialsDetailsViewState extends State { } getLogger( - 'CredentialDetailsPage - shared date') - .i(data); + 'CredentialDetailsPage - shared date', + ).i(data); final box = context.findRenderObject() as RenderBox?; diff --git a/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart b/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart index cebf329e6..8af323245 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart @@ -32,7 +32,8 @@ class CredentialActiveStatus extends StatelessWidget { .textTheme .credentialFieldTitle .copyWith( - color: Theme.of(context).colorScheme.titleColor), + color: Theme.of(context).colorScheme.titleColor, + ), ), TextSpan( text: credentialStatus?.message(context) ?? '', @@ -40,7 +41,8 @@ class CredentialActiveStatus extends StatelessWidget { .textTheme .credentialFieldDescription .copyWith( - color: Theme.of(context).colorScheme.valueColor), + color: Theme.of(context).colorScheme.valueColor, + ), ), ], ), diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index fd1dc6f35..dd24e8342 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -938,8 +938,8 @@ "pleaseInsertTheSecredCodeReceived": "Please insert the secret code received.", "verifyIssuerWebsiteIdentity": "Verify issuer website identity", "verifyIssuerWebsiteIdentitySubtitle": "Default: Off\nEnable to verify website identity before access.", - "developerMode": "Developer mode", - "developerModeSubtitle": "Default: Off\nEnable to access error description and error links.", + "developerMode": "Developer Mode", + "developerModeSubtitle": "Enable developer mode to access advanced debugging tools", "confirmVerifierAccess": "Confirm verifier access", "confirmVerifierAccessSubtitle": "Default: On\nDisable to skip confirmation when you share your verifiable credentials.", "secureAuthenticationWithPINCode": "Secure Authentication with PIN Code", diff --git a/packages/oidc4vc/lib/src/pkce_dart.dart b/packages/oidc4vc/lib/src/pkce_dart.dart index bb794c55d..554dd10b2 100644 --- a/packages/oidc4vc/lib/src/pkce_dart.dart +++ b/packages/oidc4vc/lib/src/pkce_dart.dart @@ -16,7 +16,10 @@ class PkcePair { factory PkcePair.generate({int length = 32}) { if (length < 32 || length > 96) { throw ArgumentError.value( - length, 'length', 'The length must be between 32 and 96, inclusive.'); + length, + 'length', + 'The length must be between 32 and 96, inclusive.', + ); } final random = Random.secure(); From ee653309e603b558bd7a652cbbf756835c0ec297 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 19:07:28 +0545 Subject: [PATCH 09/14] feat: Developer mode for OIDC4VP SIOPV2 #1999 --- .../helper_functions/helper_functions.dart | 128 +++++++++++++++--- .../cubit/qr_code_scan_cubit.dart | 69 ++++------ lib/splash/bloclisteners/blocklisteners.dart | 88 ++++++++++-- 3 files changed, 212 insertions(+), 73 deletions(-) diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 9c33e8f03..49edf1d1f 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -500,7 +500,7 @@ bool isSIOPV2OROIDC4VPUrl(Uri uri) { return isOpenIdUrl || isAuthorizeEndPoint || isSiopv2Url; } -Future<(Map?, Map?, dynamic)> +Future<(OIDC4VCType?, Map?, Map?, dynamic)> getIssuanceData({ required String url, required DioClient client, @@ -531,7 +531,7 @@ Future<(Map?, Map?, dynamic)> } if (issuer == null) { - return (null, null, null); + return (null, null, null, null); } final openidConfigurationResponse = await getOpenIdConfig( @@ -551,14 +551,57 @@ Future<(Map?, Map?, dynamic)> ); } + final credentialsSupported = + openidConfigurationResponse['credentials_supported'] as List; + + final credSupported = credentialsSupported[0] as Map; + for (final oidc4vcType in OIDC4VCType.values) { + if (oidc4vcType.isEnabled && url.startsWith(oidc4vcType.offerPrefix)) { + if (oidc4vcType == OIDC4VCType.DEFAULT || + oidc4vcType == OIDC4VCType.EBSIV3) { + if (credSupported['trust_framework'] == null) { + return ( + OIDC4VCType.DEFAULT, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + ); + } + + if (credSupported['trust_framework']['name'] == 'ebsi') { + return ( + OIDC4VCType.EBSIV3, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + ); + } else { + return ( + OIDC4VCType.DEFAULT, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + ); + } + } + return ( + oidc4vcType, + openidConfigurationResponse, + authorizationServerConfiguration, + credentialOfferJson, + ); + } + } + return ( + null, openidConfigurationResponse, authorizationServerConfiguration, credentialOfferJson, ); } -Future getOIDC4VCTypeForIssuance({ +Future handleErrorForOID4VCI({ required String url, required Map openidConfigurationResponse, required Map? authorizationServerConfiguration, @@ -645,31 +688,40 @@ Future getOIDC4VCTypeForIssuance({ }, ); } +} - final credentialsSupported = - openidConfigurationResponse['credentials_supported'] as List; +Future?> getPresentationDefinition({ + required Uri uri, + required DioClient client, +}) async { + try { + final keys = []; + uri.queryParameters.forEach((key, value) => keys.add(key)); - final credSupported = credentialsSupported[0] as Map; - for (final oidc4vcType in OIDC4VCType.values) { - if (oidc4vcType.isEnabled && url.startsWith(oidc4vcType.offerPrefix)) { - if (oidc4vcType == OIDC4VCType.DEFAULT || - oidc4vcType == OIDC4VCType.EBSIV3) { - if (credSupported['trust_framework'] == null) { - return OIDC4VCType.DEFAULT; - } + if (keys.contains('presentation_definition')) { + final String presentationDefinitionValue = + uri.queryParameters['presentation_definition'] ?? ''; - if (credSupported['trust_framework']['name'] == 'ebsi') { - return OIDC4VCType.EBSIV3; - } else { - return OIDC4VCType.DEFAULT; - } - } + final json = jsonDecode(presentationDefinitionValue.replaceAll("'", '"')) + as Map; + + return json; + } else if (keys.contains('presentation_definition_uri')) { + final presentationDefinitionUri = + uri.queryParameters['presentation_definition_uri'].toString(); + final dynamic response = await client.get(presentationDefinitionUri); - return oidc4vcType; + final Map data = response == String + ? jsonDecode(response.toString()) as Map + : response as Map; + + return data; + } else { + return null; } + } catch (e) { + return null; } - - return null; } Future isEBSIV3ForVerifiers({ @@ -964,7 +1016,39 @@ ${openidConfigurationResponse != null ? const JsonEncoder.withIndent(' ').conve '''; } +String getFormattedStringOIDC4VPSIOPV2({ + required String url, + required Map? presentationDefinition, +}) { + return ''' +SCHEME : ${getSchemeFromUrl(url)} +\n +PRESENTATION DEFINITION : +${presentationDefinition != null ? const JsonEncoder.withIndent(' ').convert(presentationDefinition) : 'None'} +'''; +} + String getSchemeFromUrl(String url) { final parts = url.split(':'); return parts.length > 1 ? '${parts[0]}://' : ''; } + +Future fetchRequestUriPayload({ + required String url, + required DioClient client, +}) async { + final log = getLogger('QRCodeScanCubit - fetchRequestUriPayload'); + late final dynamic data; + + try { + final dynamic response = await client.get(url); + data = response.toString(); + } catch (e, s) { + log.e( + 'An error occurred while connecting to the server.', + error: e, + stackTrace: s, + ); + } + return data; +} diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index 0e1936f4a..70c04acf9 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -756,7 +756,10 @@ class QRCodeScanCubit extends Cubit { final url = credentialModel.pendingInfo!.url; + OIDC4VCType? currentOIIDC4VCTypeForIssuance; + final ( + OIDC4VCType? oidc4vcType, Map? openidConfigurationResponse, Map? authorizationServerConfiguration, _, @@ -765,10 +768,10 @@ class QRCodeScanCubit extends Cubit { client: client, ); - OIDC4VCType? currentOIIDC4VCTypeForIssuance; + currentOIIDC4VCTypeForIssuance = oidc4vcType; if (openidConfigurationResponse != null) { - currentOIIDC4VCTypeForIssuance = await getOIDC4VCTypeForIssuance( + await handleErrorForOID4VCI( url: url, openidConfigurationResponse: openidConfigurationResponse, authorizationServerConfiguration: authorizationServerConfiguration, @@ -857,26 +860,8 @@ class QRCodeScanCubit extends Cubit { } Future launchOIDC4VPAndSIOPV2Flow(List keys) async { - late PresentationDefinition presentationDefinition; - if (keys.contains('presentation_definition')) { - final String presentationDefinitionValue = - state.uri?.queryParameters['presentation_definition'] ?? ''; - - final json = jsonDecode(presentationDefinitionValue.replaceAll("'", '"')) - as Map; - - presentationDefinition = PresentationDefinition.fromJson(json); - } else if (keys.contains('presentation_definition_uri')) { - final presentationDefinitionUri = - state.uri!.queryParameters['presentation_definition_uri'].toString(); - final dynamic response = await client.get(presentationDefinitionUri); - - final Map data = response == String - ? jsonDecode(response.toString()) as Map - : response as Map; - - presentationDefinition = PresentationDefinition.fromJson(data); - } else { + if (!keys.contains('presentation_definition') && + !keys.contains('presentation_definition_uri')) { throw ResponseMessage( data: { 'error': 'invalid_request', @@ -887,6 +872,24 @@ class QRCodeScanCubit extends Cubit { ); } + final Map? presentationDefinitionData = + await getPresentationDefinition( + client: client, + uri: state.uri!, + ); + + if (presentationDefinitionData == null) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'Presentation definition is invalid', + }, + ); + } + + final PresentationDefinition presentationDefinition = + PresentationDefinition.fromJson(presentationDefinitionData); + if (presentationDefinition.inputDescriptors.isEmpty) { throw ResponseMessage( data: { @@ -982,7 +985,10 @@ class QRCodeScanCubit extends Cubit { final String? request = state.uri?.queryParameters['request']; if (requestUri != null) { - encodedData = await fetchRequestUriPayload(url: requestUri); + encodedData = await fetchRequestUriPayload( + url: requestUri, + client: requestClient, + ); } else { encodedData = request; } @@ -1185,23 +1191,6 @@ class QRCodeScanCubit extends Cubit { ); } - Future fetchRequestUriPayload({required String url}) async { - final log = getLogger('QRCodeScanCubit - fetchRequestUriPayload'); - late final dynamic data; - - try { - final dynamic response = await requestClient.get(url); - data = response.toString(); - } catch (e, s) { - log.e( - 'An error occurred while connecting to the server.', - error: e, - stackTrace: s, - ); - } - return data; - } - Future authorizedFlowCompletion(Uri uri) async { try { final error = uri.queryParameters['error']; diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index bdb3623a2..aec367545 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -20,6 +20,7 @@ import 'package:file_saver/file_saver.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:jwt_decode/jwt_decode.dart'; import 'package:polygonid/polygonid.dart'; final splashBlocListener = BlocListener( @@ -208,6 +209,7 @@ final qrCodeBlocListener = BlocListener( final log = getLogger('qrCodeBlocListener'); final l10n = context.l10n; + final client = DioClient('', Dio()); if (state.status == QrScanStatus.loading) { LoadingView().show(context: context); @@ -242,23 +244,86 @@ final qrCodeBlocListener = BlocListener( if (isOpenIDUrl || isFromDeeplink) { final ( + OIDC4VCType? oidc4vcType, Map? openidConfigurationResponse, Map? authorizationServerConfiguration, dynamic credentialOfferJson, ) = await getIssuanceData( url: state.uri.toString(), - client: DioClient('', Dio()), + client: client, ); + oidc4vcTypeForIssuance = oidc4vcType; + /// if dev mode is ON show some dialog to show data if (profileCubit.state.model.isDeveloperMode) { - final formattedData = getFormattedStringOIDC4VCI( - url: state.uri.toString(), - authorizationServerConfiguration: - authorizationServerConfiguration, - credentialOfferJson: credentialOfferJson, - openidConfigurationResponse: openidConfigurationResponse, - ); + late String formattedData; + if (oidc4vcTypeForIssuance != null) { + /// issuance case + formattedData = getFormattedStringOIDC4VCI( + url: state.uri.toString(), + authorizationServerConfiguration: + authorizationServerConfiguration, + credentialOfferJson: credentialOfferJson, + openidConfigurationResponse: openidConfigurationResponse, + ); + } else { + var url = state.uri!.toString(); + + /// verification case + final String? requestUri = + state.uri!.queryParameters['request_uri']; + final String? request = state.uri!.queryParameters['request']; + + if (requestUri != null || request != null) { + late dynamic encodedData; + if (requestUri != null) { + encodedData = await fetchRequestUriPayload( + url: requestUri, + client: client, + ); + } else { + encodedData = request; + } + final Map response = decodePayload( + jwtDecode: JWTDecode(), + token: encodedData as String, + ); + + final presentationDefinition = + response['presentation_definition']; + final presentationDefinitionUri = + response['presentation_definition_uri']; + + final queryJson = {}; + + if (presentationDefinition != null) { + queryJson['presentation_definition'] = + jsonEncode(presentationDefinition).replaceAll('"', "'"); + } + + if (presentationDefinitionUri != null) { + queryJson['presentation_definition_uri'] = + presentationDefinitionUri; + } + + final String queryString = + Uri(queryParameters: queryJson).query; + + url = '${state.uri}}&$queryString'; + } + + final Map? presentationDefinitionData = + await getPresentationDefinition( + client: client, + uri: Uri.parse(url), + ); + formattedData = getFormattedStringOIDC4VPSIOPV2( + url: state.uri.toString(), + presentationDefinition: presentationDefinitionData, + ); + } + LoadingView().hide(); final bool moveAhead = await showDialog( context: context, @@ -318,12 +383,11 @@ final qrCodeBlocListener = BlocListener( }, ) ?? true; - if (!moveAhead) return; } if (openidConfigurationResponse != null) { - oidc4vcTypeForIssuance = await getOIDC4VCTypeForIssuance( + await handleErrorForOID4VCI( url: state.uri.toString(), openidConfigurationResponse: openidConfigurationResponse, authorizationServerConfiguration: @@ -357,7 +421,9 @@ final qrCodeBlocListener = BlocListener( if (isOpenIDUrl) { subtitle = await getHost( - uri: state.uri!, client: DioClient('', Dio())); + uri: state.uri!, + client: client, + ); } LoadingView().hide(); From ed5635721906b1b89090fb8bdc63917230a33bc2 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 19:36:31 +0545 Subject: [PATCH 10/14] feat: Time increased to go back properly --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index 70c04acf9..331849f6c 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -76,7 +76,7 @@ class QRCodeScanCubit extends Cubit { Future process({required String? scannedResponse}) async { log.i('processing scanned qr code - $scannedResponse'); goBack(); - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 1000)); emit(state.loading(isScan: true)); try { final isInternetAvailable = await isConnected(); From 0f3764136519b0139d3c9d70a9c8427aba507212 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 19:40:46 +0545 Subject: [PATCH 11/14] version update --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c79a7627c..11ac77358 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.21.14+299 +version: 1.21.15+300 environment: sdk: ">=3.1.0 <4.0.0" From 52670f01612fb883403bc42b2f91d5392711b578 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 19 Oct 2023 19:51:32 +0545 Subject: [PATCH 12/14] update pubspec.lock --- pubspec.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index eb17e2bd9..77be2ed70 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -981,10 +981,10 @@ packages: dependency: "direct main" description: name: flutter_olm - sha256: fef0c9476d02c0df25ef0a66680bc23ac529a36b4911505910bcd8711b449c81 + sha256: ff50839d83066495f79f2abdb9769984f9c2040bc9c55c65e2b5c724cbe732a4 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" flutter_openssl_crypto: dependency: "direct main" description: From f9a99baf468db8d11c9ba1bd8686cac51ac40d39 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 19 Oct 2023 17:06:24 +0200 Subject: [PATCH 13/14] Ensure profileCubit is saved in secure storage at load --- lib/dashboard/profile/cubit/profile_cubit.dart | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/dashboard/profile/cubit/profile_cubit.dart b/lib/dashboard/profile/cubit/profile_cubit.dart index 43b3fc134..0f3624673 100644 --- a/lib/dashboard/profile/cubit/profile_cubit.dart +++ b/lib/dashboard/profile/cubit/profile_cubit.dart @@ -154,13 +154,7 @@ class ProfileCubit extends Cubit { enable4DigitPINCode: enable4DigitPINCode, enableJWKThumbprint: enableJWKThumbprint, ); - - emit( - state.copyWith( - model: profileModel, - status: AppStatus.success, - ), - ); + await update(profileModel); } catch (e, s) { log.e( 'something went wrong', From 7e0fabf2c29db0b87e294ca5e939d790db073e90 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 19 Oct 2023 17:06:56 +0200 Subject: [PATCH 14/14] compileSdkVersion 34 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 45df369ef..3a8c84b34 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -32,7 +32,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 33 + compileSdkVersion 34 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8