diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index c101b9fb8..adeeb9e01 100644 --- a/lib/app/shared/constants/secure_storage_keys.dart +++ b/lib/app/shared/constants/secure_storage_keys.dart @@ -34,6 +34,7 @@ class SecureStorageKeys { static const String enableJWKThumbprint = 'enableJWKThumbprint'; static const String enableCryptographicHolderBinding = 'enableCryptographicHolderBinding'; + static const String enableScopeParameter = 'enableScopeParameter'; static const String pinCode = 'pinCode'; static const String data = 'data'; diff --git a/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/view/oidc4vc_settings_menu.dart b/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/view/oidc4vc_settings_menu.dart index 2e8cad757..197300261 100644 --- a/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/view/oidc4vc_settings_menu.dart +++ b/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/view/oidc4vc_settings_menu.dart @@ -42,6 +42,7 @@ class Oidc4vcSettingMenuView extends StatelessWidget { const DidKeyTypeWidget(), const SubjectSyntaxTypeWidget(), const CryptographicHolderBindingWidget(), + const ScopeParameterWidget(), DrawerItem( title: l10n.clientMetadata, onTap: () { diff --git a/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/widget/scope_parameter.dart b/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/widget/scope_parameter.dart new file mode 100644 index 000000000..244bb6ff3 --- /dev/null +++ b/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/widget/scope_parameter.dart @@ -0,0 +1,70 @@ +import 'package:altme/app/app.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'; + +class ScopeParameterWidget extends StatelessWidget { + const ScopeParameterWidget({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + padding: const EdgeInsets.all(Sizes.spaceSmall), + margin: const EdgeInsets.all(Sizes.spaceXSmall), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.drawerSurface, + borderRadius: const BorderRadius.all( + Radius.circular(Sizes.largeRadius), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.scopeParameters, + style: Theme.of(context).textTheme.drawerItemTitle, + ), + const SizedBox(height: 10), + Text( + l10n.scopeParametersSubtitle, + style: Theme.of(context).textTheme.drawerItemSubtitle, + ), + ], + ), + ), + const SizedBox(height: 10), + Switch( + onChanged: (value) async { + await context + .read() + .updateScopeParameterStatus( + enabled: value, + ); + }, + value: state.model.enableScopeParameter, + activeColor: Theme.of(context).colorScheme.primary, + ), + ], + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/widget/widget.dart b/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/widget/widget.dart index 47268c067..7a9ba4dee 100644 --- a/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/widget/widget.dart +++ b/lib/dashboard/drawer/ssi/advanced_settings2/oidc4vc_settngs/widget/widget.dart @@ -1,5 +1,6 @@ export 'cryptograhic_holder_binding.dart'; export 'did_key_type_widget.dart'; +export 'scope_parameter.dart'; export 'security_level_widget.dart'; export 'six_or_four_pin_widget.dart'; export 'subject_syntax_type_widget.dart'; diff --git a/lib/dashboard/profile/cubit/profile_cubit.dart b/lib/dashboard/profile/cubit/profile_cubit.dart index 248721faa..0303cf950 100644 --- a/lib/dashboard/profile/cubit/profile_cubit.dart +++ b/lib/dashboard/profile/cubit/profile_cubit.dart @@ -127,6 +127,12 @@ class ProfileCubit extends Cubit { enableCryptographicHolderBindingValue == null || enableCryptographicHolderBindingValue == 'true'; + final enableScopeParameterValue = await secureStorageProvider + .get(SecureStorageKeys.enableScopeParameter); + + final enableScopeParameter = enableScopeParameterValue != null && + enableScopeParameterValue == 'true'; + final userPINCodeForAuthenticationValue = await secureStorageProvider .get(SecureStorageKeys.userPINCodeForAuthentication); final userPINCodeForAuthentication = @@ -161,6 +167,7 @@ class ProfileCubit extends Cubit { enable4DigitPINCode: enable4DigitPINCode, enableJWKThumbprint: enableJWKThumbprint, enableCryptographicHolderBinding: enableCryptographicHolderBinding, + enableScopeParameter: enableScopeParameter, ); await update(profileModel); } catch (e, s) { @@ -275,6 +282,11 @@ class ProfileCubit extends Cubit { profileModel.enableCryptographicHolderBinding.toString(), ); + await secureStorageProvider.set( + SecureStorageKeys.enableScopeParameter, + profileModel.enableScopeParameter.toString(), + ); + emit( state.copyWith( model: profileModel, @@ -363,6 +375,11 @@ class ProfileCubit extends Cubit { await update(profileModel); } + Future updateScopeParameterStatus({bool enabled = false}) async { + final profileModel = state.model.copyWith(enableScopeParameter: enabled); + await update(profileModel); + } + Future enable4DigitPINCode({bool enabled = false}) async { final profileModel = state.model.copyWith(enable4DigitPINCode: enabled); await update(profileModel); diff --git a/lib/dashboard/profile/models/profile.dart b/lib/dashboard/profile/models/profile.dart index 5d6b18d6c..d0f6d5d81 100644 --- a/lib/dashboard/profile/models/profile.dart +++ b/lib/dashboard/profile/models/profile.dart @@ -22,6 +22,7 @@ class ProfileModel extends Equatable { required this.userPINCodeForAuthentication, required this.enableJWKThumbprint, required this.enableCryptographicHolderBinding, + required this.enableScopeParameter, this.companyName = '', this.companyWebsite = '', this.jobTitle = '', @@ -55,6 +56,7 @@ class ProfileModel extends Equatable { enable4DigitPINCode: false, enableJWKThumbprint: false, enableCryptographicHolderBinding: true, + enableScopeParameter: false, ); final String firstName; @@ -79,6 +81,7 @@ class ProfileModel extends Equatable { final bool enable4DigitPINCode; final bool enableJWKThumbprint; final bool enableCryptographicHolderBinding; + final bool enableScopeParameter; @override List get props => [ @@ -103,6 +106,7 @@ class ProfileModel extends Equatable { enable4DigitPINCode, enableJWKThumbprint, enableCryptographicHolderBinding, + enableScopeParameter, ]; Map toJson() => _$ProfileModelToJson(this); @@ -129,6 +133,7 @@ class ProfileModel extends Equatable { bool? enable4DigitPINCode, bool? enableJWKThumbprint, bool? enableCryptographicHolderBinding, + bool? enableScopeParameter, }) { return ProfileModel( firstName: firstName ?? this.firstName, @@ -147,6 +152,7 @@ class ProfileModel extends Equatable { enableJWKThumbprint: enableJWKThumbprint ?? this.enableJWKThumbprint, enableCryptographicHolderBinding: enableCryptographicHolderBinding ?? this.enableCryptographicHolderBinding, + enableScopeParameter: enableScopeParameter ?? this.enableScopeParameter, userConsentForIssuerAccess: userConsentForIssuerAccess ?? this.userConsentForIssuerAccess, userConsentForVerifierAccess: 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 993e81b41..32ed7d35c 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 @@ -1044,6 +1044,9 @@ class QRCodeScanCubit extends Cubit { required dynamic credentialOfferJson, }) async { try { + final enableScopeParameterValue = + profileCubit.state.model.enableScopeParameter; + ; if (preAuthorizedCode != null) { await addCredentialsInLoop( selectedCredentials: selectedCredentials, @@ -1065,6 +1068,7 @@ class QRCodeScanCubit extends Cubit { secureStorageProvider: secureStorageProvider, credentialOfferJson: credentialOfferJson, issuer: issuer, + credentailsInScopeParameter: enableScopeParameterValue, ); goBack(); } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 56554774e..5402474e7 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -986,6 +986,8 @@ "enableToUseTheJWKThumprintOfTheKey": "Default: DID\nEnable to use the JWK thumprint of the key", "cryptographicHolderBinding": "Cryptographic holder binding", "cryptographicHolderBindingSubtitle": "Default : On\nDisable to accept Bearer credentials as tickets with low assurance.", + "scopeParameters": "Scope Parameters", + "scopeParametersSubtitle": "Default : Off\nEnable to force wallet to use scope instead of authorization_details.", "theServiceIsNotAvailable": "The service is not available", "download": "Download", "issuerDID": "Issuer DID", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 54e630998..a941e25ac 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -891,6 +891,8 @@ "enableToUseTheJWKThumprintOfTheKey", "cryptographicHolderBinding", "cryptographicHolderBindingSubtitle", + "scopeParameters", + "scopeParametersSubtitle", "theServiceIsNotAvailable", "download", "issuerDID", @@ -1798,6 +1800,8 @@ "enableToUseTheJWKThumprintOfTheKey", "cryptographicHolderBinding", "cryptographicHolderBindingSubtitle", + "scopeParameters", + "scopeParametersSubtitle", "theServiceIsNotAvailable", "download", "issuerDID", @@ -2008,6 +2012,8 @@ "enableToUseTheJWKThumprintOfTheKey", "cryptographicHolderBinding", "cryptographicHolderBindingSubtitle", + "scopeParameters", + "scopeParametersSubtitle", "theServiceIsNotAvailable", "download", "issuerDID", @@ -2915,6 +2921,8 @@ "enableToUseTheJWKThumprintOfTheKey", "cryptographicHolderBinding", "cryptographicHolderBindingSubtitle", + "scopeParameters", + "scopeParametersSubtitle", "theServiceIsNotAvailable", "download", "issuerDID", diff --git a/lib/oidc4vc/get_authorization_uri_for_issuer.dart b/lib/oidc4vc/get_authorization_uri_for_issuer.dart index 7c8cf8ec3..638eb8054 100644 --- a/lib/oidc4vc/get_authorization_uri_for_issuer.dart +++ b/lib/oidc4vc/get_authorization_uri_for_issuer.dart @@ -16,6 +16,7 @@ Future getAuthorizationUriForIssuer({ required SecureStorageProvider secureStorageProvider, required String issuer, required dynamic credentialOfferJson, + required bool credentailsInScopeParameter, }) async { final privateKey = await fetchPrivateKey( isEBSIV3: isEBSIV3, @@ -62,6 +63,7 @@ Future getAuthorizationUriForIssuer({ pkcePair: pkcePair, state: jwtToken, authorizationEndPoint: Parameters.authorizeEndPoint, + credentailsInScopeParameter: credentailsInScopeParameter, ); await LaunchUrl.launchUri(oidc4vcAuthenticationUri); diff --git a/packages/oidc4vc/lib/src/helper_function.dart b/packages/oidc4vc/lib/src/helper_function.dart new file mode 100644 index 000000000..c246deb26 --- /dev/null +++ b/packages/oidc4vc/lib/src/helper_function.dart @@ -0,0 +1,13 @@ +String listToString(List inputList) { + final resultList = []; + + for (var i = 0; i < inputList.length; i++) { + resultList.add(inputList[i]); + + if (i < inputList.length - 1) { + resultList.add(' '); + } + } + + return resultList.join(); +} diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index ed99514b1..261547cdd 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:hex/hex.dart'; import 'package:jose/jose.dart'; import 'package:json_path/json_path.dart'; +import 'package:oidc4vc/src/helper_function.dart'; import 'package:oidc4vc/src/issuer_token_parameters.dart'; import 'package:oidc4vc/src/pkce_dart.dart'; import 'package:oidc4vc/src/token_parameters.dart'; @@ -95,6 +96,7 @@ class OIDC4VC { required PkcePair pkcePair, required String state, required String authorizationEndPoint, + required bool credentailsInScopeParameter, }) async { try { final openidConfigurationResponse = await getOpenIdConfig(issuer); @@ -113,6 +115,7 @@ class OIDC4VC { pkcePair: pkcePair, state: state, authorizationEndPoint: authorizationEndPoint, + credentailsInScopeParameter: credentailsInScopeParameter, ); final url = Uri.parse(authorizationEndpoint); @@ -136,10 +139,12 @@ class OIDC4VC { required String authorizationEndPoint, required PkcePair pkcePair, required String state, + required bool credentailsInScopeParameter, }) { //https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-successful-authorization-re final authorizationDetails = []; + final credentials = []; for (final credential in selectedCredentials) { late Map data; @@ -172,6 +177,8 @@ class OIDC4VC { 'format': credentailData['format'], 'types': credentailData['types'], }; + + credentials.addAll(credentailData['types'] as List); } else if (credential is Map) { data = { 'type': 'openid_credential', @@ -179,6 +186,7 @@ class OIDC4VC { 'format': credential['format'], 'types': credential['types'], }; + credentials.addAll(credential['types'] as List); } else { throw Exception(); } @@ -192,13 +200,11 @@ class OIDC4VC { 'response_type': 'code', 'client_id': clientId, 'redirect_uri': redirectUri, - 'scope': 'openid', 'issuer_state': issuerState, 'state': state, 'nonce': nonce, 'code_challenge': codeChallenge, 'code_challenge_method': 'S256', - 'authorization_details': jsonEncode(authorizationDetails), 'client_metadata': jsonEncode({ 'authorization_endpoint': authorizationEndPoint, 'scopes_supported': ['openid'], @@ -236,6 +242,13 @@ class OIDC4VC { 'id_token_types_supported': ['subject_signed_id_token'] }), }; + + if (credentailsInScopeParameter) { + myRequest['scope'] = listToString(credentials); + } else { + myRequest['scope'] = 'openid'; + myRequest['authorization_details'] = jsonEncode(authorizationDetails); + } return myRequest; } diff --git a/pubspec.lock b/pubspec.lock index f47407a40..a24056114 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1421,10 +1421,10 @@ packages: dependency: transitive description: name: local_auth_ios - sha256: "26a8d1ad0b4ef6f861d29921be8383000fda952e323a5b6752cf82ca9cf9a7a9" + sha256: "8293faf72ef0ac4710f209edd03916c2d4c1eeab0483bdcf9b2e659c2f7d737b" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.5" local_auth_platform_interface: dependency: transitive description: