Commit c1fcd730 authored by Timothy Andrew's avatar Timothy Andrew

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`.
parent 4dbfa14e
...@@ -33,10 +33,10 @@ class AccessTokenValidationService ...@@ -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
......
...@@ -31,7 +31,7 @@ module API ...@@ -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
......
...@@ -130,16 +130,17 @@ module Gitlab ...@@ -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
......
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment