diff --git a/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart index 85f87a2ee..5e0c52b50 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/abstract_verb_handler.dart @@ -11,6 +11,7 @@ import 'package:at_secondary/src/verb/manager/response_handler_manager.dart'; import 'package:at_server_spec/at_server_spec.dart'; import 'package:at_server_spec/at_verb_spec.dart'; import 'package:at_utils/at_logger.dart'; +import 'package:at_secondary/src/utils/secondary_util.dart'; final String paramFullCommandAsReceived = 'FullCommandAsReceived'; @@ -89,6 +90,10 @@ abstract class AbstractVerbHandler implements VerbHandler { AtData enrollData = await keyStore.get(enrollmentKey); EnrollDataStoreValue enrollDataStoreValue = EnrollDataStoreValue.fromJson(jsonDecode(enrollData.data!)); + if (!SecondaryUtil.isActiveKey(enrollData) && + enrollDataStoreValue.approval!.state != EnrollStatus.approved.name) { + enrollDataStoreValue.approval?.state = EnrollStatus.expired.name; + } return enrollDataStoreValue; } on KeyNotFoundException { logger.severe('$enrollmentKey does not exist in the keystore'); diff --git a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart index 5f4d7bd0b..49e85eacf 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/enroll_verb_handler.dart @@ -3,12 +3,11 @@ import 'dart:collection'; import 'dart:convert'; import 'package:at_commons/at_commons.dart'; import 'package:at_secondary/src/connection/inbound/inbound_connection_metadata.dart'; -import 'package:at_secondary/src/server/at_secondary_config.dart'; import 'package:at_secondary/src/server/at_secondary_impl.dart'; +import 'package:at_secondary/src/server/at_secondary_config.dart'; import 'package:at_secondary/src/constants/enroll_constants.dart'; import 'package:at_secondary/src/enroll/enroll_datastore_value.dart'; import 'package:at_secondary/src/utils/notification_util.dart'; -import 'package:at_secondary/src/utils/secondary_util.dart'; import 'package:at_secondary/src/verb/handler/otp_verb_handler.dart'; import 'package:at_server_spec/at_server_spec.dart'; import 'package:at_server_spec/at_verb_spec.dart'; @@ -47,23 +46,31 @@ class EnrollVerbHandler extends AbstractVerbHandler { throw UnAuthenticatedException( 'Cannot $operation enrollment without authentication'); } + EnrollParams? enrollVerbParams; try { - var enrollVerbParams; - if (verbParams[enrollParams] != null) { + // Ensure that enrollParams are present for all enroll operation + // Exclude operation 'list' which does not have enrollParams + if (verbParams[enrollParams] == null) { + if (operation != 'list') { + logger.severe( + 'Enroll params is empty | EnrollParams: ${verbParams[enrollParams]}'); + throw IllegalArgumentException('Enroll parameters not provided'); + } + } else { enrollVerbParams = EnrollParams.fromJson(jsonDecode(verbParams[enrollParams]!)); } switch (operation) { case 'request': await _handleEnrollmentRequest( - enrollVerbParams, currentAtSign, responseJson, atConnection); + enrollVerbParams!, currentAtSign, responseJson, atConnection); break; case 'approve': case 'deny': case 'revoke': - await _handleEnrollmentPermissions( - enrollVerbParams, currentAtSign, operation, responseJson); + await _handleEnrollmentPermissions(enrollVerbParams!, currentAtSign, + operation, responseJson, response); break; case 'list': @@ -72,14 +79,11 @@ class EnrollVerbHandler extends AbstractVerbHandler { return; } } catch (e, stackTrace) { - response.isError = true; - response.errorMessage = e.toString(); - responseJson['status'] = 'exception'; - responseJson['reason'] = e.toString(); logger.severe('Exception: $e\n$stackTrace'); rethrow; } response.data = jsonEncode(responseJson); + return; } /// Enrollment requests details are persisted in the keystore and are excluded from @@ -171,26 +175,44 @@ class EnrollVerbHandler extends AbstractVerbHandler { EnrollParams enrollParams, currentAtSign, String? operation, - Map responseJson) async { + Map responseJson, + Response response) async { final enrollmentIdFromParams = enrollParams.enrollmentId; String enrollmentKey = '$enrollmentIdFromParams.$newEnrollmentKeyPattern.$enrollManageNamespace'; logger.finer( 'Enrollment key: $enrollmentKey$currentAtSign | Enrollment operation: $operation'); + EnrollDataStoreValue? enrollDataStoreValue; + EnrollStatus? enrollStatus; // Fetch and returns enrollment data from the keystore. // Throw AtEnrollmentException, IF // 1. Enrollment key is not present in keystore // 2. Enrollment key is not active - AtData enrollData = await _fetchEnrollmentDataFromKeyStore( - enrollmentKey, currentAtSign, enrollmentIdFromParams); - var enrollDataStoreValue = - EnrollDataStoreValue.fromJson(jsonDecode(enrollData.data!)); - + try { + enrollDataStoreValue = + await getEnrollDataStoreValue('$enrollmentKey$currentAtSign'); + } on KeyNotFoundException { + // When an enrollment key is expired or invalid + enrollStatus = EnrollStatus.expired; + } + enrollStatus ??= + getEnrollStatusFromString(enrollDataStoreValue!.approval!.state); + // Validates if enrollment is not expired + if (EnrollStatus.expired == enrollStatus) { + response.isError = true; + response.errorCode = 'AT0028'; + response.errorMessage = + 'enrollment_id: $enrollmentIdFromParams is expired or invalid'; + } + if (response.isError) { + return; + } // Verifies whether the enrollment state matches the intended state // Throws AtEnrollmentException, if the enrollment state is different from // the intended state - _verifyEnrollmentStateBeforeAction(operation, enrollDataStoreValue); - enrollDataStoreValue.approval!.state = _getEnrollStatusEnum(operation).name; + _verifyEnrollmentStateBeforeAction(operation, enrollStatus); + enrollDataStoreValue!.approval!.state = + _getEnrollStatusEnum(operation).name; responseJson['status'] = _getEnrollStatusEnum(operation).name; // If an enrollment is approved, we need the enrollment to be active @@ -200,20 +222,13 @@ class EnrollVerbHandler extends AbstractVerbHandler { However, if the enrollment state is denied or revoked, unless we wanted to display denied or revoked enrollments in the UI, we can let the TTL be, so that the enrollment will be deleted subsequently.*/ - await keyStore.put( - '$enrollmentKey$currentAtSign', - AtData() - ..data = jsonEncode(enrollDataStoreValue.toJson()) - ..metaData = (enrollData.metaData - ?..ttl = 0 - ..expiresAt = null), - skipCommit: true); + await _updateEnrollmentValueAndResetTTL( + '$enrollmentKey$currentAtSign', enrollDataStoreValue); // when enrollment is approved store the apkamPublicKey of the enrollment if (operation == 'approve') { var apkamPublicKeyInKeyStore = 'public:${enrollDataStoreValue.appName}.${enrollDataStoreValue.deviceName}.pkam.$pkamNamespace.__public_keys$currentAtSign'; - var valueJson = {}; - valueJson[apkamPublicKey] = enrollDataStoreValue.apkamPublicKey; + var valueJson = {'apkamPublicKey': enrollDataStoreValue.apkamPublicKey}; var atData = AtData()..data = jsonEncode(valueJson); await keyStore.put(apkamPublicKeyInKeyStore, atData); await _storeEncryptionKeys( @@ -280,11 +295,13 @@ class EnrollVerbHandler extends AbstractVerbHandler { if (_doesEnrollmentHaveManageNamespace(enrollDataStoreValue)) { await _fetchAllEnrollments(enrollmentKeysList, enrollmentRequestsMap); } else { - enrollmentRequestsMap[enrollmentKey] = { - 'appName': enrollDataStoreValue.appName, - 'deviceName': enrollDataStoreValue.deviceName, - 'namespace': enrollDataStoreValue.namespaces - }; + if (enrollDataStoreValue.approval!.state != EnrollStatus.expired.name) { + enrollmentRequestsMap[enrollmentKey] = { + 'appName': enrollDataStoreValue.appName, + 'deviceName': enrollDataStoreValue.deviceName, + 'namespace': enrollDataStoreValue.namespaces + }; + } } return jsonEncode(enrollmentRequestsMap); } @@ -294,11 +311,13 @@ class EnrollVerbHandler extends AbstractVerbHandler { for (var enrollmentKey in enrollmentKeysList) { EnrollDataStoreValue enrollDataStoreValue = await getEnrollDataStoreValue(enrollmentKey); - enrollmentRequestsMap[enrollmentKey] = { - 'appName': enrollDataStoreValue.appName, - 'deviceName': enrollDataStoreValue.deviceName, - 'namespace': enrollDataStoreValue.namespaces - }; + if (enrollDataStoreValue.approval!.state != EnrollStatus.expired.name) { + enrollmentRequestsMap[enrollmentKey] = { + 'appName': enrollDataStoreValue.appName, + 'deviceName': enrollDataStoreValue.deviceName, + 'namespace': enrollDataStoreValue.namespaces + }; + } } } @@ -337,39 +356,34 @@ class EnrollVerbHandler extends AbstractVerbHandler { } } - Future _fetchEnrollmentDataFromKeyStore( - String enrollmentKey, currentAtSign, String? enrollmentId) async { - AtData enrollData; - // KeyStore.get will not return null. If the value is null, keyStore.get - // throws KeyNotFoundException. - // So, enrollData will NOT be null. - try { - enrollData = await keyStore.get('$enrollmentKey$currentAtSign'); - } on KeyNotFoundException { - throw AtEnrollmentException( - 'enrollment id: $enrollmentId not found in keystore'); - } - // If enrollment is not active, throw AtEnrollmentException - if (!SecondaryUtil.isActiveKey(enrollData)) { - throw AtEnrollmentException('The enrollment $enrollmentId is expired'); - } - return enrollData; - } - /// Verifies whether the enrollment state matches the intended state. /// Throws AtEnrollmentException: If the enrollment state is different /// from the intended state. void _verifyEnrollmentStateBeforeAction( - String? operation, EnrollDataStoreValue enrollDataStoreValue) { - if (operation == 'approve' && - enrollDataStoreValue.approval!.state != EnrollStatus.pending.name) { + String? operation, EnrollStatus enrollStatus) { + if (operation == 'approve' && EnrollStatus.pending != enrollStatus) { throw AtEnrollmentException( - 'Cannot approve a ${enrollDataStoreValue.approval!.state} enrollment. Only pending enrollments can be approved'); + 'Cannot approve a ${enrollStatus.name} enrollment. Only pending enrollments can be approved'); } - if (operation == 'revoke' && - enrollDataStoreValue.approval!.state != EnrollStatus.approved.name) { + if (operation == 'revoke' && EnrollStatus.approved != enrollStatus) { throw AtEnrollmentException( - 'Cannot revoke a ${enrollDataStoreValue.approval!.state} enrollment. Only approved enrollments can be revoked'); + 'Cannot revoke a ${enrollStatus.name} enrollment. Only approved enrollments can be revoked'); } } + + Future _updateEnrollmentValueAndResetTTL( + String enrollmentKey, EnrollDataStoreValue enrollDataStoreValue) async { + // Fetch the existing data + AtMetaData? enrollMetaData = await keyStore.getMeta(enrollmentKey); + // Update key with new data + // only update ttl, expiresAt in metadata to preserve all the other valid data fields + enrollMetaData?.ttl = 0; + enrollMetaData?.expiresAt = null; + await keyStore.put( + enrollmentKey, + AtData() + ..data = jsonEncode(enrollDataStoreValue.toJson()) + ..metaData = enrollMetaData, + skipCommit: true); + } } diff --git a/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart index 601c977c1..96b28aae2 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/pkam_verb_handler.dart @@ -86,16 +86,17 @@ class PkamVerbHandler extends AbstractVerbHandler { String enrollId, String atSign) async { String enrollmentKey = '$enrollId.$newEnrollmentKeyPattern.$enrollManageNamespace$atSign'; - var enrollData = await keyStore.get(enrollmentKey); - final atData = enrollData.data; - final enrollDataStoreValue = - EnrollDataStoreValue.fromJson(jsonDecode(atData)); - EnrollStatus enrollStatus = - EnrollStatus.values.byName(enrollDataStoreValue.approval!.state); - + late final EnrollDataStoreValue enrollDataStoreValue; ApkamVerificationResult apkamResult = ApkamVerificationResult(); - apkamResult.response = _getApprovalStatus( - enrollStatus, enrollId, enrollDataStoreValue.approval!.state); + EnrollStatus? enrollStatus; + try { + enrollDataStoreValue = await getEnrollDataStoreValue(enrollmentKey); + enrollStatus = getEnrollStatusFromString(enrollDataStoreValue.approval!.state); + } on KeyNotFoundException catch (e) { + logger.finer('Caught exception trying to fetch enrollment key: $e'); + enrollStatus = EnrollStatus.expired; + } + apkamResult.response = _getApprovalStatus(enrollStatus, enrollId); if (apkamResult.response.isError) { return apkamResult; } @@ -103,8 +104,7 @@ class PkamVerbHandler extends AbstractVerbHandler { return apkamResult; } - Response _getApprovalStatus( - EnrollStatus enrollStatus, enrollId, approvalState) { + Response _getApprovalStatus(EnrollStatus enrollStatus, enrollId) { Response response = Response(); switch (enrollStatus) { case EnrollStatus.denied: @@ -125,6 +125,11 @@ class PkamVerbHandler extends AbstractVerbHandler { response.errorCode = 'AT0027'; response.errorMessage = 'enrollment_id: $enrollId is revoked'; break; + case EnrollStatus.expired: + response.isError = true; + response.errorCode = 'AT0028'; + response.errorMessage = 'enrollment_id: $enrollId is expired or invalid'; + break; default: response.isError = true; response.errorCode = 'AT0026'; @@ -143,7 +148,7 @@ class PkamVerbHandler extends AbstractVerbHandler { bool isValidSignature = false; var storedSecret = await keyStore.get('private:$sessionId$atSign'); storedSecret = storedSecret?.data; - if(signature == null || signature.isEmpty ){ + if (signature == null || signature.isEmpty) { logger.severe('inputSignature is null/empty'); return false; } diff --git a/packages/at_secondary_server/pubspec.yaml b/packages/at_secondary_server/pubspec.yaml index 7e464aa84..c22bee9ab 100644 --- a/packages/at_secondary_server/pubspec.yaml +++ b/packages/at_secondary_server/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: collection: 1.18.0 basic_utils: 5.6.1 ecdsa: 0.0.4 - at_commons: 3.0.54 + at_commons: 3.0.55 at_utils: 3.0.15 at_chops: 1.0.4 at_lookup: 3.0.40 diff --git a/packages/at_secondary_server/test/enroll_verb_test.dart b/packages/at_secondary_server/test/enroll_verb_test.dart index bad17ebbb..6e48d943d 100644 --- a/packages/at_secondary_server/test/enroll_verb_test.dart +++ b/packages/at_secondary_server/test/enroll_verb_test.dart @@ -341,7 +341,9 @@ void main() { test( 'A test to verify revoke operations thrown exception when given enrollmentId is not in keystore', () async { - String enrollmentRequest = 'enroll:revoke:{"enrollmentId":"123"}'; + String enrollmentId = '123'; + String enrollmentRequest = + 'enroll:revoke:{"enrollmentId":"$enrollmentId"}'; HashMap verbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); inboundConnection.getMetaData().isAuthenticated = true; @@ -351,12 +353,13 @@ void main() { Response response = Response(); EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); - expect( - () async => await enrollVerbHandler.processVerb( - response, verbParams, inboundConnection), - throwsA(predicate((dynamic e) => - e is AtEnrollmentException && - e.message == 'enrollment id: 123 not found in keystore'))); + await enrollVerbHandler.processVerb( + response, verbParams, inboundConnection); + expect(response.isError, true); + expect(response.errorMessage, isNotNull); + assert(response.errorMessage! + .contains('enrollment_id: $enrollmentId is expired')); + expect(response.errorCode, 'AT0028'); }); tearDown(() async => await verbTestsTearDown()); }); @@ -378,12 +381,12 @@ void main() { inboundConnection.getMetaData().sessionID = 'dummy_session'; (inboundConnection.getMetaData() as InboundConnectionMetadata) .enrollmentId = '123'; - Response response = Response(); + Response responseObject = Response(); EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); await enrollVerbHandler.processVerb( - response, verbParams, inboundConnection); - Map enrollmentResponse = jsonDecode(response.data!); + responseObject, verbParams, inboundConnection); + Map enrollmentResponse = jsonDecode(responseObject.data!); expect(enrollmentResponse['enrollmentId'], isNotNull); expect(enrollmentResponse['status'], 'approved'); // Commit log @@ -464,7 +467,7 @@ void main() { }); group('A group of tests related to enrollment request expiry', () { - Response response = Response(); + String? otp; setUp(() async { await verbTestsSetUp(); // Fetch TOTP @@ -473,16 +476,19 @@ void main() { getVerbParam(VerbSyntax.otp, totpCommand); OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); inboundConnection.getMetaData().isAuthenticated = true; + Response defaultResponse = Response(); await otpVerbHandler.processVerb( - response, totpVerbParams, inboundConnection); + defaultResponse, totpVerbParams, inboundConnection); + otp = defaultResponse.data; }); test('A test to verify expired enrollment cannot be approved', () async { + Response response = Response(); // Enroll a request on an unauthenticated connection which will expire in 1 millisecond EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); enrollVerbHandler.enrollmentExpiryInMills = 1; String enrollmentRequest = - 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"${response.data}","apkamPublicKey":"dummy_apkam_public_key"}'; + 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}'; HashMap enrollVerbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); inboundConnection.getMetaData().isAuthenticated = false; @@ -500,25 +506,27 @@ void main() { getVerbParam(VerbSyntax.enroll, approveEnrollmentCommand); inboundConnection.getMetaData().isAuthenticated = true; inboundConnection.getMetaData().sessionID = 'dummy_session_id'; - expect( - () async => await enrollVerbHandler.processVerb( - response, enrollVerbParams, inboundConnection), - throwsA(predicate((dynamic e) => - e is AtEnrollmentException && - e.message == 'The enrollment $enrollmentId is expired'))); + await enrollVerbHandler.processVerb( + response, enrollVerbParams, inboundConnection); + expect(response.isError, true); + expect(response.errorMessage, isNotNull); + assert(response.errorMessage! + .contains('enrollment_id: $enrollmentId is expired')); + expect(response.errorCode, 'AT0028'); }); test('A test to verify expired enrollment cannot be denied', () async { + Response response = Response(); // Enroll a request on an unauthenticated connection which will expire in 1 millisecond EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); enrollVerbHandler.enrollmentExpiryInMills = 1; String enrollmentRequest = - 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"${response.data}","apkamPublicKey":"dummy_apkam_public_key"}'; + 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}'; HashMap enrollVerbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); inboundConnection.getMetaData().isAuthenticated = false; - inboundConnection.getMetaData().sessionID = 'dummy_session_id'; + inboundConnection.getMetaData().sessionID = 'dummy_session_id1'; await enrollVerbHandler.processVerb( response, enrollVerbParams, inboundConnection); String enrollmentId = jsonDecode(response.data!)['enrollmentId']; @@ -532,21 +540,23 @@ void main() { getVerbParam(VerbSyntax.enroll, approveEnrollmentCommand); inboundConnection.getMetaData().isAuthenticated = true; inboundConnection.getMetaData().sessionID = 'dummy_session_id'; - expect( - () async => await enrollVerbHandler.processVerb( - response, enrollVerbParams, inboundConnection), - throwsA(predicate((dynamic e) => - e is AtEnrollmentException && - e.message == 'The enrollment $enrollmentId is expired'))); + await enrollVerbHandler.processVerb( + response, enrollVerbParams, inboundConnection); + expect(response.isError, true); + expect(response.errorMessage, isNotNull); + assert(response.errorMessage! + .contains('enrollment_id: $enrollmentId is expired')); + expect(response.errorCode, 'AT0028'); }); test('A test to verify TTL on approved enrollment is reset', () async { + Response response = Response(); // Enroll a request on an unauthenticated connection which will expire in 1 minute EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); - enrollVerbHandler.enrollmentExpiryInMills = 60000; + enrollVerbHandler.enrollmentExpiryInMills = 600000; String enrollmentRequest = - 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"${response.data}","apkamPublicKey":"dummy_apkam_public_key"}'; + 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}'; HashMap enrollVerbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); inboundConnection.getMetaData().isAuthenticated = false; @@ -556,11 +566,12 @@ void main() { String enrollmentId = jsonDecode(response.data!)['enrollmentId']; String status = jsonDecode(response.data!)['status']; expect(status, 'pending'); + String enrollmentKey = + '$enrollmentId.$newEnrollmentKeyPattern.$enrollManageNamespace$alice'; // Verify TTL is added to the enrollment - AtData? enrollmentData = await secondaryKeyStore.get( - '$enrollmentId.$newEnrollmentKeyPattern.$enrollManageNamespace$alice'); + AtData? enrollmentData = await secondaryKeyStore.get(enrollmentKey); expect(enrollmentData!.metaData!.expiresAt, isNotNull); - expect(enrollmentData.metaData!.ttl, 60000); + expect(enrollmentData.metaData!.ttl, 600000); //Approve enrollment String approveEnrollmentCommand = 'enroll:approve:{"enrollmentId":"$enrollmentId"}'; @@ -571,8 +582,7 @@ void main() { await enrollVerbHandler.processVerb( response, enrollVerbParams, inboundConnection); // Verify TTL is reset - enrollmentData = await secondaryKeyStore.get( - '$enrollmentId.$newEnrollmentKeyPattern.$enrollManageNamespace$alice'); + enrollmentData = await secondaryKeyStore.get(enrollmentKey); expect(enrollmentData!.metaData!.expiresAt, null); expect(enrollmentData.metaData!.ttl, 0); }); @@ -580,10 +590,11 @@ void main() { test( 'A test to verify TTL is not set for enrollment requested on an authenticated connection', () async { + Response response = Response(); EnrollVerbHandler enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); String enrollmentRequest = - 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"${response.data}","apkamPublicKey":"dummy_apkam_public_key"}'; + 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}'; HashMap enrollVerbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); inboundConnection.getMetaData().isAuthenticated = true; @@ -604,36 +615,40 @@ void main() { }); group('A group of tests related to approve enrollment', () { - Response response = Response(); + String? otp; late String enrollmentId; late EnrollVerbHandler enrollVerbHandler; HashMap enrollVerbParams; + Response defaultResponse = Response(); setUp(() async { await verbTestsSetUp(); - // Fetch TOTP + // Fetch OTP String totpCommand = 'otp:get'; HashMap totpVerbParams = getVerbParam(VerbSyntax.otp, totpCommand); OtpVerbHandler otpVerbHandler = OtpVerbHandler(secondaryKeyStore); inboundConnection.getMetaData().isAuthenticated = true; await otpVerbHandler.processVerb( - response, totpVerbParams, inboundConnection); + defaultResponse, totpVerbParams, inboundConnection); + otp = defaultResponse.data; // Enroll a request on an unauthenticated connection which will expire in 1 minute enrollVerbHandler = EnrollVerbHandler(secondaryKeyStore); enrollVerbHandler.enrollmentExpiryInMills = 60000; String enrollmentRequest = - 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"${response.data}","apkamPublicKey":"dummy_apkam_public_key"}'; + 'enroll:request:{"appName":"wavi","deviceName":"mydevice","namespaces":{"wavi":"r"},"otp":"$otp","apkamPublicKey":"dummy_apkam_public_key"}'; HashMap enrollVerbParams = getVerbParam(VerbSyntax.enroll, enrollmentRequest); inboundConnection.getMetaData().isAuthenticated = false; inboundConnection.getMetaData().sessionID = 'dummy_session_id'; await enrollVerbHandler.processVerb( - response, enrollVerbParams, inboundConnection); - enrollmentId = jsonDecode(response.data!)['enrollmentId']; - String status = jsonDecode(response.data!)['status']; + defaultResponse, enrollVerbParams, inboundConnection); + enrollmentId = jsonDecode(defaultResponse.data!)['enrollmentId']; + String status = jsonDecode(defaultResponse.data!)['status']; expect(status, 'pending'); }); + test('A test to verify denied enrollment cannot be approved', () async { + Response response = Response(); //deny enrollment String denyEnrollmentCommand = 'enroll:deny:{"enrollmentId":"$enrollmentId"}'; @@ -661,6 +676,7 @@ void main() { }); test('A test to verify revoked enrollment cannot be approved', () async { + Response response = Response(); //approve enrollment String approveEnrollmentCommand = 'enroll:approve:{"enrollmentId":"$enrollmentId"}'; @@ -691,6 +707,7 @@ void main() { }); test('A test to verify pending enrollment cannot be revoked', () async { + Response response = Response(); //revoke enrollment String denyEnrollmentCommand = 'enroll:revoke:{"enrollmentId":"$enrollmentId"}'; diff --git a/packages/at_secondary_server/test/pkam_verb_test.dart b/packages/at_secondary_server/test/pkam_verb_test.dart index 58f3e89f8..9d93d013e 100644 --- a/packages/at_secondary_server/test/pkam_verb_test.dart +++ b/packages/at_secondary_server/test/pkam_verb_test.dart @@ -131,6 +131,18 @@ void main() { 'enrollment_id: enrollId is denied'); }); + test('verify apkam behaviour - case: enrollment expired ', () async { + enrollData.approval = EnrollApproval('denied'); + when(() => mockKeyStore.get(any())) + .thenThrow(KeyNotFoundException('key not found')); + + var apkamResult = + await pkamVerbHandler.handleApkamVerification('enrollId', '@alice'); + expect(apkamResult.response.isError, true); + expect(apkamResult.response.errorCode, 'AT0028'); + expect(apkamResult.response.errorMessage, + 'enrollment_id: enrollId is expired or invalid'); + }); tearDownAll(() async => await tearDownFunc()); }); diff --git a/tests/at_functional_test/test/enroll_verb_test.dart b/tests/at_functional_test/test/enroll_verb_test.dart index d77a878c5..2868b6704 100644 --- a/tests/at_functional_test/test/enroll_verb_test.dart +++ b/tests/at_functional_test/test/enroll_verb_test.dart @@ -119,10 +119,8 @@ void main() { await socket_writer(socketConnection1!, approveEnrollCommand); var approveEnrollResponse = await read(); approveEnrollResponse = approveEnrollResponse.replaceFirst('error:', ''); - expect( - approveEnrollResponse.contains( - 'enrollment id: $dummyEnrollmentId not found in keystore'), - true); + expect(approveEnrollResponse, + 'AT0028:enrollment_id: $dummyEnrollmentId is expired or invalid\n'); }); test( @@ -141,10 +139,8 @@ void main() { await socket_writer(socketConnection1!, denyEnrollCommand); var denyEnrollResponse = await read(); denyEnrollResponse = denyEnrollResponse.replaceFirst('error:', ''); - expect( - denyEnrollResponse.contains( - 'enrollment id: $dummyEnrollmentId not found in keystore'), - true); + expect(denyEnrollResponse, + 'AT0028:enrollment_id: $dummyEnrollmentId is expired or invalid\n'); }); test('enroll request on unauthenticated connection without otp', () async { @@ -695,8 +691,8 @@ void main() { expect(jsonDecode(enrollmentResponse)['status'], 'denied'); expect(jsonDecode(enrollmentResponse)['enrollmentId'], enrollmentId); // Approve enrollment - await socket_writer( - socketConnection1!, 'enroll:approve:{"enrollmentId":"$enrollmentId"}'); + await socket_writer(socketConnection1!, + 'enroll:approve:{"enrollmentId":"$enrollmentId"}'); enrollmentResponse = (await read()).replaceAll('error:', ''); expect( jsonDecode(enrollmentResponse)['errorDescription'], @@ -728,8 +724,8 @@ void main() { // Approve enrollment await _connect(); await prepare(socketConnection1!, firstAtsign); - await socket_writer( - socketConnection1!, 'enroll:approve:{"enrollmentId":"$enrollmentId"}'); + await socket_writer(socketConnection1!, + 'enroll:approve:{"enrollmentId":"$enrollmentId"}'); enrollmentResponse = (await read()).replaceAll('data:', ''); expect(jsonDecode(enrollmentResponse)['status'], 'approved'); expect(jsonDecode(enrollmentResponse)['enrollmentId'], enrollmentId); @@ -740,8 +736,8 @@ void main() { expect(jsonDecode(enrollmentResponse)['status'], 'revoked'); expect(jsonDecode(enrollmentResponse)['enrollmentId'], enrollmentId); // Approve a revoked enrollment - await socket_writer( - socketConnection1!, 'enroll:approve:{"enrollmentId":"$enrollmentId"}'); + await socket_writer(socketConnection1!, + 'enroll:approve:{"enrollmentId":"$enrollmentId"}'); enrollmentResponse = (await read()).replaceAll('error:', ''); expect( jsonDecode(enrollmentResponse)['errorDescription'],