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 ...@@ -15,18 +15,16 @@ module Types
field :author, Types::UserType, field :author, Types::UserType,
null: false, null: false,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } do resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find },
authorize :read_user authorize: :read_user
end
field :assignees, Types::UserType.connection_type, null: true field :assignees, Types::UserType.connection_type, null: true
field :labels, Types::LabelType.connection_type, null: true field :labels, Types::LabelType.connection_type, null: true
field :milestone, Types::MilestoneType, field :milestone, Types::MilestoneType,
null: true, null: true,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } do resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find },
authorize :read_milestone authorize: :read_milestone
end
field :due_date, Types::TimeType, null: true field :due_date, Types::TimeType, null: true
field :confidential, GraphQL::BOOLEAN_TYPE, null: false field :confidential, GraphQL::BOOLEAN_TYPE, null: false
......
...@@ -48,9 +48,7 @@ module Types ...@@ -48,9 +48,7 @@ module Types
field :downvotes, GraphQL::INT_TYPE, null: false field :downvotes, GraphQL::INT_TYPE, null: false
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false
field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline do field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline, authorize: :read_pipeline
authorize :read_pipeline
end
field :pipelines, Types::Ci::PipelineType.connection_type, field :pipelines, Types::Ci::PipelineType.connection_type,
resolver: Resolvers::MergeRequestPipelinesResolver resolver: Resolvers::MergeRequestPipelinesResolver
end end
......
...@@ -69,16 +69,14 @@ module Types ...@@ -69,16 +69,14 @@ module Types
field :merge_requests, field :merge_requests,
Types::MergeRequestType.connection_type, Types::MergeRequestType.connection_type,
null: true, null: true,
resolver: Resolvers::MergeRequestsResolver do resolver: Resolvers::MergeRequestsResolver,
authorize :read_merge_request authorize: :read_merge_request
end
field :merge_request, field :merge_request,
Types::MergeRequestType, Types::MergeRequestType,
null: true, null: true,
resolver: Resolvers::MergeRequestsResolver.single do resolver: Resolvers::MergeRequestsResolver.single,
authorize :read_merge_request authorize: :read_merge_request
end
field :issues, field :issues,
Types::IssueType.connection_type, Types::IssueType.connection_type,
......
...@@ -7,9 +7,8 @@ module Types ...@@ -7,9 +7,8 @@ module Types
field :project, Types::ProjectType, field :project, Types::ProjectType,
null: true, null: true,
resolver: Resolvers::ProjectResolver, resolver: Resolvers::ProjectResolver,
description: "Find a project" do description: "Find a project",
authorize :read_project authorize: :read_project
end
field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new
end 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. ...@@ -12,24 +12,34 @@ add a `HTTP_PRIVATE_TOKEN` header.
### Authorization ### Authorization
Fields can be authorized using the same abilities used in the Rails 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 ```ruby
module Types module Types
class QueryType < BaseObject class QueryType < BaseObject
graphql_name 'Query' graphql_name 'Query'
field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, authorize: :read_project
authorize :read_project
end
end 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. The object found by the resolve call is used for authorization.
This works for authorizing a single record, for authorizing TIP: **Tip:**
collections, we should only load what the currently authenticated user When authorizing collections, try to load only what the currently
is allowed to view. Preferably we use our existing finders for that. authenticated user is allowed to view with our existing finders first.
This minimizes database queries and unnecessary authorization checks of
the loaded records.
## Types ## Types
......
...@@ -74,12 +74,11 @@ stages: ...@@ -74,12 +74,11 @@ stages:
build: build:
stage: build stage: build
image: docker:stable-git image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image/master:stable"
services: services:
- docker:stable-dind - docker:stable-dind
script: script:
- setup_docker - /build/build.sh
- build
only: only:
- branches - branches
...@@ -491,7 +490,6 @@ rollout 100%: ...@@ -491,7 +490,6 @@ rollout 100%:
export DATABASE_URL=${DATABASE_URL-$auto_database_url} export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_APPLICATION_TAG=$CI_COMMIT_SHA
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE=$KUBE_NAMESPACE export TILLER_NAMESPACE=$KUBE_NAMESPACE
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products # 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/') export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
...@@ -851,50 +849,6 @@ rollout 100%: ...@@ -851,50 +849,6 @@ rollout 100%:
fi 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() { function initialize_tiller() {
echo "Checking Tiller..." echo "Checking Tiller..."
......
...@@ -10,21 +10,6 @@ module Gitlab ...@@ -10,21 +10,6 @@ module Gitlab
def self.use(schema_definition) def self.use(schema_definition)
schema_definition.instrument(:field, Instrumentation.new) schema_definition.instrument(:field, Instrumentation.new)
end 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 end
end end
...@@ -6,8 +6,21 @@ module Gitlab ...@@ -6,8 +6,21 @@ module Gitlab
module AuthorizeResource module AuthorizeResource
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do class_methods do
extend Gitlab::Graphql::Authorize 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
def find_object(*args) def find_object(*args)
......
...@@ -6,19 +6,15 @@ module Gitlab ...@@ -6,19 +6,15 @@ module Gitlab
class Instrumentation class Instrumentation
# Replace the resolver for the field with one that will only return the # Replace the resolver for the field with one that will only return the
# resolved object if the permissions check is successful. # 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) def instrument(_type, field)
field_definition = field.metadata[:type_class] required_permissions = Array.wrap(field.metadata[:authorize])
return field unless field_definition.respond_to?(:required_permissions) return field if required_permissions.empty?
return field if field_definition.required_permissions.empty?
old_resolver = field.resolve_proc old_resolver = field.resolve_proc
new_resolver = -> (obj, args, ctx) do new_resolver = -> (obj, args, ctx) do
resolved_obj = old_resolver.call(obj, args, ctx) 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) if resolved_obj.respond_to?(:then)
resolved_obj.then(&checker) 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 ...@@ -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}/) expect { fake_class.new.find_object }.to raise_error(/Implement #find_object in #{fake_class.name}/)
end end
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 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| RSpec::Matchers.define :require_graphql_authorizations do |*expected|
match do |field| match do |field|
field_definition = field.metadata[:type_class] expect(field.metadata[:authorize]).to eq(*expected)
expect(field_definition).to respond_to(:required_permissions)
expect(field_definition.required_permissions).to contain_exactly(*expected)
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