Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
8 changes: 8 additions & 0 deletions messages/delete_sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,11 @@ Ensure the CLI has authenticated with the sandbox's production org.
# error.missingUsername

Unable to determine the username of the org to delete. Specify the username with the --target-org | -o flag.

# error.insufficientPermissions

You do not have the required permission to delete this sandbox: %s. Contact your administrator to assign you the "DeleteSandbox" PermissionSet.

# warning.couldNotVerifyPermissions

Could not verify permissions in sandbox: %s. The delete operation will proceed, but may fail if you do not have the required permissions.
63 changes: 63 additions & 0 deletions src/commands/org/delete/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,29 @@ export default class DeleteSandbox extends SfCommand<SandboxDeleteResponse> {
throw messages.createError('error.unknownSandbox', [username]);
}

// Check if user has the DeleteSandbox PermissionSet in the sandbox
try {
const sandboxOrg = await Org.create({ aliasOrUsername: username });
const hasDeleteSandboxPermission = await this.hasPermission(sandboxOrg, 'DeleteSandbox');
this.debug('hasDeleteSandboxPermission %s ', hasDeleteSandboxPermission);
if (!hasDeleteSandboxPermission) {
throw messages.createError('error.insufficientPermissions', [username]);
}
} catch (error) {
// If it's a permission error we created, re-throw it
if (error instanceof SfError) {
const errorMessage = error.message || '';
if (errorMessage.includes('required permission') || errorMessage.includes('DeleteSandbox')) {
throw error;
}
}
// For other errors (e.g., org connection issues), log a warning but continue
// The actual delete operation will fail if permissions are truly insufficient
if (error instanceof Error) {
this.warn(messages.getMessage('warning.couldNotVerifyPermissions', [username]));
}
}

if (flags['no-prompt'] || (await this.confirm({ message: messages.getMessage('prompt.confirm', [username]) }))) {
try {
const org = await Org.create({ aliasOrUsername: username });
Expand All @@ -82,4 +105,44 @@ export default class DeleteSandbox extends SfCommand<SandboxDeleteResponse> {
}
return { username, orgId };
}

/**
* Checks if the current user has a PermissionSet with the specified name assigned.
*
* @param org The org to check permissions in
* @param permissionSetName The name of the PermissionSet to check (e.g., 'DeleteSandbox')
* @returns True if the user has the PermissionSet assigned, false otherwise
*/
// eslint-disable-next-line class-methods-use-this
private async hasPermission(org: Org, permissionSetName: string): Promise<boolean> {
try {
const connection = org.getConnection();
await org.refreshAuth();
// try to get it from Identity API
const identity = await connection.identity();
const userId = identity.user_id;

if (!userId) {
return false;
}

// Check if user has the PermissionSet assigned
const permissionSetAssignmentQuery = `
SELECT Id
FROM PermissionSetAssignment
WHERE AssigneeId = '${userId.replace(/'/g, "\\'")}'
AND PermissionSet.Name = '${permissionSetName.replace(/'/g, "\\'")}'
`;

try {
const permissionSetResult = await connection.query(permissionSetAssignmentQuery);
return permissionSetResult.totalSize > 0;
} catch {
// If query fails, return false
return false;
}
} catch {
return false;
}
}
}