diff --git a/connector/github/github.go b/connector/github/github.go index 0712d3b9ff..e2b92f2f35 100644 --- a/connector/github/github.go +++ b/connector/github/github.go @@ -699,8 +699,8 @@ type org struct { // which inserts a bearer token as part of the request. func (c *githubConnector) teamsForOrg(ctx context.Context, client *http.Client, orgName string) ([]string, error) { apiURL, groups := c.apiURL+"/user/teams", []string{} + warnedCaseMismatch := false for { - // https://developer.github.com/v3/orgs/teams/#list-user-teams var ( teams []team err error @@ -710,7 +710,17 @@ func (c *githubConnector) teamsForOrg(ctx context.Context, client *http.Client, } for _, t := range teams { - if t.Org.Login == orgName { + // GitHub org names are case-insensitive; the API returns canonical (lowercase) form. + // https://docs.github.com/en/rest/teams/teams?apiVersion=2026-03-10#list-teams-for-the-authenticated-user + if strings.EqualFold(t.Org.Login, orgName) { + if !warnedCaseMismatch && t.Org.Login != orgName { + c.logger.Warn( + "org name in config differs in case from GitHub canonical form; use lowercase in config to keep group claims consistent", + "org", orgName, + "canonical", t.Org.Login, + ) + warnedCaseMismatch = true + } groups = append(groups, c.teamGroupClaims(t)...) } } diff --git a/connector/github/github_test.go b/connector/github/github_test.go index de35149608..b9c26cea1c 100644 --- a/connector/github/github_test.go +++ b/connector/github/github_test.go @@ -126,6 +126,32 @@ func TestUserGroupsWithTeamNameAndSlugFieldConfig(t *testing.T) { }) } +// tests case-insensitive org matching and the warn log on case mismatch +func TestTeamsForOrgCaseInsensitive(t *testing.T) { + s := newTestServer(map[string]testResponse{ + "/user/teams": { + data: []team{ + {Name: "team-1", Org: org{Login: "myorg"}}, + {Name: "team-2", Org: org{Login: "myorg"}}, + {Name: "team-3", Org: org{Login: "otherorg"}}, + }, + }, + }) + defer s.Close() + + var logBuf strings.Builder + logger := slog.New(slog.NewTextHandler(&logBuf, &slog.HandlerOptions{Level: slog.LevelWarn})) + c := githubConnector{apiURL: s.URL, logger: logger} + + for _, configOrg := range []string{"MyOrg", "MYORG", "myOrg"} { + groups, err := c.teamsForOrg(context.Background(), newClient(), configOrg) + expectNil(t, err) + expectEquals(t, groups, []string{"team-1", "team-2"}) + } + + expectEquals(t, strings.Count(logBuf.String(), "level=WARN"), 3) +} + // tests that the users login is used as their username when they have no username set func TestUsernameIncludedInFederatedIdentity(t *testing.T) { s := newTestServer(map[string]testResponse{