{"record":{"id":"14aab36d2eedf202","repo":"apolloconfig/apollo","slug":"invalid-user-token-operation-s","errorCode":null,"errorMessage":"Invalid user token operation:%s","messagePattern":"Invalid user token operation:(.+?)","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/UserTokenService.java","lineNumber":327,"sourceCode":"    UserTokenScope scope = new UserTokenScope();\n    scope.setOperations(normalizeOperations(request.getOperations()));\n    scope.setAppIds(emptyToNull(request.getAppIds()));\n    scope.setEnvs(emptyToNull(request.getEnvs()));\n    scope.setNamespaces(request.getNamespaces());\n    return scope;\n  }\n\n  private Set<String> normalizeOperations(Set<String> operations) {\n    if (operations == null || operations.isEmpty()) {\n      return null;\n    }\n    Set<String> normalized = new HashSet<>();\n    for (String operation : operations) {\n      if (StringUtils.isBlank(operation)) {\n        continue;\n      }\n      if (!UserTokenOperation.ALL.contains(operation)) {\n        throw new BadRequestException(\"Invalid user token operation:%s\", operation);\n      }\n      if (!isOperationAvailable(operation)) {\n        throw new BadRequestException(\"User token operation is not allowed:%s\", operation);\n      }\n      normalized.add(operation);\n    }\n    return normalized.isEmpty() ? null : normalized;\n  }\n\n  private boolean isOperationAvailable(String operation) {\n    if (UserTokenOperation.RESOURCE_SCOPED.contains(operation)) {\n      return true;\n    }\n    if (UserTokenOperation.APP_CREATE.equals(operation)) {\n      return userPermissionValidator.hasCreateApplicationPermission();\n    }\n    if (UserTokenOperation.USER_MANAGE.equals(operation)) {\n      return userPermissionValidator.hasManageUsersPermission();","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/apolloconfig/apollo/blob/d95fc18d112589efc09ddcbe1507047584d55251/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/UserTokenService.java#L309-L345","documentation":"Thrown by UserTokenService.normalizeOperations() during token creation when one of the requested operation strings is not in the UserTokenOperation.ALL set. Valid operations are: config:read, config:modify, config:release, namespace:create, namespace:delete, cluster:create, app:create, app:manage-role, user:manage, system:admin. Blank operation strings are silently skipped; only non-blank unrecognized values trigger this. BadRequestException → HTTP 400.","triggerScenarios":"Calling createToken() with a UserTokenCreateRequest whose operations set contains a string not in UserTokenOperation.ALL — for example 'config:write', 'app:delete', 'read', or any typo. The check happens in buildScope() → normalizeOperations(), which is invoked after validateCreateRequest and resolveExpires.","commonSituations":"Client sends an operation string based on outdated or custom documentation that doesn't match the server's UserTokenOperation constants. Copy-paste from a different API version. Hardcoded operation lists that drift after an Apollo upgrade adds or renames operations.","solutions":["Use only operation strings from UserTokenOperation.ALL: config:read, config:modify, config:release, namespace:create, namespace:delete, cluster:create, app:create, app:manage-role, user:manage, system:admin.","Call userTokenService.findAvailableOperations() at runtime to discover which operations the current user can use, then submit only from that list.","Reference the UserTokenOperation constants directly instead of hardcoding strings."],"exampleFix":"// before\nrequest.setOperations(Set.of(\"config:write\"));\n// after\nrequest.setOperations(Set.of(UserTokenOperation.CONFIG_MODIFY));","handlingStrategy":"validation","validationCode":"Set<String> available = new HashSet<>(userTokenService.findAvailableOperations());\nSet<String> requested = request.getOperations();\nif (requested != null) {\n    for (String op : requested) {\n        if (!UserTokenOperation.ALL.contains(op)) {\n            throw new IllegalArgumentException(\"Unknown operation: \" + op + \". Valid: \" + UserTokenOperation.ALL);\n        }\n    }\n}\nuserTokenService.createToken(request, operator);","typeGuard":"static boolean isValidOperation(String operation) {\n    return UserTokenOperation.ALL.contains(operation);\n}","tryCatchPattern":"try {\n    userTokenService.createToken(request, operator);\n} catch (BadRequestException e) {\n    if (e.getMessage().contains(\"Invalid user token operation\")) {\n        return Response.status(400).entity(\"Valid operations: \" + UserTokenOperation.ALL).build();\n    }\n    throw e;\n}","preventionTips":["Reference UserTokenOperation constants instead of string literals.","Call findAvailableOperations() to dynamically discover allowed operations for the current user."],"tags":["apollo-portal","user-token","validation","bad-request"],"backgroundTag":null,"analyzedSha":"d95fc18d112589efc09ddcbe1507047584d55251","analyzedAt":"2026-08-14T04:00:05.477Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}