Commit de386c44 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 74983961 c3b176c5
......@@ -15,18 +15,16 @@ module Types
field :author, Types::UserType,
null: false,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } do
authorize :read_user
end
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find },
authorize: :read_user
field :assignees, Types::UserType.connection_type, null: true
field :labels, Types::LabelType.connection_type, null: true
field :milestone, Types::MilestoneType,
null: true,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } do
authorize :read_milestone
end
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find },
authorize: :read_milestone
field :due_date, Types::TimeType, null: true
field :confidential, GraphQL::BOOLEAN_TYPE, null: false
......
......@@ -48,9 +48,7 @@ module Types
field :downvotes, GraphQL::INT_TYPE, null: false
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false
field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline do
authorize :read_pipeline
end
field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline, authorize: :read_pipeline
field :pipelines, Types::Ci::PipelineType.connection_type,
resolver: Resolvers::MergeRequestPipelinesResolver
end
......
......@@ -69,16 +69,14 @@ module Types
field :merge_requests,
Types::MergeRequestType.connection_type,
null: true,
resolver: Resolvers::MergeRequestsResolver do
authorize :read_merge_request
end
resolver: Resolvers::MergeRequestsResolver,
authorize: :read_merge_request
field :merge_request,
Types::MergeRequestType,
null: true,
resolver: Resolvers::MergeRequestsResolver.single do
authorize :read_merge_request
end
resolver: Resolvers::MergeRequestsResolver.single,
authorize: :read_merge_request
field :issues,
Types::IssueType.connection_type,
......
......@@ -7,9 +7,8 @@ module Types
field :project, Types::ProjectType,
null: true,
resolver: Resolvers::ProjectResolver,
description: "Find a project" do
authorize :read_project
end
description: "Find a project",
authorize: :read_project
field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new
end
......
---
title: Use auto-build-image for build job in Auto-DevOps.gitlab-ci.yml
merge_request: 24279
author:
type: changed
# frozen_string_literal: true
GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
Types::BaseField.accepts_definition(:authorize)
......@@ -12,24 +12,34 @@ add a `HTTP_PRIVATE_TOKEN` header.
### Authorization
Fields can be authorized using the same abilities used in the Rails
app. This can be done using the `authorize` helper:
app. This can be done by supplying the `authorize` option:
```ruby
module Types
class QueryType < BaseObject
graphql_name 'Query'
field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
authorize :read_project
end
field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, authorize: :read_project
end
end
```
Fields can be authorized against multiple abilities, in which case all
ability checks must pass. This requires explicitly passing a block to `field`:
```ruby
field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
authorize [:read_project, :another_ability]
end
```
The object found by the resolve call is used for authorization.
This works for authorizing a single record, for authorizing
collections, we should only load what the currently authenticated user
is allowed to view. Preferably we use our existing finders for that.
TIP: **Tip:**
When authorizing collections, try to load only what the currently
authenticated user is allowed to view with our existing finders first.
This minimizes database queries and unnecessary authorization checks of
the loaded records.
## Types
......
......@@ -74,12 +74,11 @@ stages:
build:
stage: build
image: docker:stable-git
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image/master:stable"
services:
- docker:stable-dind
- docker:stable-dind
script:
- setup_docker
- build
- /build/build.sh
only:
- branches
......@@ -491,7 +490,6 @@ rollout 100%:
export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
export CI_APPLICATION_TAG=$CI_COMMIT_SHA
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
......@@ -851,50 +849,6 @@ rollout 100%:
fi
}
function build() {
registry_login
if [[ -f Dockerfile ]]; then
echo "Building Dockerfile-based application..."
docker build \
--build-arg HTTP_PROXY="$HTTP_PROXY" \
--build-arg http_proxy="$http_proxy" \
--build-arg HTTPS_PROXY="$HTTPS_PROXY" \
--build-arg https_proxy="$https_proxy" \
--build-arg FTP_PROXY="$FTP_PROXY" \
--build-arg ftp_proxy="$ftp_proxy" \
--build-arg NO_PROXY="$NO_PROXY" \
--build-arg no_proxy="$no_proxy" \
-t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
else
echo "Building Heroku-based application using gliderlabs/herokuish docker image..."
docker run -i \
-e BUILDPACK_URL \
-e HTTP_PROXY \
-e http_proxy \
-e HTTPS_PROXY \
-e https_proxy \
-e FTP_PROXY \
-e ftp_proxy \
-e NO_PROXY \
-e no_proxy \
--name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build
docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker rm "$CI_CONTAINER_NAME" >/dev/null
echo ""
echo "Configuring $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG docker image..."
docker create --expose 5000 --env PORT=5000 --name="$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" /bin/herokuish procfile start web
docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker rm "$CI_CONTAINER_NAME" >/dev/null
echo ""
fi
echo "Pushing to GitLab Container Registry..."
docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
echo ""
}
function initialize_tiller() {
echo "Checking Tiller..."
......
......@@ -10,21 +10,6 @@ module Gitlab
def self.use(schema_definition)
schema_definition.instrument(:field, Instrumentation.new)
end
def required_permissions
# If the `#authorize` call is used on multiple classes, we add the
# permissions specified on a subclass, to the ones that were specified
# on it's superclass.
@required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
superclass.required_permissions.dup
else
[]
end
end
def authorize(*permissions)
required_permissions.concat(permissions)
end
end
end
end
......@@ -6,8 +6,21 @@ module Gitlab
module AuthorizeResource
extend ActiveSupport::Concern
included do
extend Gitlab::Graphql::Authorize
class_methods do
def required_permissions
# If the `#authorize` call is used on multiple classes, we add the
# permissions specified on a subclass, to the ones that were specified
# on it's superclass.
@required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
superclass.required_permissions.dup
else
[]
end
end
def authorize(*permissions)
required_permissions.concat(permissions)
end
end
def find_object(*args)
......
......@@ -6,19 +6,15 @@ module Gitlab
class Instrumentation
# Replace the resolver for the field with one that will only return the
# resolved object if the permissions check is successful.
#
# Collections are not supported. Apply permissions checks for those at the
# database level instead, to avoid loading superfluous data from the DB
def instrument(_type, field)
field_definition = field.metadata[:type_class]
return field unless field_definition.respond_to?(:required_permissions)
return field if field_definition.required_permissions.empty?
required_permissions = Array.wrap(field.metadata[:authorize])
return field if required_permissions.empty?
old_resolver = field.resolve_proc
new_resolver = -> (obj, args, ctx) do
resolved_obj = old_resolver.call(obj, args, ctx)
checker = build_checker(ctx[:current_user], field_definition.required_permissions)
checker = build_checker(ctx[:current_user], required_permissions)
if resolved_obj.respond_to?(:then)
resolved_obj.then(&checker)
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Gitlab::Graphql::Authorization' do
set(:user) { create(:user) }
let(:test_object) { double(name: 'My name') }
let(:object_type) { object_type_class }
let(:query_type) { query_type_class(object_type, test_object) }
let(:schema) { schema_class(query_type) }
let(:execute) do
schema.execute(
query_string,
context: { current_user: user },
variables: {}
)
end
let(:result) { execute['data'] }
before do
# By default, disallow all permissions.
allow(Ability).to receive(:allowed?).and_return(false)
end
describe 'authorizing with a single permission' do
let(:query_string) { '{ singlePermission() { name } }' }
subject { result['singlePermission'] }
it 'should return the protected field when user has permission' do
permit(:foo)
expect(subject['name']).to eq(test_object.name)
end
it 'should return nil when user is not authorized' do
expect(subject).to be_nil
end
end
describe 'authorizing with an Array of permissions' do
let(:query_string) { '{ permissionCollection() { name } }' }
subject { result['permissionCollection'] }
it 'should return the protected field when user has all permissions' do
permit(:foo, :bar)
expect(subject['name']).to eq(test_object.name)
end
it 'should return nil when user only has one of the permissions' do
permit(:foo)
expect(subject).to be_nil
end
it 'should return nil when user only has none of the permissions' do
expect(subject).to be_nil
end
end
private
def permit(*permissions)
permissions.each do |permission|
allow(Ability).to receive(:allowed?).with(user, permission, test_object).and_return(true)
end
end
def object_type_class
Class.new(Types::BaseObject) do
graphql_name 'TestObject'
field :name, GraphQL::STRING_TYPE, null: true
end
end
def query_type_class(type, object)
Class.new(Types::BaseObject) do
graphql_name 'TestQuery'
field :single_permission, type,
null: true,
authorize: :foo,
resolve: ->(obj, args, ctx) { object }
field :permission_collection, type,
null: true,
resolve: ->(obj, args, ctx) { object } do
authorize [:foo, :bar]
end
end
end
def schema_class(query)
Class.new(GraphQL::Schema) do
use Gitlab::Graphql::Authorize
query(query)
end
end
end
......@@ -100,4 +100,22 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
expect { fake_class.new.find_object }.to raise_error(/Implement #find_object in #{fake_class.name}/)
end
end
describe '#authorize' do
it 'adds permissions from subclasses to those of superclasses when used on classes' do
base_class = Class.new do
include Gitlab::Graphql::Authorize::AuthorizeResource
authorize :base_authorization
end
sub_class = Class.new(base_class) do
authorize :sub_authorization
end
expect(base_class.required_permissions).to contain_exactly(:base_authorization)
expect(sub_class.required_permissions)
.to contain_exactly(:base_authorization, :sub_authorization)
end
end
end
require 'spec_helper'
describe Gitlab::Graphql::Authorize do
describe '#authorize' do
it 'adds permissions from subclasses to those of superclasses when used on classes' do
base_class = Class.new do
extend Gitlab::Graphql::Authorize
authorize :base_authorization
end
sub_class = Class.new(base_class) do
authorize :sub_authorization
end
expect(base_class.required_permissions).to contain_exactly(:base_authorization)
expect(sub_class.required_permissions)
.to contain_exactly(:base_authorization, :sub_authorization)
end
end
end
RSpec::Matchers.define :require_graphql_authorizations do |*expected|
match do |field|
field_definition = field.metadata[:type_class]
expect(field_definition).to respond_to(:required_permissions)
expect(field_definition.required_permissions).to contain_exactly(*expected)
expect(field.metadata[:authorize]).to eq(*expected)
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