{"record":{"id":"2be51e66547cea6f","repo":"apache/druid","slug":"user-s-already-has-role-s","errorCode":null,"errorMessage":"User [%s] already has role [%s].","messagePattern":"User \\[(.+?)\\] already has role \\[(.+?)\\]\\.","errorType":"http","errorClass":"BasicSecurityDBResourceException","httpStatus":400,"severity":"warning","filePath":"extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java","lineNumber":1000,"sourceCode":"        objectMapper,\n        oldRoleMapValue\n    );\n    if (roleMap.get(roleName) == null) {\n      throw new BasicSecurityDBResourceException(\"Role [%s] does not exist.\", roleName);\n    }\n\n    byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);\n    Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(\n        objectMapper,\n        oldUserMapValue\n    );\n    BasicAuthorizerUser user = userMap.get(userName);\n    if (userMap.get(userName) == null) {\n      throw new BasicSecurityDBResourceException(\"User [%s] does not exist.\", userName);\n    }\n\n    if (user.getRoles().contains(roleName)) {\n      throw new BasicSecurityDBResourceException(\"User [%s] already has role [%s].\", userName, roleName);\n    }\n\n    user.getRoles().add(roleName);\n    byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);\n\n    // Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)\n    return tryUpdateUserAndRoleMap(\n        prefix,\n        userMap, oldUserMapValue, newUserMapValue,\n        roleMap, oldRoleMapValue, oldRoleMapValue\n    );\n  }\n\n  private boolean unassignUserRoleOnce(String prefix, String userName, String roleName)\n  {\n    byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);\n    Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(\n        objectMapper,","sourceCodeStart":982,"sourceCodeEnd":1018,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java#L982-L1018","documentation":"BasicSecurityDBResourceException thrown when assigning a role the user already holds. The coordinator checks the user's current role set before mutating it, rejecting redundant assignments so the role list stays a clean set. Purely informational in effect: the desired end state already exists.","triggerScenarios":"POST .../users/<userName>/roles with roleName already present in the user's roles (e.g. from a previous run of the same script).","commonSituations":"Non-idempotent provisioning scripts run repeatedly; concurrent admins assigning the same role; retry logic that re-sends an assignment that already succeeded; bulk imports that assume empty role sets.","solutions":["Check the user's current roles via GET .../users/<userName> and skip assignment if present","Treat 'already has role' responses as success in idempotent automation","Track assignment state in your provisioning tool to avoid re-issuing assignments","Remove the role first (if a reset is intended) before re-assigning"],"exampleFix":"// before\nclient.assignRole(\"default\", \"alice\", \"readRole\"); // fails on rerun\n// after\nif (!userRoles(\"default\", \"alice\").contains(\"readRole\")) {\n  client.assignRole(\"default\", \"alice\", \"readRole\");\n}","handlingStrategy":"validation","validationCode":"java.util.Set<String> roles = getUser(\"default\", userName).getRoles();\nif (roles.contains(roleName)) skipAssignment(userName, roleName);","typeGuard":null,"tryCatchPattern":"try {\n  client.assignRole(\"default\", userName, roleName);\n} catch (BasicSecurityDBResourceException e) {\n  if (e.getMessage().contains(\"already has role\")) {\n    log.info(\"{} already has {}\", userName, roleName); // treat as success\n  } else { throw e; }\n}","preventionTips":["Fetch current roles before assignment and skip if present","Treat 'already has role' as success in idempotent automation","Avoid re-issuing assignments after successful runs"],"tags":["security","rest-api","duplicate-resource"],"backgroundTag":"file-already-exists","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}