Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v2.9] GitHub Auth Provider: allow to search teams #45162

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 78 additions & 25 deletions pkg/auth/providers/github/github_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -37,27 +36,24 @@ func (g *GClient) getAccessToken(code string, config *v32.GithubConfig) (string,

b, err := g.postToGithub(url, form)
if err != nil {
logrus.Errorf("Github getAccessToken: GET url %v received error from github, err: %v", url, err)
return "", err
return "", fmt.Errorf("github getAccessToken: GET url %v received error from github, err: %v", url, err)
pmatseykanets marked this conversation as resolved.
Show resolved Hide resolved
}

// Decode the response
var respMap map[string]interface{}

if err := json.Unmarshal(b, &respMap); err != nil {
logrus.Errorf("Github getAccessToken: received error unmarshalling response body, err: %v", err)
return "", err
return "", fmt.Errorf("github getAccessToken: received error unmarshalling response body, err: %v", err)
}

if respMap["error"] != nil {
desc := respMap["error_description"]
logrus.Errorf("Received Error from github %v, description from github %v", respMap["error"], desc)
return "", fmt.Errorf("Received Error from github %v, description from github %v", respMap["error"], desc)
return "", fmt.Errorf("github getAccessToken: received error from github %v, description from github %v", respMap["error"], desc)
}

acessToken, ok := respMap["access_token"].(string)
if !ok {
return "", fmt.Errorf("Received Error reading accessToken from response %v", respMap)
return "", fmt.Errorf("github getAccessToken: received error reading accessToken from response %v", respMap)
}
return acessToken, nil
}
Expand Down Expand Up @@ -118,11 +114,54 @@ func (g *GClient) getTeams(githubAccessToken string, config *v32.GithubConfig) (
logrus.Errorf("Github getGithubTeams: received error unmarshalling teams array, err: %v", err)
return teams, err
}
for _, teamObj := range teamObjs {
teams = append(teams, teamObj)
teams = append(teams, teamObjs...)

}
return teams, nil
}

// getOrgTeams returns the teams belonging to an organization.
func (g *GClient) getOrgTeams(githubAccessToken string, config *v32.GithubConfig, org Account) ([]Account, error) {
url := fmt.Sprintf(g.getURL("ORG_TEAMS", config), org.Login)
pmatseykanets marked this conversation as resolved.
Show resolved Hide resolved
responses, err := g.paginateGithub(githubAccessToken, url)
if err != nil {
logrus.Errorf("Github getGithubTeams: GET url %v received error from github, err: %v", url, err)
return nil, err
}

var teams, respTeams []Account
pmatseykanets marked this conversation as resolved.
Show resolved Hide resolved
for _, response := range responses {
respTeams, err = g.getOrgTeamInfo(response, config, org)
if err != nil {
logrus.Errorf("Github getOrgTeams: received error unmarshalling teams array, err: %v", err)
return teams, err
}
teams = append(teams, respTeams...)
}

return teams, nil
}

// getOrgTeamInfo is similar to getTeamInfo but takes an org as an argument.
func (g *GClient) getOrgTeamInfo(b []byte, config *v32.GithubConfig, org Account) ([]Account, error) {
var teams []Account
var teamObjs []Team
if err := json.Unmarshal(b, &teamObjs); err != nil {
logrus.Errorf("Github getTeamInfo: received error unmarshalling team array, err: %v", err)
return teams, err
}

url := g.getURL("TEAM_PROFILE", config)
for _, team := range teamObjs {
teams = append(teams, Account{
ID: team.ID,
Name: team.Name,
AvatarURL: org.AvatarURL,
HTMLURL: fmt.Sprintf(url, org.Login, team.Slug),
Login: team.Slug,
})
}

return teams, nil
}

Expand Down Expand Up @@ -221,22 +260,34 @@ func (g *GClient) searchUsers(searchTerm, searchType string, githubAccessToken s
return result.Items, nil
}

