Skip to content

Commit

Permalink
Merge pull request #690 from atsign-foundation/668-ensure-atkeys-file…
Browse files Browse the repository at this point in the history
…-is-writeable-before-finalizing-pkam-apkam
  • Loading branch information
gkc authored Oct 17, 2024
2 parents 089713f + 0dfaa6c commit 4ea7cc5
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/at_onboarding_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 1.8.0
- feat: add `unrevoke` command to the activate CLI
- feat: add `delete` command to the activate CLI
- fix: When submitting an enrollment request, check for write permissions of AtKeys file path.
## 1.7.0
- feat: add `auto` command to the activate CLI
## 1.6.4
Expand Down
58 changes: 58 additions & 0 deletions packages/at_onboarding_cli/lib/src/cli/auth_cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,24 @@ Future<void> enroll(ArgResults argResults, {AtOnboardingService? svc}) async {
}

File f = File(argResults[AuthCliArgs.argNameAtKeys]);

if (f.existsSync()) {
stderr.writeln('Error: atKeys file ${f.path} already exists');
return;
}

// "canCreateFile" attempts to create the directories for the given [file] if they do not exist,
// and then tries to open the file in write mode to verify write permissions.
//
// If the file can be opened for writing, it is immediately closed and deleted.
// In [AtOnboardingServiceImpl._generateAtKeysFile] method, there is a check which returns error
// if the file already exists. Therefore, delete the file here after checking for write permissions.
//
// Incase of any exceptions, the error is logged and returned.
if (canCreateFile(f) == false) {
return;
}

svc ??= createOnboardingService(argResults);

Map<String, String> namespaces = {};
Expand Down Expand Up @@ -416,6 +429,51 @@ Future<void> enroll(ArgResults argResults, {AtOnboardingService? svc}) async {
}
}

/// Checks if the specified [file] is writable.
///
/// This function attempts to create the directories for the given [file] if they do not exist,
/// and then tries to open the file in write mode to verify write permissions.
/// If the file can be opened for writing, it is immediately closed and deleted.
///
/// Returns:
/// - `true` if the file is writable, or
/// - `false` if the file is not writable due to existing files, lack of permissions,
/// or any other exceptions encountered during the process.
///
/// [file]: The [File] instance to check for write access.
///
/// Exceptions:
/// - If the file does not have write permissions, a [PathAccessException] is caught, and an error message is printed to stderr.
/// - Any other exceptions are caught and logged, indicating a failure to determine write access.
@visibleForTesting
bool canCreateFile(File file) {
try {
// If the directories do not exist, create them.
// "recursive" is set to true to ensure that any missing parent directories are created.
// "exclusive" is set to true to prevent creation of file if it already exists.
file.createSync(recursive: true, exclusive: true);
// Try opening the file in write mode, which requires write permissions
RandomAccessFile raf = file.openSync(mode: FileMode.write);
raf.closeSync();
// In [AtOnboardingServiceImpl._generateAtKeysFile] method, there is a check which returns error
// if the file already exists. Therefore, delete the file here after checking for write permissions.
// This does not delete the existing file. Deletes only if the new file is created to verify write permissions.
file.deleteSync();
return true;
} on PathExistsException {
stderr.writeln('Error : atKeys file ${file.path} already exists');
rethrow;
} on PathAccessException {
stderr.writeln(
'Error : atKeys file ${file.path} does not have write permissions');
return false;
} catch (e) {
// If any exception occurs, we assume the file is not writable
stderr.writeln('Error in writing to atKeys file: ${e.toString()}');
return false;
}
}

@visibleForTesting
Future<void> setSpp(ArgResults argResults, AtClient atClient) async {
String spp = argResults[AuthCliArgs.argNameSpp];
Expand Down
34 changes: 34 additions & 0 deletions packages/at_onboarding_cli/test/auth_cli_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dart:io';

import 'package:at_onboarding_cli/src/cli/auth_cli.dart';
import 'package:test/test.dart';

void main() {
final baseDirPath = 'test/keys';
group('A group of tests to verify write permission of apkam file path', () {
final dirPath = '$baseDirPath/@alice-apkam-keys.atKeys';

test(
'A test to verify isWritable returns false if directory has read-only permissions',
() async {
final directory = Directory(dirPath);
// Create the directory first to ensure it exists before calling isWritable.
await directory.create(recursive: true);
// Set permission to read only.
await Process.run('chmod', ['444', baseDirPath]);
expect(canCreateFile(File(dirPath)), false);
});

test(
'A test verify isWritable returns true if directory does not have a file already',
() {
expect(canCreateFile(File(dirPath)), true);
});
});

tearDown(() async {
// Set full permissions to delete the directory.
await Process.run('chmod', ['777', baseDirPath]);
Directory(baseDirPath).deleteSync(recursive: true);
});
}

0 comments on commit 4ea7cc5

Please sign in to comment.