Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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;
}
}
}