Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: new expiry logic and error codes for enrollments #1536

Merged
merged 30 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
08f2f68
fix: new field 'expiresAt' in EnrollDataStoreValue
srieteja Sep 5, 2023
55f2a71
reformat: minor refactoring
srieteja Sep 5, 2023
6804342
Merge branch 'auto_expire_enroll_key' into nwe_expire_logic
srieteja Sep 5, 2023
3bb8ff0
Merge branch 'auto_expire_enroll_key' into nwe_expire_logic
srieteja Sep 6, 2023
5db077a
revert: introduction of expiresAt into EnrollDatastoreValue
srieteja Sep 6, 2023
5a3eb16
fix: update error reponse handling for expired/invalid enrollmentId's
srieteja Sep 6, 2023
da281eb
revert: changed related to enrollmentExpiry in AtSecondaryConfig
srieteja Sep 6, 2023
fbdafe4
fix: respond with errorCode:AT0028 when enrollment is expired
srieteja Sep 6, 2023
48ffeb6
test: update tests
srieteja Sep 6, 2023
c617c02
build: add a dependency override
srieteja Sep 6, 2023
ff6ec9a
fix: exclude expired enrollments in enroll:list
srieteja Sep 6, 2023
1556d62
fix: getEnrollData() checks for expiry and sets appropriate response
srieteja Sep 6, 2023
c08d507
Merge branch 'trunk' into nwe_expire_logic
srieteja Sep 8, 2023
bc8cb39
Merge branch 'trunk' into nwe_expire_logic
srieteja Sep 8, 2023
04c08a2
fix: address review comments
srieteja Sep 9, 2023
9e099ba
Merge remote-tracking branch 'origin/nwe_expire_logic' into nwe_expir…
srieteja Sep 9, 2023
5744bf7
Merge branch 'trunk' into nwe_expire_logic
srieteja Sep 9, 2023
d451144
build: replace at_commons dep_override with latest version
srieteja Sep 9, 2023
a3a47fb
fix: change apkam expiry error code from AT0029 -> AT0028
srieteja Sep 9, 2023
f8c0393
fix: EnrollVerb Refactoring + use getEnrollDataStoreValue() in Abstra…
srieteja Sep 9, 2023
4a31b09
fix: improve error handling in pkamVerb
srieteja Sep 9, 2023
89f2247
fix: ttl not being updated in enroll_verb_handler.dart
srieteja Sep 9, 2023
4137f5e
test: fix failing functional tests
srieteja Sep 9, 2023
55e99bb
test: fix failing functional tests
srieteja Sep 9, 2023
1c0335a
Merge branch 'trunk' into nwe_expire_logic
srieteja Sep 11, 2023
25eb563
test: fix: do not use shared response object for tests
srieteja Sep 11, 2023
668fe71
Merge remote-tracking branch 'origin/nwe_expire_logic' into nwe_expir…
srieteja Sep 11, 2023
2ce1820
fix: rename verifyEnrollmentStateBeforeAction -> verifyEnrollmentStat…
srieteja Sep 11, 2023
4ef1efb
fix: ensure enrollParams are not null in EnrollVerb
srieteja Sep 11, 2023
da82205
Merge branch 'trunk' into nwe_expire_logic
murali-shris Sep 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -89,6 +90,9 @@ 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.expired.name;
}
return enrollDataStoreValue;
} on KeyNotFoundException {
logger.severe('$enrollmentKey does not exist in the keystore');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -47,23 +46,23 @@ class EnrollVerbHandler extends AbstractVerbHandler {
throw UnAuthenticatedException(
'Cannot $operation enrollment without authentication');
}
EnrollParams? enrollVerbParams;
try {
var enrollVerbParams;
if (verbParams[enrollParams] != null) {
srieteja marked this conversation as resolved.
Show resolved Hide resolved
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':
Expand All @@ -72,14 +71,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
Expand Down Expand Up @@ -171,26 +167,40 @@ class EnrollVerbHandler extends AbstractVerbHandler {
EnrollParams enrollParams,
currentAtSign,
String? operation,
Map<dynamic, dynamic> responseJson) async {
Map<dynamic, dynamic> 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
_validateEnrollmentValidity(
enrollStatus, enrollmentIdFromParams!, response);
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
Expand All @@ -200,24 +210,17 @@ 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(
enrollmentIdFromParams!, enrollParams, currentAtSign);
enrollmentIdFromParams, enrollParams, currentAtSign);
}
responseJson['enrollmentId'] = enrollmentIdFromParams;
}
Expand Down Expand Up @@ -280,11 +283,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);
}
Expand All @@ -294,11 +299,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
};
}
}
}

Expand Down Expand Up @@ -337,39 +344,45 @@ class EnrollVerbHandler extends AbstractVerbHandler {
}
}

Future<AtData> _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');
void _validateEnrollmentValidity(
srieteja marked this conversation as resolved.
Show resolved Hide resolved
EnrollStatus enrollStatus, String enrollmentId, Response response) {
if (EnrollStatus.expired == enrollStatus) {
response.isError = true;
response.errorCode = 'AT0028';
response.errorMessage =
'enrollment_id: $enrollmentId is expired or invalid';
return;
srieteja marked this conversation as resolved.
Show resolved Hide resolved
}
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(
srieteja marked this conversation as resolved.
Show resolved Hide resolved
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<void> _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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,25 @@ 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;
}
apkamResult.publicKey = enrollDataStoreValue.apkamPublicKey;
return apkamResult;
}

Response _getApprovalStatus(
EnrollStatus enrollStatus, enrollId, approvalState) {
Response _getApprovalStatus(EnrollStatus enrollStatus, enrollId) {
Response response = Response();
switch (enrollStatus) {
case EnrollStatus.denied:
Expand All @@ -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';
Expand All @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/at_secondary_server/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading