diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b4dc975..f78b24f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,12 @@ - [#258](https://github.com/pusher/oauth2_proxy/pull/258) Add IDToken for Azure provider - This PR adds the IDToken into the session for the Azure provider allowing requests to a backend to be identified as a specific user. As a consequence, if you are using a cookie to store the session the cookie will now exceed the 4kb size limit and be split into multiple cookies. This can cause problems when using nginx as a proxy, resulting in no cookie being passed at all. Either increase the proxy_buffer_size in nginx or implement the redis session storage (see https://pusher.github.io/oauth2_proxy/configuration#redis-storage) - [#286](https://github.com/pusher/oauth2_proxy/pull/286) Requests.go updated with useful error messages (@biotom) +- [#274](https://github.com/pusher/oauth2_proxy/pull/274) Supports many github teams with api pagination support (@toshi-miura, @apratina) - [#302](https://github.com/pusher/oauth2_proxy/pull/302) Rewrite dist script (@syscll) - [#304](https://github.com/pusher/oauth2_proxy/pull/304) Add new Logo! :tada: (@JoelSpeed) - [#300](https://github.com/pusher/oauth2_proxy/pull/300) Added userinfo endpoint (@kbabuadze) - [#309](https://github.com/pusher/oauth2_proxy/pull/309) Added support for custom CA when connecting to Redis cache + # v4.0.0 - [#248](https://github.com/pusher/oauth2_proxy/pull/248) Fix issue with X-Auth-Request-Redirect header being ignored diff --git a/providers/github.go b/providers/github.go index ba58bb1e..f9821cb2 100644 --- a/providers/github.go +++ b/providers/github.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "path" + "regexp" "strconv" "strings" @@ -75,8 +76,8 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) { pn := 1 for { params := url.Values{ - "limit": {"200"}, - "page": {strconv.Itoa(pn)}, + "per_page": {"100"}, + "page": {strconv.Itoa(pn)}, } endpoint := &url.URL{ @@ -139,36 +140,90 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) { } `json:"organization"` } - params := url.Values{ - "limit": {"200"}, + type teamsPage []struct { + Name string `json:"name"` + Slug string `json:"slug"` + Org struct { + Login string `json:"login"` + } `json:"organization"` } - endpoint := &url.URL{ - Scheme: p.ValidateURL.Scheme, - Host: p.ValidateURL.Host, - Path: path.Join(p.ValidateURL.Path, "/user/teams"), - RawQuery: params.Encode(), - } - req, _ := http.NewRequest("GET", endpoint.String(), nil) - req.Header.Set("Accept", "application/vnd.github.v3+json") - req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken)) - resp, err := http.DefaultClient.Do(req) - if err != nil { - return false, err - } + pn := 1 + last := 0 + for { + params := url.Values{ + "per_page": {"100"}, + "page": {strconv.Itoa(pn)}, + } - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return false, err - } - if resp.StatusCode != 200 { - return false, fmt.Errorf( - "got %d from %q %s", resp.StatusCode, endpoint.String(), body) - } + endpoint := &url.URL{ + Scheme: p.ValidateURL.Scheme, + Host: p.ValidateURL.Host, + Path: path.Join(p.ValidateURL.Path, "/user/teams"), + RawQuery: params.Encode(), + } - if err := json.Unmarshal(body, &teams); err != nil { - return false, fmt.Errorf("%s unmarshaling %s", err, body) + req, _ := http.NewRequest("GET", endpoint.String(), nil) + req.Header.Set("Accept", "application/vnd.github.v3+json") + req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return false, err + } + + if last == 0 { + // link header may not be obtained + // When paging is not required and all data can be retrieved with a single call + + // Conditions for obtaining the link header. + // 1. When paging is required (Example: When the data size is 100 and the page size is 99 or less) + // 2. When it exceeds the paging frame (Example: When there is only 10 records but the second page is called with a page size of 100) + + // link header at not last page + // ; rel="prev", ; rel="last", ; rel="first" + // link header at last page (doesn't exist last info) + // ; rel="prev", ; rel="first" + + link := resp.Header.Get("Link") + rep1 := regexp.MustCompile(`(?s).*\; rel="last".*`) + i, converr := strconv.Atoi(rep1.ReplaceAllString(link, "$1")) + + // If the last page cannot be taken from the link in the http header, the last variable remains zero + if converr == nil { + last = i + } + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + resp.Body.Close() + return false, err + } + resp.Body.Close() + + if resp.StatusCode != 200 { + return false, fmt.Errorf( + "got %d from %q %s", resp.StatusCode, endpoint.String(), body) + } + + var tp teamsPage + if err := json.Unmarshal(body, &tp); err != nil { + return false, fmt.Errorf("%s unmarshaling %s", err, body) + } + if len(tp) == 0 { + break + } + + teams = append(teams, tp...) + + if pn == last { + break + } + if last == 0 { + break + } + + pn++ } var hasOrg bool diff --git a/providers/github_test.go b/providers/github_test.go index 2d45b841..56611518 100644 --- a/providers/github_test.go +++ b/providers/github_test.go @@ -32,7 +32,7 @@ func testGitHubBackend(payload []string) *httptest.Server { pathToQueryMap := map[string][]string{ "/user": {""}, "/user/emails": {""}, - "/user/orgs": {"limit=200&page=1", "limit=200&page=2", "limit=200&page=3"}, + "/user/orgs": {"page=1&per_page=100", "page=2&per_page=100", "page=3&per_page=100"}, } return httptest.NewServer(http.HandlerFunc(