Implement review comments from @DouweM for !12300.

- Use a struct for scopes, so we can call `scope.if` instead of `scope[:if]`

- Refactor the "remove scopes whose :if condition returns false" logic to use a
  `select` rather than a `reject`.
This commit is contained in:
Timothy Andrew 2017-06-26 04:14:10 +00:00
parent 4dbfa14e16
commit c1fcd730cc
4 changed files with 28 additions and 14 deletions

View file

@ -33,10 +33,10 @@ class AccessTokenValidationService
true true
else else
# Remove any scopes whose `if` condition does not return `true` # Remove any scopes whose `if` condition does not return `true`
scopes = scopes.reject { |scope| scope[:if].presence && !scope[:if].call(request) } scopes = scopes.select { |scope| scope.if.nil? || scope.if.call(request) }
# Check whether the token is allowed access to any of the required scopes. # Check whether the token is allowed access to any of the required scopes.
passed_scope_names = scopes.map { |scope| scope[:name].to_sym } passed_scope_names = scopes.map { |scope| scope.name.to_sym }
token_scope_names = token.scopes.map(&:to_sym) token_scope_names = token.scopes.map(&:to_sym)
Set.new(passed_scope_names).intersection(Set.new(token_scope_names)).present? Set.new(passed_scope_names).intersection(Set.new(token_scope_names)).present?
end end

View file

@ -31,7 +31,7 @@ module API
# the scopes are all aggregated. # the scopes are all aggregated.
def allow_access_with_scope(scopes, options = {}) def allow_access_with_scope(scopes, options = {})
Array(scopes).each do |scope| Array(scopes).each do |scope|
allowed_scopes << { name: scope, if: options[:if] } allowed_scopes << OpenStruct.new(name: scope.to_sym, if: options[:if])
end end
end end

View file

@ -130,16 +130,17 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password) token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
if token && valid_scoped_token?(token, AVAILABLE_SCOPES.map { |scope| { name: scope.to_s }}) if token && valid_scoped_token?(token, AVAILABLE_SCOPES)
Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes)) Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes))
end end
end end
def valid_oauth_token?(token) def valid_oauth_token?(token)
token && token.accessible? && valid_scoped_token?(token, [{ name: "api" }]) token && token.accessible? && valid_scoped_token?(token, ['api'])
end end
def valid_scoped_token?(token, scopes) def valid_scoped_token?(token, scopes)
scopes = scopes.map { |scope| OpenStruct.new(name: scope) }
AccessTokenValidationService.new(token).include_any_scope?(scopes) AccessTokenValidationService.new(token).include_any_scope?(scopes)
end end

View file

@ -1,62 +1,75 @@
require 'spec_helper' require 'spec_helper'
describe AccessTokenValidationService, services: true do describe AccessTokenValidationService, services: true do
def scope(data)
OpenStruct.new(data)
end
describe ".include_any_scope?" do describe ".include_any_scope?" do
let(:request) { double("request") } let(:request) { double("request") }
it "returns true if the required scope is present in the token's scopes" do it "returns true if the required scope is present in the token's scopes" do
token = double("token", scopes: [:api, :read_user]) token = double("token", scopes: [:api, :read_user])
scopes = [scope({ name: :api })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :api }])).to be(true) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end end
it "returns true if more than one of the required scopes is present in the token's scopes" do it "returns true if more than one of the required scopes is present in the token's scopes" do
token = double("token", scopes: [:api, :read_user, :other_scope]) token = double("token", scopes: [:api, :read_user, :other_scope])
scopes = [scope({ name: :api }), scope({ name: :other_scope })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :api }, { name: :other_scope }])).to be(true) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end end
it "returns true if the list of required scopes is an exact match for the token's scopes" do it "returns true if the list of required scopes is an exact match for the token's scopes" do
token = double("token", scopes: [:api, :read_user, :other_scope]) token = double("token", scopes: [:api, :read_user, :other_scope])
scopes = [scope({ name: :api }), scope({ name: :read_user }), scope({ name: :other_scope })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :api }, { name: :read_user }, { name: :other_scope }])).to be(true) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end end
it "returns true if the list of required scopes contains all of the token's scopes, in addition to others" do it "returns true if the list of required scopes contains all of the token's scopes, in addition to others" do
token = double("token", scopes: [:api, :read_user]) token = double("token", scopes: [:api, :read_user])
scopes = [scope({ name: :api }), scope({ name: :read_user }), scope({ name: :other_scope })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :api }, { name: :read_user }, { name: :other_scope }])).to be(true) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end end
it 'returns true if the list of required scopes is blank' do it 'returns true if the list of required scopes is blank' do
token = double("token", scopes: []) token = double("token", scopes: [])
scopes = []
expect(described_class.new(token, request: request).include_any_scope?([])).to be(true) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end end
it "returns false if there are no scopes in common between the required scopes and the token scopes" do it "returns false if there are no scopes in common between the required scopes and the token scopes" do
token = double("token", scopes: [:api, :read_user]) token = double("token", scopes: [:api, :read_user])
scopes = [scope({ name: :other_scope })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :other_scope }])).to be(false) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
end end
context "conditions" do context "conditions" do
it "ignores any scopes whose `if` condition returns false" do it "ignores any scopes whose `if` condition returns false" do
token = double("token", scopes: [:api, :read_user]) token = double("token", scopes: [:api, :read_user])
scopes = [scope({ name: :api, if: ->(_) { false } })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :api, if: ->(_) { false } }])).to be(false) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
end end
it "does not ignore scopes whose `if` condition is not set" do it "does not ignore scopes whose `if` condition is not set" do
token = double("token", scopes: [:api, :read_user]) token = double("token", scopes: [:api, :read_user])
scopes = [scope({ name: :api, if: ->(_) { false } }), scope({ name: :read_user })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :api, if: ->(_) { false } }, { name: :read_user }])).to be(true) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end end
it "does not ignore scopes whose `if` condition returns true" do it "does not ignore scopes whose `if` condition returns true" do
token = double("token", scopes: [:api, :read_user]) token = double("token", scopes: [:api, :read_user])
scopes = [scope({ name: :api, if: ->(_) { true } }), scope({ name: :read_user, if: ->(_) { false } })]
expect(described_class.new(token, request: request).include_any_scope?([{ name: :api, if: ->(_) { true } }, { name: :read_user, if: ->(_) { false } }])).to be(true) expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end end
end end
end end