func (g *GClient) getOrgByName(org string, githubAccessToken string, config *v32.GithubConfig) (Account, error) {
org = URLEncoded(org)
url := g.getURL("ORGS", config) + org

b, _, err := g.getFromGithub(githubAccessToken, url)
// searchTeams searches for teams that match the search term in the organizations the access token has access to.
// At the moment it only does a case-insensitive prefix match on the team's name.
func (g *GClient) searchTeams(searchTerm, githubAccessToken string, config *v32.GithubConfig) ([]Account, error) {
orgs, err := g.getOrgs(githubAccessToken, config)
if err != nil {
logrus.Debugf("Github getGithubOrgByName: GET url %v received error from github, err: %v", url, err)
return Account{}, err
return nil, err
}
var githubAcct Account
if err := json.Unmarshal(b, &githubAcct); err != nil {
logrus.Errorf("Github getGithubOrgByName: error unmarshalling response, err: %v", err)
return Account{}, err

lowerSearchTerm := strings.ToLower(searchTerm)

var matches, teams []Account
for _, org := range orgs {
teams, err = g.getOrgTeams(githubAccessToken, config, org)
if err != nil {
return nil, err
}

for _, team := range teams {
if !strings.HasPrefix(strings.ToLower(team.Name), lowerSearchTerm) {
continue
}

matches = append(matches, team)
}

}

return githubAcct, nil
return matches, nil
}

func (g *GClient) getUserOrgByID(id string, githubAccessToken string, config *v32.GithubConfig) (Account, error) {
Expand Down Expand Up @@ -289,10 +340,10 @@ func (g *GClient) postToGithub(url string, form url.Values) ([]byte, error) {
default:
var body bytes.Buffer
io.Copy(&body, resp.Body)
return nil, fmt.Errorf("Request failed, got status code: %d. Response: %s",
return nil, fmt.Errorf("request failed, got status code: %d. Response: %s",
resp.StatusCode, body.Bytes())
}
return ioutil.ReadAll(resp.Body)
return io.ReadAll(resp.Body)
}

func (g *GClient) getFromGithub(githubAccessToken string, url string) ([]byte, string, error) {
Expand Down Expand Up @@ -321,7 +372,7 @@ func (g *GClient) getFromGithub(githubAccessToken string, url string) ([]byte, s
}

nextURL := g.nextGithubPage(resp)
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
return b, nextURL, err
}

Expand Down Expand Up @@ -368,6 +419,8 @@ func (g *GClient) getURL(endpoint string, config *v32.GithubConfig) string {
toReturn = apiEndpoint + "/user/teams?per_page=100"
case "TEAM_PROFILE":
toReturn = hostName + "/orgs/%s/teams/%s"
case "ORG_TEAMS":
toReturn = apiEndpoint + "/orgs/%s/teams?per_page=100"
default:
toReturn = apiEndpoint
}
Expand Down
27 changes: 18 additions & 9 deletions pkg/auth/providers/github/github_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,9 @@ func (g *ghProvider) RefetchGroupPrincipals(principalID string, secret string) (
var err error
var config *v32.GithubConfig

if config == nil {
config, err = g.getGithubConfigCR()
if err != nil {
return nil, err
}
config, err = g.getGithubConfigCR()
if err != nil {
return nil, err
}

orgAccts, err := g.githubClient.getOrgs(secret, config)
Expand Down Expand Up @@ -294,6 +292,19 @@ func (g *ghProvider) SearchPrincipals(searchKey, principalType string, token v3.
principals = append(principals, p)
}

if principalType == "" || principalType == "group" {
// Additionally see if there are any matching teams since GitHub user search API doesn't cover those.
teamAccts, err := g.githubClient.searchTeams(searchKey, accessToken, config)
if err != nil {
return nil, err
}

for _, acct := range teamAccts {
p := g.toPrincipal(teamType, acct, &token)
principals = append(principals, p)
}
}

return principals, nil
}

Expand Down Expand Up @@ -331,9 +342,7 @@ func (g *ghProvider) GetPrincipal(principalID string, token v3.Token) (v3.Princi
principalType := parts[1]
var acct Account
switch principalType {
case userType:
fallthrough
case orgType:
case userType, orgType:
acct, err = g.githubClient.getUserOrgByID(externalID, accessToken, config)
if err != nil {
return v3.Principal{}, err
Expand All @@ -344,7 +353,7 @@ func (g *ghProvider) GetPrincipal(principalID string, token v3.Token) (v3.Princi
return v3.Principal{}, err
}
default:
return v3.Principal{}, fmt.Errorf("Cannot get the github account due to invalid externalIDType %v", principalType)
return v3.Principal{}, fmt.Errorf("cannot get the github account due to invalid externalIDType %v", principalType)
}

princ := g.toPrincipal(principalType, acct, &token)
Expand Down
Loading