{"record":{"id":"22dba7eac9c9080b","repo":"SonarSource/sonarqube","slug":"s-must-be-a-member-of-at-least-one-organization","errorCode":null,"errorMessage":"'%s' must be a member of at least one organization which has installed the SonarQube GitHub app","messagePattern":"'(.+?)' must be a member of at least one organization which has installed the SonarQube GitHub app","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":403,"severity":"error","filePath":"server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java","lineNumber":150,"sourceCode":"    if (user.getEmail() == null) {\n      // if the user has not specified a public email address in their profile\n      email = gitHubRestClient.getEmail(scribe, accessToken);\n    } else {\n      email = user.getEmail();\n    }\n\n    UserIdentity userIdentity = userIdentityFactory.create(user, email,\n      settings.syncGroups() ? gitHubRestClient.getTeams(scribe, accessToken) : null);\n    context.authenticate(userIdentity);\n    context.redirectToRequestedPage();\n  }\n\n  private void check(OAuth20Service scribe, OAuth2AccessToken accessToken, GsonUser user) {\n    if (!isUserAuthorized(scribe, accessToken)) {\n      String message = settings.getOrganizations().isEmpty()\n        ? format(\"'%s' must be a member of at least one organization which has installed the SonarQube GitHub app\", user.getLogin())\n        : format(\"'%s' must be a member of at least one organization: '%s'\", user.getLogin(), String.join(\"', '\", settings.getOrganizations().stream().sorted().toList()));\n      throw new UnauthorizedException(message);\n    }\n  }\n\n  private boolean isUserAuthorized(OAuth20Service scribe, OAuth2AccessToken accessToken) {\n    Set<String> userOrganizationNames = getUserOrganizationNames(scribe, accessToken);\n    if (isOrganizationMembershipRequired()) {\n      return isOrganizationsMember(settings.getOrganizations(), userOrganizationNames);\n    } else {\n      return isMemberOfInstallationOrganization(userOrganizationNames);\n    }\n  }\n\n  private static boolean isOrganizationsMember(Set<String> organizations, Set<String> userOrganizationNames) {\n    return organizations.stream().anyMatch(userOrganizationNames::contains);\n  }\n\n  @NotNull\n  private Set<String> getUserOrganizationNames(OAuth20Service scribe, OAuth2AccessToken accessToken) {","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java#L132-L168","documentation":"Thrown by GitHubIdentityProvider.check when isUserAuthorized returns false: the authenticated GitHub user's organizations do not satisfy the configured organization restriction. When no organizations are configured, the message asks that the user belong to at least one organization with the SonarQube GitHub App installed.","triggerScenarios":"onCallback -> check(scribe, accessToken, user); getUserOrganizationNames returns a set that fails isUserAuthorized — either organizations are configured and the user belongs to none of them, or none are configured and the user belongs to no organization that has the SonarQube GitHub App installed.","commonSituations":"User belongs to an org but the SonarQube GitHub App is not installed on that org; user's org membership is private so it is not visible to the app; admin listed wrong org names; user logs in with a personal account unrelated to the company's GitHub orgs.","solutions":["Install the SonarQube GitHub App on the user's organization (or add the org to sonar.auth.github.organizations).","Verify the user's membership is public in the org or that the app has permission to see memberships.","Check the configured organization names match GitHub org slugs exactly (case-insensitive, no typos).","Test with a known member of an authorized org to confirm the check works."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify app installation and org membership before login\n// GET /user/memberships/orgs/{org} with the app token must return 200","typeGuard":null,"tryCatchPattern":"try {\n    check(scribe, accessToken, user);\n} catch (UnauthorizedException e) {\n    LOG.warn(\"GitHub org check failed: {}\", e.getMessage());\n    throw e; // surface to the login page\n}","preventionTips":["Install the SonarQube GitHub App on every organization that should have access.","Ask users to make org membership public, or grant the app membership-read permission.","Keep sonar.auth.github.organizations slugs exact and sorted/deduplicated."],"tags":["oauth","github","authorization","github-app"],"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"}