apache/druid · error · BasicSecurityDBResourceException

User [%s] does not have role [%s].

Error message

User [%s] does not have role [%s].

What it means

Thrown by the coordinator basic authorizer metadata storage updater when unassigning a role from a user: the user exists in the authorizer's metadata store but its role set does not contain the requested role. Druid validates the user-role association before writing the updated user map, so the delete fails fast with BasicSecurityDBResourceException instead of silently no-oping.

Source

Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java:1036

        objectMapper,
        oldRoleMapValue
    );
    if (roleMap.get(roleName) == null) {
      throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
    }

    byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
    Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
        objectMapper,
        oldUserMapValue
    );
    BasicAuthorizerUser user = userMap.get(userName);
    if (userMap.get(userName) == null) {
      throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
    }

    if (!user.getRoles().contains(roleName)) {
      throw new BasicSecurityDBResourceException("User [%s] does not have role [%s].", userName, roleName);
    }

    user.getRoles().remove(roleName);
    byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);

    // Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
    return tryUpdateUserAndRoleMap(
        prefix,
        userMap, oldUserMapValue, newUserMapValue,
        roleMap, oldRoleMapValue, oldRoleMapValue
    );
  }

  private boolean assignGroupMappingRoleOnce(String prefix, String groupMappingName, String roleName)
  {
    byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
    Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
        objectMapper,

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Verify the user's current roles via GET /druid-ext/basic-security/authorization/db/v1/<authorizerName>/users/<userName> before unassigning
  2. Treat this 400 response as an idempotent success if the goal is 'user must not have role' and skip retrying
  3. Re-fetch the user list to refresh stale caches before retrying
  4. Check for concurrent admin/API callers modifying the same user

Example fix

// before (blind delete)
client.delete("/druid-ext/basic-security/authorization/db/v1/internal-auth/users/alice/roles/readonly");
// after (check first)
BasicAuthorizerUser user = client.getUser("internal-auth", "alice");
if (user.getRoles().contains("readonly")) {
  client.delete("/druid-ext/basic-security/authorization/db/v1/internal-auth/users/alice/roles/readonly");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check assignment before deleting
Set<String> roles = getUser(authorizer, userName).getRoles();
if (!roles.contains(roleName)) return; // nothing to revoke

Type guard

boolean isAssigned(BasicAuthorizerUser u, String role) { return u != null && u.getRoles() != null && u.getRoles().contains(role); }

Try / catch

try {
  unassignRole(authorizer, userName, roleName);
} catch (BasicSecurityDBResourceException e) {
  if (e.getMessage().contains("does not have role")) { /* already revoked: treat as success */ }
  else throw e;
}

Prevention

When it happens

Trigger: Calling the coordinator security API DELETE to unassign a role (unassignRoleFromUser path) with a (userName, roleName) pair where the user was never assigned that role; racing with another admin who already removed the role from the user; stale client-side state from a cached user listing.

Common situations: Automation scripts that revoke roles idempotently without first checking the user's current roles; cleanup tooling deleting role assignments that were already removed; concurrent admin operations through the Druid console and API.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/43f7760c93c70356. Report an issue: GitHub.