{"record":{"id":"886bbb895f73fe4f","repo":"SonarSource/sonarqube","slug":"user-s-is-not-part-of-allowed-workspaces-list","errorCode":null,"errorMessage":"User %s is not part of allowed workspaces list","messagePattern":"User (.+?) is not part of allowed workspaces list","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":403,"severity":"error","filePath":"server/sonar-auth-bitbucket/src/main/java/org/sonar/auth/bitbucket/BitbucketIdentityProvider.java","lineNumber":172,"sourceCode":"\n  @CheckForNull\n  private GsonEmails requestEmails(OAuth20Service service, OAuth2AccessToken accessToken) throws InterruptedException, ExecutionException, IOException {\n    OAuthRequest userRequest = new OAuthRequest(Verb.GET, settings.apiURL() + \"2.0/user/emails\");\n    service.signRequest(accessToken, userRequest);\n    Response emailsResponse = service.execute(userRequest);\n    if (emailsResponse.isSuccessful()) {\n      return GsonEmails.parse(emailsResponse.getBody());\n    }\n    return null;\n  }\n\n  private void checkTeamRestriction(OAuth20Service service, OAuth2AccessToken accessToken, GsonUser user) throws InterruptedException, ExecutionException, IOException {\n    String[] workspaceAllowed = settings.workspaceAllowedList();\n    if (workspaceAllowed != null && workspaceAllowed.length > 0) {\n      GsonWorkspaceAccesses userWorkspaces = requestWorkspaces(service, accessToken);\n      String errorMessage = format(\"User %s is not part of allowed workspaces list\", user.getUsername());\n      if (userWorkspaces == null || userWorkspaces.getWorkspaces() == null) {\n        throw new UnauthorizedException(errorMessage);\n      } else {\n        Set<String> uniqueUserWorkspaceSlugs = userWorkspaces.getWorkspaces().stream().map(w -> w.getWorkspace().getSlug()).collect(toSet());\n        List<String> workspaceAllowedList = asList(workspaceAllowed);\n        if (uniqueUserWorkspaceSlugs.stream().anyMatch(workspaceAllowedList::contains)) {\n          return;\n        }\n        List<String> workspaceNames = requestWorkspaceNames(service, accessToken, uniqueUserWorkspaceSlugs);\n        if (workspaceNames.stream().noneMatch(workspaceAllowedList::contains)) {\n          throw new UnauthorizedException(errorMessage);\n        }\n      }\n    }\n  }\n\n  @CheckForNull\n  private GsonWorkspaceAccesses requestWorkspaces(OAuth20Service service, OAuth2AccessToken accessToken) throws InterruptedException, ExecutionException, IOException {\n    OAuthRequest userRequest = new OAuthRequest(Verb.GET, settings.apiURL() + \"2.0/user/workspaces\");\n    service.signRequest(accessToken, userRequest);","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-auth-bitbucket/src/main/java/org/sonar/auth/bitbucket/BitbucketIdentityProvider.java#L154-L190","documentation":"Thrown by checkTeamRestriction when workspace allowlisting is configured and the workspace-access response from Bitbucket is null or contains no workspaces, so the user cannot be matched against any allowed workspace. It is an UnauthorizedException: authentication succeeded but the user is denied by policy.","triggerScenarios":"settings.workspaceAllowedList() is non-empty, requestWorkspaces returns a GsonWorkspaceAccesses whose getWorkspaces() is null (or the whole object is null), immediately throwing before slug comparison.","commonSituations":"Bitbucket returns an empty/absent workspaces page (user truly has no workspace memberships); the token lacks the workspace scope so Bitbucket returns no workspace data; apiURL misconfigured so the workspaces endpoint silently returns an empty payload.","solutions":["Grant the OAuth consumer the workspace membership scope so requestWorkspaces can read the user's workspaces.","Confirm the user actually belongs to one of the workspaces listed in sonar.auth.bitbucket.workspaces.","Check the workspaces API response (add logging) to distinguish 'no memberships' from 'unreadable due to scope'.","If the restriction is unintended, clear the workspace allowlist setting."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-check that the user has workspace data before enforcing restriction\nif (userWorkspaces == null || userWorkspaces.getWorkspaces() == null || userWorkspaces.getWorkspaces().isEmpty()) {\n    LOG.warn(\"No workspace memberships returned for user {} — check token scopes\", user.getUsername());\n}","typeGuard":null,"tryCatchPattern":"try {\n    checkTeamRestriction(service, accessToken, user);\n} catch (UnauthorizedException e) {\n    LOG.warn(\"Workspace restriction rejected login: {}\", e.getMessage());\n}","preventionTips":["Grant the workspace membership scope to the OAuth consumer.","Confirm each configured allowed workspace exists and the users are members.","Log the raw workspaces response once when setting up to validate scopes."],"tags":["oauth","bitbucket","authorization","workspace-restriction"],"backgroundTag":"permission-denied","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}