From a48424d79369fc2ff0614dbcdfb06f3bacdf8e82 Mon Sep 17 00:00:00 2001 From: Brahim LAISSAOUI Date: Sun, 14 Dec 2025 11:59:25 +0100 Subject: [PATCH] blaissao : Enforce 'DeleteSandbox' Permission Set requirement for Sandbox Deletion --- messages/delete_sandbox.md | 8 ++++ src/commands/org/delete/sandbox.ts | 63 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/messages/delete_sandbox.md b/messages/delete_sandbox.md index bf24b3f7..e6efa986 100644 --- a/messages/delete_sandbox.md +++ b/messages/delete_sandbox.md @@ -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. diff --git a/src/commands/org/delete/sandbox.ts b/src/commands/org/delete/sandbox.ts index 1646a15d..9069d2a8 100644 --- a/src/commands/org/delete/sandbox.ts +++ b/src/commands/org/delete/sandbox.ts @@ -61,6 +61,29 @@ export default class DeleteSandbox extends SfCommand { 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 }); @@ -82,4 +105,44 @@ export default class DeleteSandbox extends SfCommand { } 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 { + 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; + } + } }