{"record":{"id":"46c64530818773c6","repo":"passbolt/passbolt_api","slug":"the-role-identifier-is-not-valid-rolesdeleteservice","errorCode":null,"errorMessage":"The role identifier is not valid.","messagePattern":"The role identifier is not valid\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Service/Roles/RolesDeleteService.php","lineNumber":60,"sourceCode":"    /**\n     * Constructor.\n     */\n    public function __construct()\n    {\n        $this->Roles = TableRegistry::getTableLocator()->get('Roles');\n    }\n\n    /**\n     * @param \\App\\Utility\\UserAccessControl $uac UAC object.\n     * @param string $roleId Role identifier to update.\n     * @return void\n     */\n    public function delete(UserAccessControl $uac, string $roleId): void\n    {\n        $uac->assertIsAdmin();\n\n        if (!Validation::uuid($roleId)) {\n            throw new BadRequestException(__('The role identifier is not valid.'));\n        }\n\n        try {\n            /** @var \\App\\Model\\Entity\\Role $role */\n            $role = $this->Roles->find('notDeleted')->where(['id' => $roleId])->firstOrFail();\n        } catch (RecordNotFoundException $e) {\n            throw new NotFoundException(__('The role does not exist or deleted.'), null, $e);\n        }\n\n        $role = $this->softDeleteRole($role, $uac);\n\n        $this->dispatchEvent(self::AFTER_ROLE_DELETE_SUCCESS_EVENT_NAME, [\n            'uac' => $uac,\n            'role' => $role,\n        ]);\n    }\n\n    /**","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/Roles/RolesDeleteService.php#L42-L78","documentation":"RolesDeleteService::delete rejects a role deletion request whose roleId is not a valid UUID string before touching the database. This guards the DELETE /roles/{id} endpoint against malformed identifiers with a fast 400 response.","triggerScenarios":"DELETE /roles/{roleId} where roleId is not a 36-char UUID — e.g. a role name ('admin'), an empty string, or a truncated ID passed by a client bug.","commonSituations":"Scripts substituting the role name instead of its ID, URL-encoding corruption dropping parts of the UUID, or API consumers reading the wrong field from a roles listing response.","solutions":["Fetch role IDs via GET /roles and use the `id` field (UUID) in the DELETE URL.","Validate the ID client-side with a UUID regex or the same Validation::uuid() rule before calling.","Fix client code that passes name/slug instead of ID.","If an ID looks wrong, re-list roles rather than guessing the identifier."],"exampleFix":"// before\nDELETE /roles/admin            -> 400 The role identifier is not valid\n// after\nDELETE /roles/8e07f60c-0d72-5f21-a10b-66c0ca44f2b0  -> 200","handlingStrategy":"validation","validationCode":"const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nif (!UUID_RE.test(roleId)) throw new Error('roleId must be a UUID before DELETE /roles/' + roleId);","typeGuard":"function isUuid(v) {\n  return typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);\n}","tryCatchPattern":"try {\n  await api.deleteRole(roleId);\n} catch (e) {\n  if (e.status === 400 && /identifier is not valid/i.test(e.message)) {\n    // refetch roles and correct the ID\n  } else throw e;\n}","preventionTips":["Always take role IDs from the GET /roles response `id` field","Validate UUID format before any roles/{id} call","Never substitute role name/slug for the ID in URLs","Beware URL encoding corrupting UUID characters"],"tags":["validation","roles","uuid","passbolt"],"backgroundTag":"invalid-argument-format","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}