From 608d7770fc12fc67a6487c57d4af1931e67b3a6c Mon Sep 17 00:00:00 2001 From: Talwinder kaur Date: Mon, 13 Nov 2023 08:23:40 -0500 Subject: [PATCH] feat(app): Add support for custom scope in verfication flow Signed-off-by: Talwinder kaur --- demo/app/ios/Runner/OpenID4VP.swift | 35 ++++++++++++++++--- demo/app/ios/Runner/flutterPlugin.swift | 30 ++++++++++++---- demo/app/lib/assets/customScopes.json | 6 ++++ demo/app/lib/services/config_service.dart | 16 +++++++++ demo/app/lib/views/presentation_preview.dart | 35 +++++++++++-------- ...presentation_preview_multi_cred_radio.dart | 24 +++++++++++-- .../app/lib/wallet_sdk/wallet_sdk_mobile.dart | 26 ++++++++++---- demo/app/pubspec.yaml | 1 + 8 files changed, 139 insertions(+), 34 deletions(-) create mode 100644 demo/app/lib/assets/customScopes.json diff --git a/demo/app/ios/Runner/OpenID4VP.swift b/demo/app/ios/Runner/OpenID4VP.swift index 69823c8f..e0363510 100644 --- a/demo/app/ios/Runner/OpenID4VP.swift +++ b/demo/app/ios/Runner/OpenID4VP.swift @@ -58,17 +58,43 @@ public class OpenID4VP { /** * initiatedInteraction has PresentCredential method which presents credentials to redirect uri from request object. */ - func presentCredential(selectedCredentials: VerifiableCredentialsArray) throws { + func presentCredential(selectedCredentials: VerifiableCredentialsArray, customScopes: Dictionary) throws { // guard let vpQueryContent = self.vpQueryContent else { // throw OpenID4VPError.runtimeError("OpenID4VP interaction not properly initialized, call processAuthorizationRequest first") // } guard let initiatedInteraction = self.initiatedInteraction else { throw OpenID4VPError.runtimeError("OpenID4VP interaction not properly initialized, call processAuthorizationRequest first") } + + let opts = Openid4vpNewPresentCredentialOpts() -// let verifiablePresentation = try CredentialNewInquirer(documentLoader)!.query(vpQueryContent, credentials: selectedCredentials) + + for scope in customScopes { + opts?.addScopeClaim(scope.key, claimJSON: scope.value as? String) - try initiatedInteraction.presentCredential(selectedCredentials) + } + try initiatedInteraction.presentCredentialOpts(selectedCredentials, opts: opts) + + } + + func getCustomScope() throws -> [String] { + guard let initiatedInteraction = self.initiatedInteraction else { + throw OpenID4VPError.runtimeError("OpenID4VP interaction not properly initialized, call processAuthorizationRequest first") + } + + let customScopes = initiatedInteraction.customScope() + var customScopesList = [String]() + + if (customScopes?.length() != 0){ + for i in 0...((customScopes?.length() ?? 0)-1) { + if (customScopes?.atIndex(i) != "openid"){ + customScopesList.append(customScopes?.atIndex(i) ?? "") + } + } + } + + // Otherwise return the default scope + return customScopesList } func getVerifierDisplayData() throws -> Openid4vpVerifierDisplayData { @@ -76,7 +102,6 @@ public class OpenID4VP { throw OpenID4VPError.runtimeError("OpenID4VP interaction not properly initialized, call processAuthorizationRequest first") } - let verifierDisplayData = try initiatedInteraction?.verifierDisplayData() - return verifierDisplayData! + return initiatedInteraction?.verifierDisplayData() ?? Openid4vpVerifierDisplayData() } } diff --git a/demo/app/ios/Runner/flutterPlugin.swift b/demo/app/ios/Runner/flutterPlugin.swift index 76e89f40..ba8b3d72 100644 --- a/demo/app/ios/Runner/flutterPlugin.swift +++ b/demo/app/ios/Runner/flutterPlugin.swift @@ -100,6 +100,9 @@ public class SwiftWalletSDKPlugin: NSObject, FlutterPlugin { case "getMatchedSubmissionRequirements": getMatchedSubmissionRequirements(arguments: arguments!, result: result) + + case "getCustomScope": + getCustomScope(result: result) case "presentCredential": presentCredential(arguments: arguments!, result: result) @@ -247,6 +250,23 @@ public class SwiftWalletSDKPlugin: NSObject, FlutterPlugin { } } + public func getCustomScope(result: @escaping FlutterResult){ + do { + guard let openID4VP = self.openID4VP else{ + return result(FlutterError.init(code: "NATIVE_ERR", + message: "error while process present credential", + details: "OpenID4VP interaction is not initialted")) + } + + let customScopeList = try openID4VP.getCustomScope() + result(customScopeList) + } catch let error as NSError{ + result(FlutterError.init(code: "NATIVE_ERR", + message: "error while getting custom scope ", + details: error.localizedDescription)) + } + } + /** This method invokes presentCredentialt defined in OpenID4Vp file. */ @@ -259,6 +279,8 @@ public class SwiftWalletSDKPlugin: NSObject, FlutterPlugin { } let selectedCredentials = arguments["selectedCredentials"] as? Array + let customScopeList = arguments["customScopeList"] as? Dictionary ?? [String: Any]() + let selectedCredentialsArray: VerifiableCredentialsArray? if (selectedCredentials != nil) { @@ -274,14 +296,10 @@ public class SwiftWalletSDKPlugin: NSObject, FlutterPlugin { } try openID4VP.presentCredential( - selectedCredentials: selectedCredentialsArray!) + selectedCredentials: selectedCredentialsArray!, customScopes: customScopeList) result(true); - } catch OpenID4VPError.runtimeError(let errorMsg as NSError){ - result(FlutterError.init(code: "NATIVE_ERR", - message: "error while processing present credential", - details: errorMsg.localizedDescription)) - } catch let error as NSError{ + } catch let error as NSError{ result(FlutterError.init(code: "NATIVE_ERR", message: "error while processing present credential", details: error.localizedDescription)) diff --git a/demo/app/lib/assets/customScopes.json b/demo/app/lib/assets/customScopes.json new file mode 100644 index 00000000..b38c96f5 --- /dev/null +++ b/demo/app/lib/assets/customScopes.json @@ -0,0 +1,6 @@ +{ + "test": { + "email": "test@gmail.com", + "name": "Test User" + } +} \ No newline at end of file diff --git a/demo/app/lib/services/config_service.dart b/demo/app/lib/services/config_service.dart index 98454ccb..ff461c8b 100644 --- a/demo/app/lib/services/config_service.dart +++ b/demo/app/lib/services/config_service.dart @@ -25,4 +25,20 @@ class ConfigService { log('decodedResponse $configResponseDecoded'); return connectIssuerConfigList; } + + Future> readCustomScopeConfig(List customScopeList) async { + Map customScopeConfigList = {}; + final String scopeConfigResponse = await rootBundle.loadString('lib/assets/customScopes.json'); + final configDataResp = await json.decode(scopeConfigResponse); + + List customScopeStr = customScopeList.cast(); + for (var customScope in customScopeStr) { + var claimJSON = configDataResp[customScope]; + customScopeConfigList.addAll( + {customScope.toString(): jsonEncode(claimJSON)} + ); + } + log('customScope config list $customScopeConfigList'); + return customScopeConfigList; + } } diff --git a/demo/app/lib/views/presentation_preview.dart b/demo/app/lib/views/presentation_preview.dart index 7bac513f..a129b9c5 100644 --- a/demo/app/lib/views/presentation_preview.dart +++ b/demo/app/lib/views/presentation_preview.dart @@ -23,6 +23,8 @@ import 'package:app/models/activity_data_object.dart'; import 'package:app/widgets/credential_card.dart'; import 'package:app/views/custom_error.dart'; +import 'package:app/services/config_service.dart'; + class PresentationPreview extends StatefulWidget { final CredentialData credentialData; @@ -160,20 +162,25 @@ class PresentationPreviewState extends State { PrimaryButton( onPressed: () async { final SharedPreferences pref = await prefs; - try { - await WalletSDKPlugin.presentCredential( - selectedCredentials: [widget.credentialData.rawCredential]); - } catch (error) { - var errString = error.toString().replaceAll(r'\', ''); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CustomError( - titleBar: 'Presentation Preview', - requestErrorTitleMsg: 'error while presenting credential', - requestErrorSubTitleMsg: errString))); - return; - } + Map customScopeConfigList = {}; + final ConfigService configService = ConfigService(); + WalletSDKPlugin.getCustomScope() + .then((customScopeList) async { + customScopeConfigList = await configService.readCustomScopeConfig(customScopeList); + }) + .whenComplete(() => WalletSDKPlugin.presentCredential( + selectedCredentials: [widget.credentialData.rawCredential], + customScopeList: customScopeConfigList)) + .onError((error, stackTrace) { + var errString = error.toString().replaceAll(r'\', ''); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CustomError( + titleBar: 'Presentation Preview', + requestErrorTitleMsg: 'error while presenting credential', + requestErrorSubTitleMsg: errString))); + }); var activities = await WalletSDKPlugin.storeActivityLogger(); var credID = pref.getString('credID'); _storageService.addActivities(ActivityDataObj(credID!, activities)); diff --git a/demo/app/lib/views/presentation_preview_multi_cred_radio.dart b/demo/app/lib/views/presentation_preview_multi_cred_radio.dart index 9fe089ab..c173d135 100644 --- a/demo/app/lib/views/presentation_preview_multi_cred_radio.dart +++ b/demo/app/lib/views/presentation_preview_multi_cred_radio.dart @@ -18,6 +18,9 @@ import 'package:app/services/storage_service.dart'; import 'package:app/models/activity_data_object.dart'; import 'package:app/widgets/credential_card.dart'; +import '../services/config_service.dart'; +import 'custom_error.dart'; + class PresentationPreviewMultiCred extends StatefulWidget { final List credentialData; @@ -174,8 +177,25 @@ class PresentationPreviewMultiCredState extends State customScopeConfigList = {}; + final ConfigService configService = ConfigService(); + WalletSDKPlugin.getCustomScope() + .then((customScopeList) async { + customScopeConfigList = await configService.readCustomScopeConfig(customScopeList); + }) + .whenComplete(() => WalletSDKPlugin.presentCredential( + selectedCredentials: [selectedCredentialData.rawCredential], + customScopeList: customScopeConfigList)) + .onError((error, stackTrace) { + var errString = error.toString().replaceAll(r'\', ''); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CustomError( + titleBar: 'Presentation Preview', + requestErrorTitleMsg: 'error while presenting credential', + requestErrorSubTitleMsg: errString))); + }); var activities = await WalletSDKPlugin.storeActivityLogger(); var credID = pref.getString('credID'); _storageService.addActivities(ActivityDataObj(credID!, activities)); diff --git a/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart b/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart index 96b0e90b..f9f9b4cf 100644 --- a/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart +++ b/demo/app/lib/wallet_sdk/wallet_sdk_mobile.dart @@ -44,10 +44,11 @@ class WalletSDK extends WalletPlatform { } } - Future> initializeWalletInitiatedFlow(String issuerURI, List credentialTypes ) async { + Future> initializeWalletInitiatedFlow( + String issuerURI, List credentialTypes) async { try { - List supportedCredentialResp = - await methodChannel.invokeMethod('initializeWalletInitiatedFlow', {'issuerURI': issuerURI, 'credentialTypes': credentialTypes}); + List supportedCredentialResp = await methodChannel.invokeMethod('initializeWalletInitiatedFlow', + {'issuerURI': issuerURI, 'credentialTypes': credentialTypes}); return supportedCredentialResp.map((d) => SupportedCredentials.fromMap(d.cast())).toList(); } on PlatformException catch (error) { debugPrint(error.toString()); @@ -156,6 +157,10 @@ class WalletSDK extends WalletPlatform { .toList(); } + Future> getCustomScope() async { + return await methodChannel.invokeMethod('getCustomScope'); + } + Future> getSubmissionRequirements({required List? storedCredentials}) async { return (await methodChannel.invokeMethod>( 'getMatchedSubmissionRequirements', {'storedCredentials': storedCredentials}))! @@ -185,9 +190,15 @@ class WalletSDK extends WalletPlatform { purpose: data['purpose'] as String); } - Future presentCredential({required List selectedCredentials}) async { - await methodChannel - .invokeMethod('presentCredential', {'selectedCredentials': selectedCredentials}); + Future presentCredential( + {required List selectedCredentials, Map? customScopeList}) async { + try { + return await methodChannel.invokeMethod('presentCredential', + {'selectedCredentials': selectedCredentials, 'customScopeList': customScopeList}); + } on PlatformException catch (error) { + debugPrint(error.toString()); + rethrow; + } } Future> storeActivityLogger() async { @@ -212,7 +223,8 @@ class WalletSDK extends WalletPlatform { } Future> getIssuerMetaData(List credentialTypes) async { - List getIssuerMetaDataResp = await methodChannel.invokeMethod('getIssuerMetaData', {'credentialTypes': credentialTypes}); + List getIssuerMetaDataResp = + await methodChannel.invokeMethod('getIssuerMetaData', {'credentialTypes': credentialTypes}); return getIssuerMetaDataResp.map((d) => IssuerMetaData.fromMap(d.cast())).toList(); } diff --git a/demo/app/pubspec.yaml b/demo/app/pubspec.yaml index af035837..3ae10806 100644 --- a/demo/app/pubspec.yaml +++ b/demo/app/pubspec.yaml @@ -97,6 +97,7 @@ flutter: - lib/assets/images/logoIcon.png - lib/assets/issuerAuthFlowConfig.json - lib/assets/walletInitiatedIssuerConfig.json + - lib/assets/customScopes.json # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware