diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb index 3c617ecca..f615edbfd 100644 --- a/app/controllers/auth/omniauth_callbacks_controller.rb +++ b/app/controllers/auth/omniauth_callbacks_controller.rb @@ -24,6 +24,22 @@ def google_oauth2 # Callback for Bitbucket. alias bitbucket google_oauth2 + protected + + def github_next(resp) + # -> gitlab: x-next-page is in headers, and not empty + resp.headers.key?("x-next-page") && \ + resp.headers["x-next-page"].present? + end + + def gitlab_next(resp) + # -> github: Link is in headers + # and if we are not on last page, we have a last link + resp.headers.key?("Link") && \ + resp.headers["Link"].include?('rel="last"') && \ + !resp.headers.key?("x-next-page") + end + private # If user does not exist then ask for username and display_name. @@ -70,9 +86,8 @@ def check_membership when "gitlab" if conf["group"].present? # Get user's groups. - server = conf.fetch("server", "") - server = server.presence || "https://gitlab.com" - is_member = member_of("#{server}/api/v4/groups") do |g| + server = conf.fetch("server", "").presence || "https://gitlab.com" + is_member = member_of("#{server}/api/v4/groups", per_page: 100) do |g| g["name"] == conf["group"] end "The Gitlab account isn't in allowed group." unless is_member @@ -99,11 +114,21 @@ def github_member?(conf) end # Get user's teams and check if one match to restriction. - def member_of(url) + # This method uses pagination, and the caller can specify + # the number of teams per page in the `per_page` parameter. + def member_of(url, per_page: nil) # Get user's groups. token = request.env["omniauth.auth"].credentials["token"] - resp = Faraday.get url, access_token: token - teams = JSON.parse resp.body + teams = [] + np = 0 + loop do + np += 1 + resp = Faraday.get url, { page: np, per_page: per_page, + access_token: token }.compact + teams.concat JSON.parse resp.body + # if no last/next page, we stop iteration + break unless gitlab_next(resp) || github_next(resp) + end # Check if the user is member of allowed group. !teams.find_all { |t| yield(t) }.empty? diff --git a/spec/controllers/auth/omniauth_callbacks_controller_spec.rb b/spec/controllers/auth/omniauth_callbacks_controller_spec.rb index a88c77c00..2470cd33c 100644 --- a/spec/controllers/auth/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/auth/omniauth_callbacks_controller_spec.rb @@ -154,6 +154,46 @@ end end + describe "GET custom #gitlab" do + before do + APP_CONFIG["oauth"] = { "gitlab" => { "server": "https://gitlab.com", + "domain" => "", "group" => "" } } + OmniAuth.config.add_mock(:gitlab, + provider: "gitlab", + uid: "12345", + credentials: { token: "1234567890" }, + info: { email: "test@mail.net" }) + request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:gitlab] + create :user, email: "test@mail.net" + end + + context "CUSTOMGITLAB: with group is setted," do + it "when group matches, sign in and redirect to /" do + APP_CONFIG["oauth"]["gitlab"]["group"] = "group" + VCR.use_cassette "api_gitlab_groups" do + get :gitlab + end + expect(response).to redirect_to authenticated_root_url + expect(subject.current_user).not_to eql nil + end + + it "when group not match, redirect to /users/sign_in" do + APP_CONFIG["oauth"]["gitlab"]["group"] = "wrong_group" + VCR.use_cassette "api_gitlab_groups" do + get :gitlab + end + expect(response).to redirect_to new_user_session_url + expect(subject.current_user).to be nil + end + end + + it "when group isn't setted, sign in and redirect to /" do + get :gitlab + expect(response).to redirect_to authenticated_root_url + expect(subject.current_user).not_to eql nil + end + end + describe "GET #gitlab" do before do APP_CONFIG["oauth"] = { "gitlab" => { "domain" => "", "group" => "" } } diff --git a/spec/vcr_cassettes/api_github_orgs.yml b/spec/vcr_cassettes/api_github_orgs.yml index ad9d6f3dc..982e2898e 100644 --- a/spec/vcr_cassettes/api_github_orgs.yml +++ b/spec/vcr_cassettes/api_github_orgs.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/user/orgs?access_token=1234567890 + uri: https://api.github.com/user/orgs?access_token=1234567890&page=1 body: encoding: US-ASCII string: '' @@ -55,9 +55,73 @@ http_interactions: - '0.008503' X-Github-Request-Id: - EB40:69AA:32F133B:7180EE4:597A1020 + Link: + - '; rel="prev", ; rel="next", ; rel="last", ; rel="first"' body: encoding: UTF-8 string: '[{"login":"org"}]' http_version: recorded_at: Thu, 27 Jul 2017 16:09:04 GMT +- request: + method: get + uri: https://api.github.com/user/orgs?access_token=1234567890&page=2 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v0.9.1 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: Ok + headers: + Server: + - GitHub.com + Date: + - Thu, 27 Jul 2017 16:09:04 GMT + Content-Type: + - application/json; charset=utf-8 + # Content-Length: + # - '83' + # Status: + # - 401 Unauthorized + X-Github-Media-Type: + - github.v3; format=json + X-Ratelimit-Limit: + - '60' + X-Ratelimit-Remaining: + - '59' + X-Ratelimit-Reset: + - '1501175344' + Access-Control-Expose-Headers: + - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, + X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: + - "*" + Content-Security-Policy: + - default-src 'none' + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Xss-Protection: + - 1; mode=block + X-Runtime-Rack: + - '0.008503' + X-Github-Request-Id: + - EB40:69AA:32F133B:7180EE4:597A1020 + Link: + - '; rel="prev", ; rel="next", ; rel="first"' + body: + encoding: UTF-8 + string: '[{"login":"org2"}]' + http_version: + recorded_at: Thu, 27 Jul 2017 16:09:04 GMT recorded_with: VCR 3.0.3 diff --git a/spec/vcr_cassettes/api_github_teams.yml b/spec/vcr_cassettes/api_github_teams.yml index 39d4aa235..79c824c01 100644 --- a/spec/vcr_cassettes/api_github_teams.yml +++ b/spec/vcr_cassettes/api_github_teams.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/user/teams?access_token=1234567890 + uri: https://api.github.com/user/teams?access_token=1234567890&page=1 body: encoding: US-ASCII string: '' @@ -55,9 +55,74 @@ http_interactions: - '0.015137' X-Github-Request-Id: - 4EBE:69A6:1B2F78A:3C72533:5979D2BF + Link: + - '; rel="prev", ; rel="next", ; rel="last", ; rel="first"' body: encoding: UTF-8 string: '[{ "name": "team", "organization": { "login": "org" }}]' http_version: recorded_at: Thu, 27 Jul 2017 11:47:11 GMT +- request: + method: get + uri: https://api.github.com/user/teams?access_token=1234567890&page=2 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v0.9.1 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: Ok + headers: + Server: + - GitHub.com + Date: + - Thu, 27 Jul 2017 11:47:11 GMT + Content-Type: + - application/json; charset=utf-8 + # Content-Length: + # - '83' + # Status: + # - 401 Unauthorized + X-Github-Media-Type: + - github.v3; format=json + X-Ratelimit-Limit: + - '60' + X-Ratelimit-Remaining: + - '59' + X-Ratelimit-Reset: + - '1501159631' + Access-Control-Expose-Headers: + - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, + X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: + - "*" + Content-Security-Policy: + - default-src 'none' + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Xss-Protection: + - 1; mode=block + X-Runtime-Rack: + - '0.015137' + X-Github-Request-Id: + - 4EBE:69A6:1B2F78A:3C72533:5979D2BF + Link: + - '; rel="prev", ; rel="next", ; rel="first"' + body: + encoding: UTF-8 + + string: '[{ "name": "team2", "organization": { "login": "org2" }}]' + http_version: + recorded_at: Thu, 27 Jul 2017 11:47:11 GMT recorded_with: VCR 3.0.3 diff --git a/spec/vcr_cassettes/api_gitlab_groups.yml b/spec/vcr_cassettes/api_gitlab_groups.yml index bf37a6242..956413dad 100644 --- a/spec/vcr_cassettes/api_gitlab_groups.yml +++ b/spec/vcr_cassettes/api_gitlab_groups.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://gitlab.com/api/v4/groups?access_token=1234567890 + uri: https://gitlab.com/api/v4/groups?access_token=1234567890&page=1&per_page=100 body: encoding: US-ASCII string: '' @@ -36,9 +36,54 @@ http_interactions: - 83f721d1-af21-4040-991b-a63f9ca2b59c X-Runtime: - '0.031551' + X-Next-Page: + - "3" body: encoding: UTF-8 string: '[{"name":"group"}]' http_version: recorded_at: Thu, 27 Jul 2017 14:17:23 GMT +- request: + method: get + uri: https://gitlab.com/api/v4/groups?access_token=1234567890&page=2&per_page=100 + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - Faraday v0.9.1 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: Ok + headers: + Server: + - nginx + Date: + - Thu, 27 Jul 2017 14:17:23 GMT + Content-Type: + - application/json + # Content-Length: + # - '30' + Cache-Control: + - no-cache + Vary: + - Origin + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 83f721d1-af21-4040-991b-a63f9ca2b59c + X-Runtime: + - '0.031551' + X-Next-Page: + - "" + body: + encoding: UTF-8 + string: '[{"name":"group2"}]' + http_version: + recorded_at: Thu, 27 Jul 2017 14:17:23 GMT recorded_with: VCR 3.0.3