Commit abfb082a authored by Bob Van Landuyt's avatar Bob Van Landuyt

Authorize project access with external service

When this feature is enabled, users will not be able to see any cross
project references.

When a user tries to view a project we will first validate if the user
has access to the classification label assigned to the project.

When no classification label is assigned to a project, a default label
is used. This default can be defined in the settings.
parent fb4c0b7e
class Projects::ApplicationController < ApplicationController
prepend EE::Projects::ApplicationController
include RoutableActions
skip_before_action :authenticate_user!
......
......@@ -213,7 +213,11 @@ class Issue < ActiveRecord::Base
.preload(preload)
.reorder('issue_link_id')
Ability.issues_readable_by_user(related_issues, current_user)
cross_project_filter = -> (issues) { issues.where(project: project) }
Ability.issues_readable_by_user(
related_issues, current_user,
filters: { read_cross_project: cross_project_filter }
)
end
# Returns boolean if a related branch exists for the current issue
......
require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base
prepend EE::BasePolicy
desc "User is an instance admin"
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
......
......@@ -29,7 +29,7 @@ module Boards
{ project_ids: [parent.id], group_ids: [parent.group&.id] }
end
milestone = MilestonesFinder.new(finder_params).execute.find_by_id(milestone_id)
milestone = MilestonesFinder.new(finder_params).find_by(id: milestone_id)
params[:milestone_id] = milestone&.id
end
......
......@@ -927,5 +927,8 @@
.col-sm-10
= f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
- if License.feature_available?(:external_authorization_service)
= render partial: 'external_authorization_service_form', locals: { f: f }
.form-actions
= f.submit 'Save', class: 'btn btn-save'
......@@ -8,6 +8,7 @@
= render "layouts/header/ee_license_banner"
= render "layouts/broadcast"
= render 'layouts/header/ee/geo_secondary_banner'
= render "layouts/nav/ee/classification_level_banner"
= yield :flash_message
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
......
......@@ -37,6 +37,8 @@
%span.light (optional)
= f.text_area :description, class: "form-control", rows: 3, maxlength: 250
= render 'projects/ee/classification_policy_settings', f: f
- unless @project.empty_repo?
.form-group
= f.label :default_branch, "Default Branch", class: 'label-light'
......
......@@ -180,6 +180,9 @@ ActiveRecord::Schema.define(version: 20180215143644) do
t.boolean "mirror_available", default: true, null: false
t.string "auto_devops_domain"
t.integer "default_project_creation", default: 2, null: false
t.boolean "external_authorization_service_enabled", default: false, null: false
t.string "external_authorization_service_url"
t.string "external_authorization_service_default_label"
end
create_table "approvals", force: :cascade do |t|
......@@ -1914,6 +1917,7 @@ ActiveRecord::Schema.define(version: 20180215143644) do
t.boolean "pull_mirror_available_overridden"
t.integer "jobs_cache_index"
t.boolean "mirror_overwrites_diverged_branches"
t.string "external_authorization_classification_label"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
......@@ -76,3 +76,6 @@
justify-content: center;
}
}
.classification-label {
background-color: $common-red;
}
......@@ -12,6 +12,10 @@ module EE
attrs << :default_project_creation
end
if License.feature_available?(:external_authorization_service)
attrs += EE::ApplicationSettingsHelper.external_authorization_service_attributes
end
attrs
end
end
......
module EE
module Boards
module IssuesController
extend ActiveSupport::Concern
include ControllerWithCrossProjectAccessCheck
prepended do
requires_cross_project_access if: -> { board.group_board? }
end
def issues_finder
return super unless board.group_board?
......
module EE
module Groups
module ApplicationController
extend ActiveSupport::Concern
def check_group_feature_available!(feature)
render_404 unless group.feature_available?(feature)
end
......
module EE
module Groups
module GroupMembersController
extend ActiveSupport::Concern
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def override
@group_member = @group.group_members.find(params[:id])
......
module EE
module GroupsController
extend ActiveSupport::Concern
def group_params_attributes
super + group_params_ee
end
......
module EE
module Projects
module ApplicationController
extend ::Gitlab::Utils::Override
override :handle_not_found_or_authorized
def handle_not_found_or_authorized(project)
return super unless project
label = project.external_authorization_classification_label
rejection_reason = nil
unless EE::Gitlab::ExternalAuthorization.access_allowed?(current_user, label)
rejection_reason = EE::Gitlab::ExternalAuthorization.rejection_reason(current_user, label)
rejection_reason ||= _('External authorization denied access to this project')
end
if rejection_reason
access_denied!(rejection_reason)
else
super
end
end
end
end
end
......@@ -20,6 +20,7 @@ module EE
mirror
mirror_trigger_builds
mirror_user_id
external_authorization_classification_label
]
end
end
......
......@@ -34,5 +34,17 @@ module EE
:mirror_capacity_threshold
]
end
def self.external_authorization_service_attributes
[
:external_authorization_service_enabled,
:external_authorization_service_url,
:external_authorization_service_default_label
]
end
def self.possible_licensed_attributes
repository_mirror_attributes + external_authorization_service_attributes
end
end
end
......@@ -10,5 +10,27 @@ module EE
super
end
end
private
def get_group_sidebar_links
links = super
if can?(current_user, :read_cross_project)
if @group.feature_available?(:contribution_analytics) || show_promotions?
links << :contribution_analytics
end
if @group.feature_available?(:group_issue_boards)
links << :boards
end
if @group.feature_available?(:epics)
links << :epics
end
end
links
end
end
end
......@@ -5,5 +5,15 @@ module EE
can?(current_user, :"change_#{rule}", @project)
end
def external_classification_label_help_message
default_label = ::Gitlab::CurrentSettings.current_application_settings
.external_authorization_service_default_label
s_(
"ExternalAuthorizationService|When no classification label is set the "\
"default label `%{default_label}` will be used."
) % { default_label: default_label }
end
end
end
......@@ -185,6 +185,12 @@ module Elastic
# documents gated by that project feature - e.g., "issues". The feature's
# visibility level must be taken into account.
def project_ids_query(user, project_ids, public_and_internal_projects, feature = nil)
# When reading cross project is not allowed, only allow searching a
# a single project, so the `:read_*` ability is only checked once.
unless Ability.allowed?(user, :read_cross_project)
project_ids = [] if project_ids.is_a?(Array) && project_ids.size > 1
end
# At least one condition must be present, so pick no projects for
# anonymous users.
# Pick private, internal and public projects the user is a member of.
......
......@@ -66,7 +66,7 @@ module Elastic
bool: {
should: [
{ term: { author_id: user.id } },
{ terms: { project_id: user.authorized_projects.pluck(:id) } },
{ terms: { project_id: authorized_project_ids_for_user(user) } },
{
bool: {
filter: { terms: { visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL] } },
......@@ -88,6 +88,14 @@ module Elastic
query_hash[:query][:bool][:filter] = filter
query_hash
end
def self.authorized_project_ids_for_user(user)
if Ability.allowed?(user, :read_cross_project)
user.authorized_projects.pluck(:id)
else
[]
end
end
end
end
end
......@@ -39,6 +39,15 @@ module EE
validates :elasticsearch_aws_region,
presence: { message: "can't be blank when using aws hosted elasticsearch" },
if: ->(setting) { setting.elasticsearch_indexing? && setting.elasticsearch_aws? }
validates :external_authorization_service_url,
:external_authorization_service_default_label,
presence: true,
if: :external_authorization_service_enabled?
validates :external_authorization_service_url,
url: true,
if: :external_authorization_service_enabled?
end
module ClassMethods
......@@ -103,6 +112,12 @@ module EE
}
end
def external_authorization_service_enabled
License.feature_available?(:external_authorization_service) && super
end
alias_method :external_authorization_service_enabled?,
:external_authorization_service_enabled
private
def mirror_max_delay_in_minutes
......
module EE
module Issue
extend ::Gitlab::Utils::Override
# override
def check_for_spam?
author.support_bot? || super
......@@ -34,6 +36,23 @@ module EE
super if supports_weight?
end
# The functionality here is duplicated from the `IssuePolicy` and the
# `EE::IssuePolicy` for better performace
#
# Make sure to keep this in sync with the policies.
override :readable_by?
def readable_by?(user)
return super if user.full_private_access?
super && ::EE::Gitlab::ExternalAuthorization
.access_allowed?(user, project.external_authorization_classification_label)
end
override :publicly_visible?
def publicly_visible?
super && !::EE::Gitlab::ExternalAuthorization.enabled?
end
def supports_weight?
project&.feature_available?(:issue_weights)
end
......
......@@ -457,6 +457,13 @@ module EE
::Gitlab::CurrentSettings.mirror_available
end
def external_authorization_classification_label
return nil unless feature_available?(:external_authorization_service)
super || ::Gitlab::CurrentSettings.current_application_settings
.external_authorization_service_default_label
end
private
def set_override_pull_mirror_available
......
......@@ -53,6 +53,7 @@ class License < ActiveRecord::Base
reject_unsigned_commits
commit_committer_check
project_creation_level
external_authorization_service
].freeze
EEU_FEATURES = EEP_FEATURES + %i[
......@@ -133,6 +134,7 @@ class License < ActiveRecord::Base
multiple_ldap_servers
object_storage
repository_size_limit
external_authorization_service
].freeze
validate :valid_license
......
module EE
module BasePolicy
extend ActiveSupport::Concern
prepended do
condition(:external_authorization_enabled, scope: :global, score: 0) do
::EE::Gitlab::ExternalAuthorization.enabled?
end
rule { external_authorization_enabled & ~admin & ~auditor }.policy do
prevent :read_cross_project
end
end
end
end
......@@ -15,6 +15,14 @@ module EE
with_scope :subject
condition(:deploy_board_disabled) { !@subject.feature_available?(:deploy_board) }
with_scope :subject
condition(:classification_label_authorized, score: 32) do
EE::Gitlab::ExternalAuthorization.access_allowed?(
@user,
@subject.external_authorization_classification_label
)
end
with_scope :global
condition(:is_development) { Rails.env.development? }
......@@ -97,6 +105,22 @@ module EE
rule { admin | (commit_committer_check_disabled_globally & can?(:master_access)) }.enable :change_commit_committer_check
rule { owner | reporter }.enable :build_read_project
rule { ~can?(:read_cross_project) & ~classification_label_authorized }.policy do
# Preventing access here still allows the projects to be listed. Listing
# projects doesn't check the `:read_project` ability. But instead counts
# on the `project_authorizations` table.
#
# All other actions should explicitly check read project, which would
# trigger the `classification_label_authorized` condition.
prevent :guest_access
prevent :public_access
prevent :public_user_access
prevent :reporter_access
prevent :developer_access
prevent :master_access
prevent :owner_access
end
end
end
end
class EpicPolicy < BasePolicy
delegate { @subject.group }
rule { can?(:read_epic) }.enable :read_epic_iid
end
- if License.feature_available?(:external_authorization_service)
%fieldset
%legend
= _('External Classification Policy Authorization')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :external_authorization_service_enabled do
= f.check_box :external_authorization_service_enabled
Enable classification control using an external service
%span.help-block
If enabled, access to projects will be validated on an external service
using their classification label.
= link_to icon('question-circle'), help_page_path('#')
.form-group
= f.label :external_authorization_service_url, _('Service URL'), class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :external_authorization_service_url, class: 'form-control'
.form-group
= f.label :external_authorization_service_default_label, _('Default classification label'), class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :external_authorization_service_default_label, class: 'form-control'
- if EE::Gitlab::ExternalAuthorization.enabled? && @project
= content_for :header_content do
%span.label.color-label.classification-label.has-tooltip{ title: s_('ExternalAuthorizationService|Classification label') }
= sprite_icon('lock-open', size: 8, css_class: 'inline')
= @project.external_authorization_classification_label
- return unless group.feature_available?(:epics)
- return unless group_sidebar_link?(:epics)
- epics = EpicsFinder.new(current_user, group_id: @group.id).execute
- epics_items = ['epics#show', 'epics#index', 'roadmap#show']
......
- if ::EE::Gitlab::ExternalAuthorization.enabled?
.form-group
= f.label :external_authorization_classification_label, class: 'label-light' do
= s_('ExternalAuthorizationService|Classification Label')
%span.light (optional)
= f.text_field :external_authorization_classification_label, class: "form-control"
%span.help-block
= external_classification_label_help_message
class AddExternalClassificationAuthorizationSettingsToApplictionSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default :application_settings,
:external_authorization_service_enabled,
:boolean,
default: false
add_column :application_settings,
:external_authorization_service_url,
:string
add_column :application_settings,
:external_authorization_service_default_label,
:string
end
def down
remove_column :application_settings,
:external_authorization_service_default_label
remove_column :application_settings,
:external_authorization_service_url
remove_column :application_settings,
:external_authorization_service_enabled
end
end
class AddExternalAuthorizationServiceClassificationLabelToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :projects,
:external_authorization_classification_label,
:string
end
end
module EE
module Gitlab
module ExternalAuthorization
RequestFailed = Class.new(StandardError)
def self.access_allowed?(user, label)
return true unless enabled?
return false unless user
access_for_user_to_label(user, label).has_access?
end
def self.rejection_reason(user, label)
return nil unless enabled?
return nil unless user
access_for_user_to_label(user, label).reason
end
def self.access_for_user_to_label(user, label)
if RequestStore.active?
RequestStore.fetch("external_authorisation:user-#{user.id}:label-#{label}") do
EE::Gitlab::ExternalAuthorization::Access.new(user, label).load!
end
else
EE::Gitlab::ExternalAuthorization::Access.new(user, label).load!
end
end
def self.enabled?
::Gitlab::CurrentSettings
.current_application_settings
.external_authorization_service_enabled?
end
def self.service_url
::Gitlab::CurrentSettings
.current_application_settings
.external_authorization_service_url
end
end
end
end
module EE
module Gitlab
module ExternalAuthorization
class Access
attr_reader :access, :reason, :loaded_at
def initialize(user, label)
@user, @label = user, label
end
def loaded?
loaded_at && (loaded_at > Cache::VALIDITY_TIME.ago)
end
def has_access?
@access
end
def load!
load_from_cache
load_from_service unless loaded?
self
end
private
def load_from_cache
@access, @reason, @loaded_at = cache.load
end
def load_from_service
response = Client.build(@user, @label).request_access
@access = response.successful?
@reason = response.reason
@loaded_at = Time.now
cache.store(@access, @reason, @loaded_at) if response.valid?
rescue EE::Gitlab::ExternalAuthorization::RequestFailed => e
@access = false
@reason = e.message
@loaded_at = Time.now
end
def cache
@cache ||= Cache.new(@user, @label)
end
end
end
end
end
module EE
module Gitlab
module ExternalAuthorization
class Cache
VALIDITY_TIME = 6.hours
def initialize(user, label)
@user, @label = user, label
end
def load
@access, @reason, @refreshed_at = ::Gitlab::Redis::Cache.with do |redis|
redis.hmget(cache_key, :access, :reason, :refreshed_at)
end
[access, reason, refreshed_at]
end
def store(new_access, new_reason, new_refreshed_at)
::Gitlab::Redis::Cache.with do |redis|
redis.pipelined do
redis.mapped_hmset(
cache_key,
{
access: new_access.to_s,
reason: new_reason.to_s,
refreshed_at: new_refreshed_at.to_s
}
)
redis.expire(cache_key, VALIDITY_TIME)
end
end
end
private
def access
::Gitlab::Utils.to_boolean(@access)
end
def reason
# `nil` if the cached value was an empty string
return nil unless @reason.present?
@reason
end
def refreshed_at
# Don't try to parse a time if there was no cache
return nil unless @refreshed_at.present?
Time.parse(@refreshed_at)
end
def cache_key
"external_authorization:user-#{@user.id}:label-#{@label}"
end
end
end
end
end
module EE
module Gitlab
module ExternalAuthorization
class Client
REQUEST_HEADERS = {
'Content-Type' => 'application/json',
'Accept' => 'application/json'
}.freeze
TIMEOUT = 0.5
def self.build(user, label)
new(
::EE::Gitlab::ExternalAuthorization.service_url,
user,
label
)
end
def initialize(url, user, label)
@url, @user, @label = url, user, label
end
def request_access
response = Excon.post(
@url,
headers: REQUEST_HEADERS,
body: body.to_json,
connect_timeout: TIMEOUT,
read_timeout: TIMEOUT,
write_timeout: TIMEOUT
)
EE::Gitlab::ExternalAuthorization::Response.new(response)
rescue Excon::Error => e
raise EE::Gitlab::ExternalAuthorization::RequestFailed.new(e)
end
private
def body
@body ||= begin
body = {
user_identifier: @user.email,
project_classification_label: @label
}
if @user.ldap_identity
body[:user_ldap_dn] = @user.ldap_identity.extern_uid
end
body
end
end
end
end
end
end
module EE
module Gitlab
module ExternalAuthorization
class Response
include ::Gitlab::Utils::StrongMemoize
def initialize(excon_response)
@excon_response = excon_response
end
def valid?
@excon_response && [200, 401].include?(@excon_response.status)
end
def successful?
valid? && @excon_response.status == 200
end
def reason
parsed_response['reason'] if parsed_response
end
private
def parsed_response
strong_memoize(:parsed_response) { parse_response! }
end
def parse_response!
JSON.parse(@excon_response.body)
rescue JSON::JSONError
# The JSON response is optional, so don't fail when it's missing
nil
end
end
end
end
end
......@@ -47,38 +47,50 @@ describe Admin::ApplicationSettingsController do
expect(ApplicationSetting.current.elasticsearch_url).to contain_exactly(settings[:elasticsearch_url])
end
it 'does not update mirror settings when repository mirrors unlicensed' do
stub_licensed_features(repository_mirrors: false)
shared_examples 'settings for licensed features' do
it 'does not update settings when licesed feature is not available' do
stub_licensed_features(feature => false)
attribute_names = settings.keys.map(&:to_s)
settings = {
mirror_max_delay: 12,
mirror_max_capacity: 2,
mirror_capacity_threshold: 2
}
settings.each do |setting, _value|
expect do
put :update, application_setting: settings
end.not_to change(ApplicationSetting.current.reload, setting)
expect { put :update, application_setting: settings }
.not_to change { ApplicationSetting.current.reload.attributes.slice(*attribute_names) }
end
end
it 'updates mirror settings when repository mirrors is licensed' do
stub_licensed_features(repository_mirrors: true)
it 'updates settings when the feature is available' do
stub_licensed_features(feature => true)
mirror_delay = (Gitlab::Mirror.min_delay_upper_bound / 60) + 1
put :update, application_setting: settings
settings = {
mirror_max_delay: mirror_delay,
mirror_max_capacity: 2,
mirror_capacity_threshold: 2
}
settings.each do |attribute, value|
expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
end
end
end
put :update, application_setting: settings
context 'mirror settings' do
let(:settings) do
{
mirror_max_delay: (Gitlab::Mirror.min_delay_upper_bound / 60) + 1,
mirror_max_capacity: 200,
mirror_capacity_threshold: 2
}
end
let(:feature) { :repository_mirrors }
settings.each do |setting, value|
expect(ApplicationSetting.current.public_send(setting)).to eq(value)
it_behaves_like 'settings for licensed features'
end
context 'external policy classification settings' do
let(:settings) do
{
external_authorization_service_enabled: true,
external_authorization_service_url: 'https://custom.service/',
external_authorization_service_default_label: 'default'
}
end
let(:feature) { :external_authorization_service }
it_behaves_like 'settings for licensed features'
end
it 'updates the default_project_creation for string value' do
......
require 'spec_helper'
describe Boards::IssuesController do
include ExternalAuthorizationServiceHelpers
let(:group) { create(:group) }
let(:project_1) { create(:project, namespace: group) }
let(:project_2) { create(:project, namespace: group) }
......@@ -77,6 +79,7 @@ describe Boards::IssuesController do
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
end
......@@ -87,6 +90,26 @@ describe Boards::IssuesController do
end
end
context 'with external authorization' do
before do
sign_in(user)
enable_external_authorization_service
end
it 'returns a 404 for group boards' do
get :index, board_id: board
expect(response).to have_gitlab_http_status(404)
end
it 'is successful for project boards' do
project_board = create(:board, project: project_1)
list_issues(user: user, board: project_board)
expect(response).to have_gitlab_http_status(200)
end
end
def list_issues(user:, board:, list: nil)
sign_in(user)
......
require 'spec_helper'
describe Dashboard::GroupsController do
include ExternalAuthorizationServiceHelpers
before do
sign_in create(:user)
end
describe '#index' do
it 'works when the external authorization service is enabled' do
enable_external_authorization_service
get :index
expect(response).to have_gitlab_http_status(200)
end
end
end
require 'spec_helper'
describe Dashboard::LabelsController do
before do
sign_in create(:user)
end
describe '#index' do
subject { get :index, format: :json }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
describe Dashboard::MilestonesController do
before do
sign_in create(:user)
end
describe '#index' do
subject { get :index }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
describe Dashboard::ProjectsController do
include ExternalAuthorizationServiceHelpers
before do
sign_in create(:user)
end
describe '#index' do
subject { get :index }
it 'it works when the external authorization service is enabled' do
enable_external_authorization_service
get :index
expect(response).to have_gitlab_http_status(200)
end
end
end
require 'spec_helper'
describe Dashboard::TodosController do
before do
sign_in create(:user)
end
describe '#index' do
subject { get :index }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
describe EE::Projects::ApplicationController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
before do
sign_in user
end
render_views
describe '#handle_not_found_or_authorized' do
controller(::Projects::ApplicationController) do
def show
render nothing: true
end
end
let(:project) { create(:project) }
before do
project.add_developer(user)
end
it 'renders a 200 when the service allows access to the project' do
external_service_allow_access(user, project)
get :show, namespace_id: project.namespace.to_param, id: project.to_param
expect(response).to have_gitlab_http_status(200)
end
it 'renders a 404 when the service denies access to the project' do
external_service_deny_access(user, project)
get :show, namespace_id: project.namespace.to_param, id: project.to_param
expect(response).to have_gitlab_http_status(404)
expect(response.body).to match("External authorization denied access to this project")
end
end
end
......@@ -93,4 +93,10 @@ describe Groups::AnalyticsController do
expect { get :show, group_id: group.path }.not_to exceed_query_limit(control_count)
end
end
describe 'GET #index' do
subject { get :show, group_id: group.to_param }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
describe Groups::AvatarsController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
before do
group.add_owner(user)
sign_in(user)
end
it 'works when external authorization service is enabled' do
enable_external_authorization_service
delete :destroy, group_id: group
expect(response).to have_gitlab_http_status(302)
end
end
......@@ -2,9 +2,10 @@ require 'spec_helper'
describe Groups::BoardsController do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:user) { create(:user) }
before do
allow(Ability).to receive(:allowed?).and_call_original
group.add_master(user)
sign_in(user)
stub_licensed_features(group_issue_boards: true)
......@@ -64,6 +65,10 @@ describe Groups::BoardsController do
end
end
it_behaves_like 'disabled when using an external authorization service' do
subject { list_boards }
end
def list_boards(format: :html)
get :index, group_id: group, format: format
end
......@@ -125,6 +130,10 @@ describe Groups::BoardsController do
end
end
it_behaves_like 'disabled when using an external authorization service' do
subject { read_board board: board }
end
def read_board(board:, format: :html)
get :show, group_id: group,
id: board.to_param,
......
require 'spec_helper'
describe Groups::ChildrenController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
group.add_owner(user)
sign_in(user)
end
it 'works when external authorization service is enabled' do
enable_external_authorization_service
get :index, group_id: group, format: :json
expect(response).to have_gitlab_http_status(200)
end
end
......@@ -75,6 +75,10 @@ describe Groups::EpicsController do
expect(assigns(:epics).current_page).to eq(last_page)
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'disabled when using an external authorization service' do
subject { get :index, group_id: group }
end
end
context 'when format is JSON' do
......@@ -133,6 +137,14 @@ describe Groups::EpicsController do
expect(response.content_type).to eq 'text/html'
end
end
it_behaves_like 'disabled when using an external authorization service' do
subject { show_epic }
before do
group.add_developer(user)
end
end
end
context 'when format is JSON' do
......@@ -188,6 +200,12 @@ describe Groups::EpicsController do
expect(response).to have_http_status(404)
end
end
it_behaves_like 'disabled when using an external authorization service' do
before do
group.add_developer(user)
end
end
end
describe '#create' do
......@@ -216,6 +234,8 @@ describe Groups::EpicsController do
expect(JSON.parse(response.body)).to eq({ 'web_url' => group_epic_path(group, Epic.last) })
end
it_behaves_like 'disabled when using an external authorization service'
end
context 'when required parameter is missing' do
......
require 'spec_helper'
describe Groups::GroupMembersController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:group) { create(:group, :public, :access_requestable) }
let(:membership) { create(:group_member, group: group) }
before do
group.add_owner(user)
sign_in(user)
end
context 'with external authorization enabled' do
before do
enable_external_authorization_service
end
describe 'GET #index' do
it 'is successful' do
get :index, group_id: group
expect(response).to have_gitlab_http_status(200)
end
end
describe 'POST #create' do
it 'is successful' do
post :create, group_id: group, users: user, access_level: Gitlab::Access::GUEST
expect(response).to have_gitlab_http_status(302)
end
end
describe 'PUT #update' do
it 'is successful' do
put :update,
group_member: { access_level: Gitlab::Access::GUEST },
group_id: group,
id: membership,
format: :js
expect(response).to have_gitlab_http_status(200)
end
end
describe 'DELETE #destroy' do
it 'is successful' do
delete :destroy, group_id: group, id: membership
expect(response).to have_gitlab_http_status(302)
end
end
describe 'POST #destroy' do
it 'is successful' do
sign_in(create(:user))
post :request_access, group_id: group
expect(response).to have_gitlab_http_status(302)
end
end
describe 'POST #approve_request_access' do
it 'is successful' do
access_request = create(:group_member, :access_request, group: group)
post :approve_access_request, group_id: group, id: access_request
expect(response).to have_gitlab_http_status(302)
end
end
describe 'DELETE #leave' do
it 'is successful' do
group.add_owner(create(:user))
delete :leave, group_id: group
expect(response).to have_gitlab_http_status(302)
end
end
describe 'POST #resend_invite' do
it 'is successful' do
post :resend_invite, group_id: group, id: membership
expect(response).to have_gitlab_http_status(302)
end
end
describe 'POST #override' do
let(:group) { create(:group_with_ldap_group_link) }
it 'is successful' do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(user, :override_group_member, membership) { true }
post :override,
group_id: group,
id: membership,
group_member: { override: true },
format: :js
expect(response).to have_gitlab_http_status(200)
end
end
end
end
require 'spec_helper'
describe GroupsController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
group.add_owner(user)
sign_in(user)
end
context 'with external authorization service enabled' do
before do
enable_external_authorization_service
end
describe 'GET #show' do
it 'is successful' do
get :show, id: group.to_param
expect(response).to have_gitlab_http_status(200)
end
it 'does not allow other formats' do
get :show, id: group.to_param, format: :atom
expect(response).to have_gitlab_http_status(404)
end
end
describe 'GET #edit' do
it 'is successful' do
get :edit, id: group.to_param
expect(response).to have_gitlab_http_status(200)
end
end
describe 'GET #new' do
it 'is successful' do
get :new
expect(response).to have_gitlab_http_status(200)
end
end
describe 'GET #index' do
it 'is successful' do
get :index
# Redirects to the dashboard
expect(response).to have_gitlab_http_status(302)
end
end
describe 'POST #create' do
it 'creates a group' do
expect do
post :create, group: { name: 'a name', path: 'a-name' }
end.to change { Group.count }.by(1)
end
end
describe 'PUT #update' do
it 'updates a group' do
expect do
put :update, id: group.to_param, group: { name: 'world' }
end.to change { group.reload.name }
end
end
describe 'DELETE #destroy' do
it 'deletes the group' do
delete :destroy, id: group.to_param
expect(response).to have_gitlab_http_status(302)
end
end
end
describe 'GET #activity' do
subject { get :activity, id: group.to_param }
it_behaves_like 'disabled when using an external authorization service'
end
describe 'GET #issues' do
subject { get :issues, id: group.to_param }
it_behaves_like 'disabled when using an external authorization service'
end
describe 'GET #merge_requests' do
subject { get :merge_requests, id: group.to_param }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
describe Groups::LabelsController do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
group.add_owner(user)
sign_in(user)
end
describe 'GET #index' do
subject { get :index, group_id: group.to_param }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
describe Groups::MilestonesController do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
group.add_owner(user)
sign_in(user)
end
describe 'GET #index' do
subject { get :index, group_id: group.to_param }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
describe Groups::Settings::CiCdController do
include ExternalAuthorizationServiceHelpers
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
group.add_master(user)
sign_in(user)
end
context 'with external authorization enabled' do
before do
enable_external_authorization_service
end
describe 'GET #show' do
it 'renders show with 200 status code' do
get :show, group_id: group
expect(response).to have_gitlab_http_status(200)
end
end
end
end
require 'spec_helper'
describe Groups::VariablesController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:variable) { create(:ci_group_variable, group: group) }
before do
group.add_owner(user)
sign_in(user)
end
context 'with external authorization enabled' do
before do
enable_external_authorization_service
end
describe 'GET #show' do
let!(:variable) { create(:ci_group_variable, group: group) }
it 'is successful' do
get :show, group_id: group, format: :json
expect(response).to have_gitlab_http_status(200)
end
end
describe 'PATCH #update' do
let!(:variable) { create(:ci_group_variable, group: group) }
let(:owner) { group }
it 'is successful' do
patch :update,
group_id: group,
variables_attributes: [{ id: variable.id, key: 'hello' }],
format: :json
expect(response).to have_gitlab_http_status(200)
end
end
end
end
require 'spec_helper'
describe Projects::BoardsController do
include Rails.application.routes.url_helpers
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
allow(Ability).to receive(:allowed?).and_call_original
sign_in(user)
end
......@@ -22,6 +25,10 @@ describe Projects::BoardsController do
expect(parsed_response.length).to eq 2
end
it_behaves_like 'unauthorized when external service denies access' do
subject { list_boards }
end
def list_boards(format: :html)
get :index, namespace_id: project.namespace,
project_id: project,
......
require('spec_helper')
describe Projects::IssuesController do
include Rails.application.routes.url_helpers
let(:namespace) { create(:group, :public) }
let(:project) { create(:project_empty_repo, :public, namespace: namespace) }
let(:user) { create(:user) }
describe 'GET #index' do
before do
sign_in user
project.add_developer(user)
end
it_behaves_like 'unauthorized when external service denies access' do
subject { get :index, namespace_id: project.namespace, project_id: project }
end
end
describe 'POST export_csv' do
let(:user) { create(:user) }
let(:viewer) { user }
let(:issue) { create(:issue, project: project) }
let(:globally_licensed) { false }
......
require 'spec_helper'
describe ProjectsController do
include ExternalAuthorizationServiceHelpers
let(:project) { create(:project) }
let(:user) { create(:user) }
......@@ -42,7 +44,7 @@ describe ProjectsController do
stub_licensed_features(repository_mirrors: false)
end
it 'has mirror enabled in new project' do
it 'has mirror disabled in new project' do
post :create, project: params
created_project = Project.find_by_path('foo')
......@@ -53,10 +55,6 @@ describe ProjectsController do
end
describe 'PUT #update' do
before do
controller.instance_variable_set(:@project, project)
end
it 'updates EE attributes' do
params = {
repository_size_limit: 1024
......@@ -64,8 +62,9 @@ describe ProjectsController do
put :update,
namespace_id: project.namespace,
id: project.id,
id: project,
project: params
project.reload
expect(response).to have_gitlab_http_status(302)
params.except(:repository_size_limit).each do |param, value|
......@@ -84,8 +83,9 @@ describe ProjectsController do
put :update,
namespace_id: project.namespace,
id: project.id,
id: project,
project: params
project.reload
expect(response).to have_gitlab_http_status(302)
expect(project.approver_groups.pluck(:group_id)).to contain_exactly(params[:approver_group_ids])
......@@ -100,8 +100,9 @@ describe ProjectsController do
put :update,
namespace_id: project.namespace,
id: project.id,
id: project,
project: params
project.reload
expect(response).to have_gitlab_http_status(302)
params.each do |param, value|
......@@ -119,8 +120,9 @@ describe ProjectsController do
put :update,
namespace_id: project.namespace,
id: project.id,
id: project,
project: params
project.reload
expect(response).to have_gitlab_http_status(302)
expect(project.service_desk_enabled).to eq(true)
......@@ -131,7 +133,8 @@ describe ProjectsController do
{
mirror: true,
mirror_trigger_builds: true,
mirror_user_id: user.id
mirror_user_id: user.id,
import_url: 'https://example.com'
}
end
......@@ -143,12 +146,14 @@ describe ProjectsController do
it 'updates repository mirror attributes' do
put :update,
namespace_id: project.namespace,
id: project.id,
id: project,
project: params
project.reload
params.each do |param, value|
expect(project.public_send(param)).to eq(value)
end
expect(project.mirror).to eq(true)
expect(project.mirror_trigger_builds).to eq(true)
expect(project.mirror_user).to eq(user)
expect(project.import_url).to eq('https://example.com')
end
end
......@@ -162,12 +167,59 @@ describe ProjectsController do
expect do
put :update,
namespace_id: project.namespace,
id: project.id,
id: project,
project: params
project.reload
end.not_to change(project, param)
end
end
end
end
context 'external authaurization service attributes' do
def update_classification_label
put :update,
namespace_id: project.namespace,
id: project,
project: { external_authorization_classification_label: 'new_label' }
project.reload
end
it 'updates the project classification label' do
external_service_allow_access(user, project)
expect { update_classification_label }
.to change(project, :external_authorization_classification_label).to('new_label')
end
it 'does not update the project classification label when the feature is not available' do
stub_licensed_features(external_authorization_service: false)
expect { update_classification_label }
.not_to change(project, :external_authorization_classification_label)
end
end
it_behaves_like 'unauthorized when external service denies access' do
subject do
put :update,
namespace_id: project.namespace,
id: project,
project: { description: 'Hello world' }
project.reload
end
it 'updates when the service allows access' do
external_service_allow_access(user, project)
expect { subject }.to change(project, :description)
end
it 'does not update when the service rejects access' do
external_service_deny_access(user, project)
expect { subject }.not_to change(project, :description)
end
end
end
end
require 'spec_helper'
describe SearchController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:note) { create(:note_on_issue, project: project) }
before do
sign_in(user)
end
context 'with external authorization service enabled' do
before do
enable_external_authorization_service
end
describe 'GET #show' do
it 'renders a 404 when no project is given' do
get :show, scope: 'notes', search: note.note
expect(response).to have_gitlab_http_status(404)
end
it 'renders a 200 when a project was set' do
get :show, project_id: project.id, scope: 'notes', search: note.note
expect(response).to have_gitlab_http_status(200)
end
end
describe 'GET #autocomplete' do
it 'renders a 404 when no project is given' do
get :autocomplete, term: 'hello'
expect(response).to have_gitlab_http_status(404)
end
it 'renders a 200 when a project was set' do
get :autocomplete, project_id: project.id, term: 'hello'
expect(response).to have_gitlab_http_status(200)
end
end
end
end
require 'spec_helper'
describe UsersController do
let(:user) { create(:user) }
before do
sign_in(user)
end
describe 'GET #snippets' do
subject { get :snippets, username: user.username }
it_behaves_like 'disabled when using an external authorization service'
end
describe 'GET #calendar_activities' do
subject { get :calendar_activities, username: user.username }
it_behaves_like 'disabled when using an external authorization service'
end
end
require 'spec_helper'
feature 'The group dashboard' do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
before do
sign_in user
end
describe 'The top navigation' do
it 'has all the expected links' do
visit dashboard_groups_path
within('.navbar') do
expect(page).to have_link('Projects')
expect(page).to have_link('Groups')
expect(page).to have_link('Activity')
expect(page).to have_link('Milestones')
expect(page).to have_link('Snippets')
end
end
it 'hides some links when an external authorization service is enabled' do
enable_external_authorization_service
visit dashboard_groups_path
within('.navbar') do
expect(page).to have_link('Projects')
expect(page).to have_link('Groups')
expect(page).not_to have_link('Activity')
expect(page).not_to have_link('Milestones')
expect(page).to have_link('Snippets')
end
end
end
end
require 'spec_helper'
feature 'The group page' do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
sign_in user
group.add_owner(user)
end
describe 'The sidebar' do
it 'has all the expected links' do
visit group_path(group)
within('.nav-sidebar') do
expect(page).to have_link('Overview')
expect(page).to have_link('Details')
expect(page).to have_link('Activity')
expect(page).to have_link('Contribution Analytics')
expect(page).to have_link('Issues')
expect(page).to have_link('Merge Requests')
expect(page).to have_link('Members')
end
end
it 'hides some links when an external authorization service is enabled' do
enable_external_authorization_service
visit group_path(group)
within('.nav-sidebar') do
expect(page).to have_link('Overview')
expect(page).to have_link('Details')
expect(page).not_to have_link('Activity')
expect(page).not_to have_link('Contribution Analytics')
expect(page).not_to have_link('Issues')
expect(page).not_to have_link('Merge Requests')
expect(page).to have_link('Members')
end
end
context 'when epics are available' do
before do
stub_licensed_features(epics: true)
end
it 'shows the link to epics' do
visit group_path(group)
within('.nav-sidebar') do
expect(page).to have_link('Epics')
end
end
it 'hides the epics link when an external authorization service is enabled' do
enable_external_authorization_service
visit group_path(group)
within('.nav-sidebar') do
expect(page).not_to have_link('Epics')
end
end
end
end
end
require 'spec_helper'
describe 'Classification label on project pages' do
include ExternalAuthorizationServiceHelpers
let(:project) do
create(:project, external_authorization_classification_label: 'authorized label')
end
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
end
it 'shows the classification label on the project page when the feature is enabled' do
external_service_allow_access(user, project)
visit project_path(project)
expect(page).to have_content('authorized label')
end
it 'does not show the classification label when the feature is not enabled' do
stub_licensed_features(external_authorization_service: false)
visit project_path(project)
expect(page).not_to have_content('authorized label')
end
end
require 'spec_helper'
describe 'listing forks of a project' do
include ProjectForksHelper
include ExternalAuthorizationServiceHelpers
let(:source) { create(:project, :public, :repository) }
let!(:fork) { fork_project(source, nil, repository: true) }
let(:user) { create(:user) }
before do
source.add_master(user)
sign_in(user)
end
it 'shows the forked project in the list with commit as description' do
visit project_forks_path(source)
page.within('li.project-row') do
expect(page).to have_content(fork.full_name)
expect(page).to have_css('a.commit-row-message')
end
end
it 'does not show the commit message when an external authorization service is used' do
enable_external_authorization_service
visit project_forks_path(source)
page.within('li.project-row') do
expect(page).to have_content(fork.full_name)
expect(page).not_to have_css('a.commit-row-message')
end
end
end
require 'spec_helper'
describe 'viewing an issue with cross project references' do
include ExternalAuthorizationServiceHelpers
include Gitlab::Routing.url_helpers
let(:user) { create(:user) }
let(:other_project) do
create(:project, :public,
external_authorization_classification_label: 'other_label')
end
let(:other_issue) do
create(:issue, :closed,
title: 'I am in another project',
project: other_project)
end
let(:other_confidential_issue) do
create(:issue, :confidential, :closed,
title: 'I am in another project and confidential',
project: other_project)
end
let(:description_referencing_other_issue) do
"Referencing: #{other_issue.to_reference(project)}, and "\
"a confidential issue #{confidential_issue.to_reference}"\
"a cross project confidential issue #{other_confidential_issue.to_reference(project)}"
end
let(:project) { create(:project) }
let(:issue) do
create(:issue,
project: project,
description: description_referencing_other_issue )
end
let(:confidential_issue) do
create(:issue, :confidential, :closed,
title: "I am in the same project and confidential",
project: project)
end
before do
project.add_developer(user)
sign_in(user)
end
it 'shows all information related to the cross project reference' do
visit project_issue_path(project, issue)
expect(page).to have_link("#{other_issue.to_reference(project)} (#{other_issue.state})")
expect(page).to have_xpath("//a[@title='#{other_issue.title}']")
end
it 'shows a link to the confidential issue in the same project' do
visit project_issue_path(project, issue)
expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})")
expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']")
end
it 'does not show the link to a cross project confidential issue when the user does not have access' do
visit project_issue_path(project, issue)
expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})")
expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']")
end
it 'shows the link to a cross project confidential issue when the user has access' do
other_project.add_developer(user)
visit project_issue_path(project, issue)
expect(page).to have_link("#{other_confidential_issue.to_reference(project)} (#{other_confidential_issue.state})")
expect(page).to have_xpath("//a[@title='#{other_confidential_issue.title}']")
end
context 'when an external authorization service is enabled' do
before do
enable_external_authorization_service
end
it 'only hits the external service for the project the user is viewing' do
expect(EE::Gitlab::ExternalAuthorization)
.to receive(:access_allowed?).with(user, 'default_label').at_least(1).and_return(true)
visit project_issue_path(project, issue)
end
it 'shows only the link to the cross project reference' do
visit project_issue_path(project, issue)
expect(page).to have_link("#{other_issue.to_reference(project)}")
expect(page).not_to have_content("#{other_issue.to_reference(project)} (#{other_issue.state})")
expect(page).not_to have_xpath("//a[@title='#{other_issue.title}']")
end
it 'does not link a cross project confidential issue if the user does not have access' do
visit project_issue_path(project, issue)
expect(page).not_to have_link("#{other_confidential_issue.to_reference(project)}")
expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']")
end
it 'links a cross project confidential issue without exposing information when the user has access' do
other_project.add_developer(user)
visit project_issue_path(project, issue)
expect(page).to have_link("#{other_confidential_issue.to_reference(project)}")
expect(page).not_to have_xpath("//a[@title='#{other_confidential_issue.title}']")
end
it 'shows a link to the confidential issue in the same project' do
visit project_issue_path(project, issue)
expect(page).to have_link("#{confidential_issue.to_reference(project)} (#{confidential_issue.state})")
expect(page).to have_xpath("//a[@title='#{confidential_issue.title}']")
end
end
end
require 'spec_helper'
describe 'Project settings > [EE] repository' do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:project) { create(:project_empty_repo) }
before do
project.add_master(user)
sign_in(user)
end
it 'shows the field to set a classification label when the feature is enabled' do
external_service_allow_access(user, project)
visit edit_project_path(project)
expect(page).to have_selector('#project_external_authorization_classification_label')
end
it 'shows the field to set a classification label when the feature is unavailable' do
stub_licensed_features(external_authorization_service: false)
visit edit_project_path(project)
expect(page).not_to have_selector('#project_external_authorization_classification_label')
end
end
require 'spec_helper'
describe 'User page' do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
it 'shows the most recent activity' do
visit(user_path(user))
expect(page).to have_content('Most Recent Activity')
end
describe 'when external authorization is enabled' do
before do
enable_external_authorization_service
end
it 'hides the most recent activity' do
visit(user_path(user))
expect(page).not_to have_content('Most Recent Activity')
end
end
end
require 'spec_helper'
describe IssuesFinder do
it_behaves_like 'a finder with external authorization service' do
let!(:subject) { create(:issue, project: project) }
let(:project_params) { { project_id: project.id } }
end
end
require 'spec_helper'
describe LabelsFinder do
it_behaves_like 'a finder with external authorization service' do
let!(:subject) { create(:label, project: project) }
let(:project_params) { { project_id: project.id } }
end
end
require 'spec_helper'
describe MergeRequestsFinder do
it_behaves_like 'a finder with external authorization service' do
let!(:subject) { create(:merge_request, source_project: project) }
let(:project_params) { { project_id: project.id } }
end
end
require 'spec_helper'
describe SnippetsFinder do
include ExternalAuthorizationServiceHelpers
it_behaves_like 'a finder with external authorization service' do
let!(:subject) { create(:project_snippet, project: project) }
let(:project_params) { { project: project } }
end
context 'external authorization service enabled' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let!(:snippet) { create(:project_snippet, :public, project: project) }
before do
project.add_master(user)
end
it 'includes the result if the external service allows access' do
external_service_allow_access(user, project)
results = described_class.new(user, project: project).execute
expect(results).to contain_exactly(snippet)
end
it 'does not include any results if the external service denies access' do
external_service_deny_access(user, project)
results = described_class.new(user, project: project).execute
expect(results).to be_empty
end
end
end
require 'spec_helper'
describe TodosFinder do
it_behaves_like 'a finder with external authorization service' do
let!(:subject) { create(:todo, project: project, user: user) }
let(:project_params) { { project_id: project.id } }
end
end
require 'spec_helper'
describe GroupsHelper do
describe '#group_sidebar_links' do
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
allow(helper).to receive(:current_user) { user }
helper.instance_variable_set(:@group, group)
allow(helper).to receive(:can?).with(user, :admin_group, group) { false }
end
it 'shows the licenced cross project features when the user can read cross project' do
expect(helper).to receive(:can?).with(user, :read_cross_project).at_least(1) { true }
stub_licensed_features(contribution_analytics: true,
group_issue_boards: true,
epics: true)
expect(helper.group_sidebar_links).to include(:contribution_analytics, :boards, :epics)
end
end
end
require 'spec_helper'
describe EE::Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do
subject(:access) { described_class.new(build(:user), 'dummy_label') }
describe '#loaded?' do
it 'is `true` when it was loaded recently' do
Timecop.freeze do
allow(access).to receive(:loaded_at).and_return(5.minutes.ago)
expect(access).to be_loaded
end
end
it 'is `false` when there is no loading time' do
expect(access).not_to be_loaded
end
it 'is `false` when there the result was loaded a long time ago' do
Timecop.freeze do
allow(access).to receive(:loaded_at).and_return(2.weeks.ago)
expect(access).not_to be_loaded
end
end
end
describe 'load!' do
let(:fake_client) { double('ExternalAuthorization::Client') }
let(:fake_response) do
double(
'Response',
'successful?' => true,
'valid?' => true,
'reason' => nil
)
end
before do
allow(access).to receive(:load_from_cache)
allow(fake_client).to receive(:request_access).and_return(fake_response)
allow(EE::Gitlab::ExternalAuthorization::Client).to receive(:build) { fake_client }
end
context 'when loading from the webservice' do
it 'loads from the webservice it the cache was empty' do
expect(access).to receive(:load_from_cache)
expect(access).to receive(:load_from_service).and_call_original
access.load!
expect(access).to be_loaded
end
it 'assigns the accessibility, reason and loaded_at' do
allow(fake_response).to receive(:successful?).and_return(false)
allow(fake_response).to receive(:reason).and_return('Inaccessible label')
access.load!
expect(access.reason).to eq('Inaccessible label')
expect(access).not_to have_access
expect(access.loaded_at).not_to be_nil
end
it 'returns itself' do
expect(access.load!).to eq(access)
end
it 'it stores the result in redis' do
Timecop.freeze do
fake_cache = double
expect(fake_cache).to receive(:store).with(true, nil, Time.now)
expect(access).to receive(:cache).and_return(fake_cache)
access.load!
end
end
context 'when the request fails' do
before do
allow(fake_client).to receive(:request_access) do
raise EE::Gitlab::ExternalAuthorization::RequestFailed.new('Service unavailable')
end
end
it 'is loaded' do
access.load!
expect(access).to be_loaded
end
it 'assigns the correct accessibility, reason and loaded_at' do
access.load!
expect(access.reason).to eq('Service unavailable')
expect(access).not_to have_access
expect(access.loaded_at).not_to be_nil
end
it 'it does not store the result in redis' do
fake_cache = double
expect(fake_cache).not_to receive(:store)
allow(access).to receive(:cache).and_return(fake_cache)
access.load!
end
end
end
context 'When loading from cache' do
let(:fake_cache) { double('ExternalAuthorization::Cache') }
before do
allow(access).to receive(:cache).and_return(fake_cache)
end
it 'does not load from the webservice' do
Timecop.freeze do
expect(fake_cache).to receive(:load).and_return([true, nil, Time.now])
expect(access).to receive(:load_from_cache).and_call_original
expect(access).not_to receive(:load_from_service)
access.load!
end
end
it 'loads from the webservice when the cached result was too old' do
Timecop.freeze do
expect(fake_cache).to receive(:load).and_return([true, nil, 2.days.ago])
expect(access).to receive(:load_from_cache).and_call_original
expect(access).to receive(:load_from_service).and_call_original
allow(fake_cache).to receive(:store)
access.load!
end
end
end
end
end
require 'spec_helper'
describe EE::Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do
let(:user) { build_stubbed(:user) }
let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" }
subject(:cache) { described_class.new(user, 'dummy_label') }
def read_from_redis(key)
Gitlab::Redis::Cache.with do |redis|
redis.hget(cache_key, key)
end
end
def set_in_redis(key, value)
Gitlab::Redis::Cache.with do |redis|
redis.hmset(cache_key, key, value)
end
end
describe '#load' do
it 'reads stored info from redis' do
Timecop.freeze do
set_in_redis(:access, false)
set_in_redis(:reason, 'Access denied for now')
set_in_redis(:refreshed_at, Time.now)
access, reason, refreshed_at = cache.load
expect(access).to eq(false)
expect(reason).to eq('Access denied for now')
expect(refreshed_at).to be_within(1.second).of(Time.now)
end
end
end
describe '#store' do
it 'sets the values in redis' do
Timecop.freeze do
cache.store(true, 'the reason', Time.now)
expect(read_from_redis(:access)).to eq('true')
expect(read_from_redis(:reason)).to eq('the reason')
expect(read_from_redis(:refreshed_at)).to eq(Time.now.to_s)
end
end
end
end
require 'spec_helper'
describe EE::Gitlab::ExternalAuthorization::Client do
let(:user) { build(:user, email: 'dummy_user@example.com') }
let(:dummy_url) { 'https://dummy.net/' }
subject(:client) { described_class.build(user, 'dummy_label') }
before do
stub_application_setting(external_authorization_service_url: dummy_url)
end
describe '#request_access' do
it 'performs requests to the configured endpoint' do
expect(Excon).to receive(:post).with(dummy_url, any_args)
client.request_access
end
it 'adds the correct params for the user to the body of the request' do
expected_body = {
user_identifier: 'dummy_user@example.com',
project_classification_label: 'dummy_label'
}.to_json
expect(Excon).to receive(:post)
.with(dummy_url, hash_including(body: expected_body))
client.request_access
end
it 'returns an expected response' do
expect(Excon).to receive(:post)
expect(client.request_access)
.to be_kind_of(::EE::Gitlab::ExternalAuthorization::Response)
end
it 'wraps exceptions if the request fails' do
expect(Excon).to receive(:post) { raise Excon::Error.new('the request broke') }
expect { client.request_access }
.to raise_error(EE::Gitlab::ExternalAuthorization::RequestFailed)
end
describe 'for ldap users' do
let(:user) do
create(:omniauth_user,
email: 'dummy_user@example.com',
extern_uid: 'external id',
provider: 'ldapprovider')
end
it 'includes the ldap dn for ldap users' do
expected_body = {
user_identifier: 'dummy_user@example.com',
project_classification_label: 'dummy_label',
user_ldap_dn: 'external id'
}.to_json
expect(Excon).to receive(:post)
.with(dummy_url, hash_including(body: expected_body))
client.request_access
end
end
end
end
require 'spec_helper'
describe EE::Gitlab::ExternalAuthorization::Response do
let(:excon_response) { double }
subject(:response) { described_class.new(excon_response) }
describe '#valid?' do
it 'is valid for 200 & 401 responses' do
[200, 401].each do |status|
expect(excon_response).to receive(:status).and_return(status)
expect(response).to be_valid
end
end
it "is invalid for other statuses" do
expect(excon_response).to receive(:status).and_return(500)
expect(response).not_to be_valid
end
end
describe '#reason' do
it 'returns a reason if it was included in the response body' do
expect(excon_response).to receive(:body).and_return({ reason: 'Not authorized' }.to_json)
expect(response.reason).to eq('Not authorized')
end
it 'returns nil when there was no body' do
expect(excon_response).to receive(:body).and_return('')
expect(response.reason).to eq(nil)
end
end
describe '#successful?' do
it 'is `true` if the status is 200' do
allow(excon_response).to receive(:status).and_return(200)
expect(response).to be_successful
end
end
end
require 'spec_helper'
describe EE::Gitlab::ExternalAuthorization, :request_store do
include ExternalAuthorizationServiceHelpers
let(:user) { build(:user) }
let(:label) { 'dummy_label' }
describe '#access_allowed?' do
it 'is always true when the feature is disabled' do
# Not using `stub_application_setting` because the method is prepended in
# `EE::ApplicationSetting` which breaks when using `any_instance`
# https://gitlab.com/gitlab-org/gitlab-ce/issues/33587
expect(::Gitlab::CurrentSettings.current_application_settings)
.to receive(:external_authorization_service_enabled?) { false }
expect(described_class).not_to receive(:access_for_user_to_label)
expect(described_class.access_allowed?(user, label)).to be_truthy
end
end
describe '#rejection_reason' do
it 'is alwaus nil when the feature is disabled' do
expect(::Gitlab::CurrentSettings.current_application_settings)
.to receive(:external_authorization_service_enabled?) { false }
expect(described_class).not_to receive(:access_for_user_to_label)
expect(described_class.rejection_reason(user, label)).to be_nil
end
end
describe '#access_for_user_to_label' do
it 'only loads the access once per request' do
enable_external_authorization_service
expect(EE::Gitlab::ExternalAuthorization::Access)
.to receive(:new).with(user, label).once.and_call_original
2.times { described_class.access_for_user_to_label(user, label) }
end
end
end
require 'spec_helper'
describe ApplicationSetting do
let(:setting) { described_class.create_from_defaults }
subject(:setting) { described_class.create_from_defaults }
describe 'validations' do
it { is_expected.to allow_value(100).for(:mirror_max_delay) }
......@@ -23,6 +23,18 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value(1.0).for(:mirror_capacity_threshold) }
it { is_expected.not_to allow_value(-1).for(:mirror_capacity_threshold) }
it { is_expected.not_to allow_value(subject.mirror_max_capacity + 1).for(:mirror_capacity_threshold) }
describe 'when external authorization service is enabled' do
before do
stub_licensed_features(external_authorization_service: true)
setting.external_authorization_service_enabled = true
end
it { is_expected.not_to allow_value(nil).for(:external_authorization_service_url) }
it { is_expected.not_to allow_value('not a URL').for(:external_authorization_service_url) }
it { is_expected.to allow_value('https://example.com').for(:external_authorization_service_url) }
it { is_expected.not_to allow_value(nil).for(:external_authorization_service_default_label) }
end
end
describe '#should_check_namespace_plan?' do
......
......@@ -44,4 +44,9 @@ describe Issue, elastic: true do
expect(issue.as_indexed_json).to eq(expected_hash)
end
it_behaves_like 'no results when the user cannot read cross project' do
let(:record1) { create(:issue, project: project, title: 'test-issue') }
let(:record2) { create(:issue, project: project2, title: 'test-issue') }
end
end
......@@ -54,4 +54,9 @@ describe MergeRequest, elastic: true do
expect(merge_request.as_indexed_json).to eq(expected_hash)
end
it_behaves_like 'no results when the user cannot read cross project' do
let(:record1) { create(:merge_request, source_project: project, title: 'test-mr') }
let(:record2) { create(:merge_request, source_project: project2, title: 'test-mr') }
end
end
......@@ -45,4 +45,9 @@ describe Milestone, elastic: true do
expect(milestone.as_indexed_json).to eq(expected_hash)
end
it_behaves_like 'no results when the user cannot read cross project' do
let(:record1) { create(:milestone, project: project, title: 'test-milestone') }
let(:record2) { create(:milestone, project: project2, title: 'test-milestone') }
end
end
......@@ -155,6 +155,13 @@ describe Note, elastic: true do
end
end
it_behaves_like 'no results when the user cannot read cross project' do
let(:issue1) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project2) }
let(:record1) { create :note, note: 'test-note', project: issue1.project, noteable: issue1 }
let(:record2) { create :note, note: 'test-note', project: issue2.project, noteable: issue2 }
end
def create_notes_for(issue, note)
create :note, note: note, project: issue.project, noteable: issue
create :note, project: issue.project, noteable: issue
......
......@@ -80,6 +80,22 @@ describe Snippet, elastic: true do
expect(result.records).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet]
end
end
describe 'when the user cannot read cross project' do
before do
allow(Ability).to receive(:allowed?).and_call_original
end
it 'returns public, internal snippets, but not project snippets' do
member = create(:user)
project.add_developer(member)
expect(Ability).to receive(:allowed?).with(member, :read_cross_project) { false }
result = described_class.elastic_search_code('password', options: { user: member })
expect(result.records).to match_array [public_snippet, internal_snippet]
end
end
end
it 'searches snippets by title and file_name' do
......
require 'spec_helper'
describe EE::ProtectedRefAccess do
include ExternalAuthorizationServiceHelpers
included_in_classes = [ProtectedBranch::MergeAccessLevel,
ProtectedBranch::PushAccessLevel,
ProtectedTag::CreateAccessLevel]
......@@ -71,4 +72,20 @@ describe EE::ProtectedRefAccess do
end
end
end
describe '#check_access' do
subject(:protected_ref_access) do
create(:protected_branch, :masters_can_push).push_access_levels.first
end
let(:project) { protected_ref_access.project }
it 'is false if external authorization denies access' do
master = create(:user)
project.add_master(master)
external_service_deny_access(master, project)
expect(protected_ref_access.check_access(master)).to be_falsey
end
end
end
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe Issue do
using RSpec::Parameterized::TableSyntax
include ExternalAuthorizationServiceHelpers
describe '#allows_multiple_assignees?' do
it 'does not allow multiple assignees without license' do
......@@ -41,4 +42,48 @@ describe Issue do
it { is_expected.to eq(expected) }
end
end
context 'when an external authentication service' do
before do
enable_external_authorization_service
end
describe '#publicly_visible?' do
it 'is `false` when an external authorization service is enabled' do
issue = build(:issue, project: build(:project, :public))
expect(issue).not_to be_publicly_visible
end
end
describe '#readable_by?' do
it 'checks the external service to determine if an issue is readable by a user' do
project = build(:project, :public,
external_authorization_classification_label: 'a-label')
issue = build(:issue, project: project)
user = build(:user)
expect(EE::Gitlab::ExternalAuthorization).to receive(:access_allowed?).with(user, 'a-label') { false }
expect(issue.readable_by?(user)).to be_falsy
end
it 'does not check the external webservice for admins' do
issue = build(:issue)
user = build(:admin)
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
issue.readable_by?(user)
end
it 'does not check the external webservice for auditors' do
issue = build(:issue)
user = build(:auditor)
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
issue.readable_by?(user)
end
end
end
end
require 'spec_helper'
describe Project do
include ExternalAuthorizationServiceHelpers
using RSpec::Parameterized::TableSyntax
describe 'associations' do
......@@ -1155,4 +1156,44 @@ describe Project do
expect(projects).not_to include(project2)
end
end
describe '#external_authorization_classification_label' do
it 'falls back to the default when none is configured' do
enable_external_authorization_service
expect(build(:project).external_authorization_classification_label)
.to eq('default_label')
end
it 'returns `nil` if the feature is disabled' do
stub_licensed_features(external_authorization_service: false)
project = build(:project,
external_authorization_classification_label: 'hello')
expect(project.external_authorization_classification_label)
.to eq(nil)
end
it 'returns the classification label if it was configured on the project' do
enable_external_authorization_service
project = build(:project,
external_authorization_classification_label: 'hello')
expect(project.external_authorization_classification_label)
.to eq('hello')
end
end
describe '#user_can_push_to_empty_repo?' do
it 'returns false when the external service denies access' do
user = create(:user)
project = create(:project)
project.add_master(user)
external_service_deny_access(user, project)
expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
end
end
end
require 'spec_helper'
describe BasePolicy do
include ExternalAuthorizationServiceHelpers
let(:current_user) { create(:user) }
let(:user) { create(:user) }
subject { described_class.new(current_user, [user]) }
describe 'read cross project' do
it { is_expected.to be_allowed(:read_cross_project) }
context 'when an external authorization service is enabled' do
before do
enable_external_authorization_service
end
it { is_expected.not_to be_allowed(:read_cross_project) }
it 'allows admins' do
expect(described_class.new(build(:admin), nil)).to be_allowed(:read_cross_project)
end
it 'allows auditors' do
expect(described_class.new(build(:auditor), nil)).to be_allowed(:read_cross_project)
end
end
end
end
require 'spec_helper'
describe EpicPolicy do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
def permissions(user, group)
......@@ -16,7 +17,7 @@ describe EpicPolicy do
group.add_owner(user)
expect(permissions(user, group))
.to be_disallowed(:read_epic, :update_epic, :destroy_epic, :admin_epic, :create_epic)
.to be_disallowed(:read_epic, :read_epic_iid, :update_epic, :destroy_epic, :admin_epic, :create_epic)
end
end
......@@ -30,18 +31,18 @@ describe EpicPolicy do
it 'anonymous user can not read epics' do
expect(permissions(nil, group))
.to be_disallowed(:read_epic, :update_epic, :destroy_epic, :admin_epic, :create_epic)
.to be_disallowed(:read_epic, :read_epic_iid, :update_epic, :destroy_epic, :admin_epic, :create_epic)
end
it 'user who is not a group member can not read epics' do
expect(permissions(user, group))
.to be_disallowed(:read_epic, :update_epic, :destroy_epic, :admin_epic, :create_epic)
.to be_disallowed(:read_epic, :read_epic_iid, :update_epic, :destroy_epic, :admin_epic, :create_epic)
end
it 'guest group member can only read epics' do
group.add_guest(user)
expect(permissions(user, group)).to be_allowed(:read_epic)
expect(permissions(user, group)).to be_allowed(:read_epic, :read_epic_iid)
expect(permissions(user, group)).to be_disallowed(:update_epic, :destroy_epic, :admin_epic, :create_epic)
end
......@@ -50,14 +51,14 @@ describe EpicPolicy do
expect(permissions(user, group)).to be_disallowed(:destroy_epic)
expect(permissions(user, group))
.to be_allowed(:read_epic, :update_epic, :admin_epic, :create_epic)
.to be_allowed(:read_epic, :read_epic_iid, :update_epic, :admin_epic, :create_epic)
end
it 'only group owner can destroy epics' do
group.add_owner(user)
expect(permissions(user, group))
.to be_allowed(:read_epic, :update_epic, :destroy_epic, :admin_epic, :create_epic)
.to be_allowed(:read_epic, :read_epic_iid, :update_epic, :destroy_epic, :admin_epic, :create_epic)
end
end
......@@ -66,18 +67,18 @@ describe EpicPolicy do
it 'anonymous user can not read epics' do
expect(permissions(nil, group))
.to be_disallowed(:read_epic, :update_epic, :destroy_epic, :admin_epic, :create_epic)
.to be_disallowed(:read_epic, :read_epic_iid, :update_epic, :destroy_epic, :admin_epic, :create_epic)
end
it 'user who is not a group member can only read epics' do
expect(permissions(user, group)).to be_allowed(:read_epic)
expect(permissions(user, group)).to be_allowed(:read_epic, :read_epic_iid)
expect(permissions(user, group)).to be_disallowed(:update_epic, :destroy_epic, :admin_epic, :create_epic)
end
it 'guest group member can only read epics' do
group.add_guest(user)
expect(permissions(user, group)).to be_allowed(:read_epic)
expect(permissions(user, group)).to be_allowed(:read_epic, :read_epic_iid)
expect(permissions(user, group)).to be_disallowed(:update_epic, :destroy_epic, :admin_epic, :create_epic)
end
......@@ -86,14 +87,14 @@ describe EpicPolicy do
expect(permissions(user, group)).to be_disallowed(:destroy_epic)
expect(permissions(user, group))
.to be_allowed(:read_epic, :update_epic, :admin_epic, :create_epic)
.to be_allowed(:read_epic, :read_epic_iid, :update_epic, :admin_epic, :create_epic)
end
it 'only group owner can destroy epics' do
group.add_owner(user)
expect(permissions(user, group))
.to be_allowed(:read_epic, :update_epic, :destroy_epic, :admin_epic, :create_epic)
.to be_allowed(:read_epic, :read_epic_iid, :update_epic, :destroy_epic, :admin_epic, :create_epic)
end
end
......@@ -101,19 +102,19 @@ describe EpicPolicy do
let(:group) { create(:group, :public) }
it 'anonymous user can only read epics' do
expect(permissions(nil, group)).to be_allowed(:read_epic)
expect(permissions(nil, group)).to be_allowed(:read_epic, :read_epic_iid)
expect(permissions(nil, group)).to be_disallowed(:update_epic, :destroy_epic, :admin_epic, :create_epic)
end
it 'user who is not a group member can only read epics' do
expect(permissions(user, group)).to be_allowed(:read_epic)
expect(permissions(user, group)).to be_allowed(:read_epic, :read_epic_iid)
expect(permissions(user, group)).to be_disallowed(:update_epic, :destroy_epic, :admin_epic, :create_epic)
end
it 'guest group member can only read epics' do
group.add_guest(user)
expect(permissions(user, group)).to be_allowed(:read_epic)
expect(permissions(user, group)).to be_allowed(:read_epic, :read_epic_iid)
expect(permissions(user, group)).to be_disallowed(:update_epic, :destroy_epic, :admin_epic, :create_epic)
end
......@@ -122,15 +123,32 @@ describe EpicPolicy do
expect(permissions(user, group)).to be_disallowed(:destroy_epic)
expect(permissions(user, group))
.to be_allowed(:read_epic, :update_epic, :admin_epic, :create_epic)
.to be_allowed(:read_epic, :read_epic_iid, :update_epic, :admin_epic, :create_epic)
end
it 'only group owner can destroy epics' do
group.add_owner(user)
expect(permissions(user, group))
.to be_allowed(:read_epic, :update_epic, :destroy_epic, :admin_epic, :create_epic)
.to be_allowed(:read_epic, :read_epic_iid, :update_epic, :destroy_epic, :admin_epic, :create_epic)
end
end
end
context 'when external authorization is enabled' do
let(:group) { create(:group) }
before do
enable_external_authorization_service
group.add_owner(user)
end
it 'does not allow any epic permissions' do
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(permissions(user, group))
.not_to be_allowed(:read_epic, :read_epic_iid, :update_epic,
:destroy_epic, :admin_epic, :create_epic)
end
end
end
require 'spec_helper'
describe IssuePolicy do
include ExternalAuthorizationServiceHelpers
context 'with external authorization enabled' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:policies) { described_class.new(user, issue) }
before do
enable_external_authorization_service
end
it 'can read the issue iid without accessing the external service' do
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(policies).to be_allowed(:read_issue_iid)
end
end
end
require 'spec_helper'
describe MergeRequestPolicy do
include ExternalAuthorizationServiceHelpers
let(:guest) { create(:user) }
let(:developer) { create(:user) }
let(:master) { create(:user) }
......@@ -108,4 +110,21 @@ describe MergeRequestPolicy do
end
end
end
context 'with external authorization enabled' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) }
let(:policies) { described_class.new(user, merge_request) }
before do
enable_external_authorization_service
end
it 'can read the issue iid without accessing the external service' do
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(policies).to be_allowed(:read_merge_request_iid)
end
end
end
require 'spec_helper'
describe ProjectPolicy do
include ExternalAuthorizationServiceHelpers
set(:owner) { create(:user) }
set(:admin) { create(:admin) }
set(:developer) { create(:user) }
......@@ -121,4 +123,57 @@ describe ProjectPolicy do
end
end
end
context 'reading a project' do
it 'allows access when a user has read access to the repo' do
expect(described_class.new(owner, project)).to be_allowed(:read_project)
expect(described_class.new(developer, project)).to be_allowed(:read_project)
expect(described_class.new(admin, project)).to be_allowed(:read_project)
end
it 'never checks the external service' do
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(described_class.new(owner, project)).to be_allowed(:read_project)
end
context 'with an external authorization service' do
before do
enable_external_authorization_service
end
it 'allows access when the external service allows it' do
external_service_allow_access(owner, project)
external_service_allow_access(developer, project)
expect(described_class.new(owner, project)).to be_allowed(:read_project)
expect(described_class.new(developer, project)).to be_allowed(:read_project)
end
it 'does not check the external service for admins and allows access' do
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(described_class.new(admin, project)).to be_allowed(:read_project)
end
it 'allows auditors' do
stub_licensed_features(auditor_user: true)
auditor = create(:user, :auditor)
expect(described_class.new(auditor, project)).to be_allowed(:read_project)
end
it 'prevents all but seeing a public project in a list when access is denied' do
external_service_deny_access(owner, project)
external_service_deny_access(developer, project)
[developer, owner, create(:user), nil].each do |user|
policy = described_class.new(owner, project)
expect(policy).not_to be_allowed(:read_project)
expect(policy).not_to be_allowed(:owner_access)
expect(policy).not_to be_allowed(:change_namespace)
end
end
end
end
end
......@@ -19,41 +19,75 @@ describe API::Settings, 'EE Settings' do
end
end
context 'when the repository mirrors feature is not available' do
shared_examples 'settings for licensed features' do
let(:attribute_names) { settings.keys.map(&:to_s) }
before do
stub_licensed_features(repository_mirrors: false)
# Make sure the settings exist before the specs
get api("/application/settings", admin)
end
it 'hides repository mirror attributes when the feature is available' do
get api("/application/settings", admin)
context 'when the feature is not available' do
before do
stub_licensed_features(feature => false)
end
expect(response).to have_gitlab_http_status(200)
expect(json_response.keys).not_to include('mirror_max_capacity')
end
it 'hides the attributes in the API' do
get api("/application/settings", admin)
it 'does not update repository mirror attributes' do
expect { put api("/application/settings", admin), mirror_max_capacity: 15 }
.not_to change(ApplicationSetting.current.reload, :mirror_max_capacity)
end
end
expect(response).to have_gitlab_http_status(200)
attribute_names.each do |attribute|
expect(json_response.keys).not_to include(attribute)
end
end
context 'when the repository mirrors feature is available' do
before do
stub_licensed_features(repository_mirrors: true)
it 'does not update application settings' do
expect { put api("/application/settings", admin), settings }
.not_to change { ApplicationSetting.current.reload.attributes.slice(*attribute_names) }
end
end
it 'has repository mirror attributes when the feature is available' do
get api("/application/settings", admin)
context 'when the feature is available' do
before do
stub_licensed_features(feature => true)
end
expect(response).to have_gitlab_http_status(200)
expect(json_response.keys).to include('mirror_max_capacity')
it 'includes the attributes in the API' do
get api("/application/settings", admin)
expect(response).to have_gitlab_http_status(200)
attribute_names.each do |attribute|
expect(json_response.keys).to include(attribute)
end
end
it 'allows updating the settings' do
put api("/application/settings", admin), settings
settings.each do |attribute, value|
expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
end
end
end
end
context 'mirroring settings' do
let(:settings) { { mirror_max_capacity: 15 } }
let(:feature) { :repository_mirrors }
it 'updates repository mirror attributes' do
put api("/application/settings", admin), mirror_max_capacity: 15
it_behaves_like 'settings for licensed features'
end
expect(ApplicationSetting.current.reload.mirror_max_capacity).to eq(15)
context 'external policy classification settings' do
let(:settings) do
{
external_authorization_service_enabled: true,
external_authorization_service_url: 'https://custom.service/',
external_authorization_service_default_label: 'default'
}
end
let(:feature) { :external_authorization_service }
it_behaves_like 'settings for licensed features'
end
end
require 'spec_helper'
describe GroupChildEntity do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:request) { double('request') }
let(:entity) { described_class.new(object, request: request) }
subject(:json) { entity.as_json }
before do
allow(request).to receive(:current_user).and_return(user)
end
describe 'for a project with external authorization enabled' do
let(:object) do
create(:project, :with_avatar,
description: 'Awesomeness')
end
before do
enable_external_authorization_service
object.add_master(user)
end
it 'does not hit the external authorization service' do
expect(EE::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?)
expect(json[:can_edit]).to eq(false)
end
end
end
require 'spec_helper'
describe EE::NotificationService do
describe EE::NotificationService, :mailer do
include ExternalAuthorizationServiceHelpers
let(:subject) { NotificationService.new }
before do
allow(Notify).to receive(:service_desk_new_note_email)
.with(kind_of(Integer), kind_of(Integer)).and_return(double(deliver_later: true))
context 'with external authentication service' do
let(:issue) { create(:issue) }
let(:project) { issue.project }
let(:note) { create(:note, noteable: issue, project: project) }
let(:member) { create(:user) }
allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
before do
project.add_master(member)
member.global_notification_setting.update!(level: :watch)
end
def should_email!
expect(Notify).to receive(:service_desk_new_note_email).with(issue.id, kind_of(Integer))
end
it 'sends email when the service is not enabled' do
expect(Notify).to receive(:new_issue_email).with(member.id, issue.id, nil).and_call_original
def should_not_email!
expect(Notify).not_to receive(:service_desk_new_note_email)
end
subject.new_issue(issue, member)
end
def execute!
subject.send_service_desk_notification(note)
end
context 'when the service is enabled' do
before do
enable_external_authorization_service
end
it 'does not send an email' do
expect(Notify).not_to receive(:new_issue_email)
def self.it_should_email!
it 'sends the email' do
should_email!
execute!
subject.new_issue(issue, member)
end
it 'still delivers email to admins' do
member.update!(admin: true)
expect(Notify).to receive(:new_issue_email).with(member.id, issue.id, nil).and_call_original
subject.new_issue(issue, member)
end
end
end
def self.it_should_not_email!
it 'doesn\'t send the email' do
should_not_email!
execute!
context 'service desk issues' do
before do
allow(Notify).to receive(:service_desk_new_note_email)
.with(kind_of(Integer), kind_of(Integer)).and_return(double(deliver_later: true))
allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
end
let(:issue) { create(:issue, author: User.support_bot) }
let(:project) { issue.project }
let(:note) { create(:note, noteable: issue, project: project) }
def should_email!
expect(Notify).to receive(:service_desk_new_note_email).with(issue.id, kind_of(Integer))
end
context 'a non-service-desk issue' do
it_should_not_email!
end
def should_not_email!
expect(Notify).not_to receive(:service_desk_new_note_email)
end
context 'a service-desk issue' do
before do
issue.update!(service_desk_reply_to: 'service.desk@example.com')
project.update!(service_desk_enabled: true)
def execute!
subject.send_service_desk_notification(note)
end
it_should_email!
def self.it_should_email!
it 'sends the email' do
should_email!
execute!
end
end
context 'where the project has disabled the feature' do
before do
project.update(service_desk_enabled: false)
def self.it_should_not_email!
it 'doesn\'t send the email' do
should_not_email!
execute!
end
end
let(:issue) { create(:issue, author: User.support_bot) }
let(:project) { issue.project }
let(:note) { create(:note, noteable: issue, project: project) }
context 'a non-service-desk issue' do
it_should_not_email!
end
context 'when the license doesn\'t allow service desk' do
context 'a service-desk issue' do
before do
expect(EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
issue.update!(service_desk_reply_to: 'service.desk@example.com')
project.update!(service_desk_enabled: true)
end
it_should_not_email!
end
it_should_email!
context 'when the support bot has unsubscribed' do
before do
issue.unsubscribe(User.support_bot, project)
context 'where the project has disabled the feature' do
before do
project.update(service_desk_enabled: false)
end
it_should_not_email!
end
it_should_not_email!
context 'when the license doesn\'t allow service desk' do
before do
expect(EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(false)
end
it_should_not_email!
end
context 'when the support bot has unsubscribed' do
before do
issue.unsubscribe(User.support_bot, project)
end
it_should_not_email!
end
end
end
end
module ExternalAuthorizationServiceHelpers
def enable_external_authorization_service
stub_licensed_features(external_authorization_service: true)
# Not using `stub_application_setting` because the method is prepended in
# `EE::ApplicationSetting` which breaks when using `any_instance`
# https://gitlab.com/gitlab-org/gitlab-ce/issues/33587
allow(::Gitlab::CurrentSettings.current_application_settings)
.to receive(:external_authorization_service_enabled) { true }
allow(::Gitlab::CurrentSettings.current_application_settings)
.to receive(:external_authorization_service_enabled?) { true }
stub_application_setting(external_authorization_service_url: 'https://authorize.me')
stub_application_setting(external_authorization_service_default_label: 'default_label')
stub_request(:post, "https://authorize.me").to_return(status: 200)
end
def external_service_set_access(allowed, user, project)
enable_external_authorization_service
classification_label = ::Gitlab::CurrentSettings.current_application_settings
.external_authorization_service_default_label
# Reload the project so cached licensed features are reloaded
if project
classification_label = Project.find(project.id).external_authorization_classification_label
end
allow(EE::Gitlab::ExternalAuthorization)
.to receive(:access_allowed?)
.with(user, classification_label)
.and_return(allowed)
end
def external_service_allow_access(user, project = nil)
external_service_set_access(true, user, project)
end
def external_service_deny_access(user, project = nil)
external_service_set_access(false, user, project)
end
end
require 'spec_helper'
shared_examples 'disabled when using an external authorization service' do
include ExternalAuthorizationServiceHelpers
it 'works when the feature is not enabled' do
subject
expect(response).to be_success
end
it 'renders a 404 with a message when the feature is enabled' do
enable_external_authorization_service
subject
expect(response).to have_gitlab_http_status(404)
end
end
shared_examples 'unauthorized when external service denies access' do
include ExternalAuthorizationServiceHelpers
it 'allows access when the authorization service allows it' do
external_service_allow_access(user, project)
subject
# Account for redirects after updates
expect(response.status).to be_between(200, 302)
end
it 'allows access when the authorization service denies it' do
external_service_deny_access(user, project)
subject
expect(response).to have_gitlab_http_status(404)
end
end
require 'spec_helper'
shared_examples 'a finder with external authorization service' do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
before do
project.add_master(user)
end
it 'finds the subject' do
expect(described_class.new(user).execute).to include(subject)
end
context 'with an external authorization service' do
before do
enable_external_authorization_service
end
it 'does not include the subject when no project was given' do
expect(described_class.new(user).execute).not_to include(subject)
end
it 'includes the subject when a project id was given' do
expect(described_class.new(user, project_params).execute).to include(subject)
end
end
end
shared_examples 'no results when the user cannot read cross project' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:project2) { create(:project, :public) }
before do
allow(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
record1
record2
Gitlab::Elastic::Helper.refresh_index
end
it 'returns the record if a single project was passed' do
result = described_class.elastic_search(
'test',
options: {
current_user: user,
project_ids: [project.id]
}
)
expect(result.records).to match_array [record1]
end
it 'does not return anything when trying to search cross project' do
result = described_class.elastic_search(
'test',
options: {
current_user: user,
project_ids: [project.id, project2.id]
}
)
expect(result.records).to be_empty
end
end
......@@ -965,6 +965,10 @@ module API
expose(*EE::ApplicationSettingsHelper.repository_mirror_attributes, if: lambda do |_instance, _options|
::License.feature_available?(:repository_mirrors)
end)
expose(*EE::ApplicationSettingsHelper.external_authorization_service_attributes, if: lambda do |_instance, _options|
::License.feature_available?(:external_authorization_service)
end)
expose(:restricted_visibility_levels) do |setting, _options|
setting.restricted_visibility_levels.map { |level| Gitlab::VisibilityLevel.string_level(level) }
end
......
......@@ -150,17 +150,26 @@ module API
optional :repository_storages, type: Array[String], desc: 'A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random.'
optional :repository_size_limit, type: Integer, desc: 'Size limit per repository (MB)'
all_attributes = ::EE::ApplicationSettingsHelper.repository_mirror_attributes + ApplicationSettingsHelper.visible_attributes
all_attributes = ApplicationSettingsHelper.visible_attributes
## EE-only
all_attributes += EE::ApplicationSettingsHelper.possible_licensed_attributes
optional(*all_attributes)
at_least_one_of(*all_attributes)
end
put "application/settings" do
attrs = declared_params(include_missing: false)
## EE-only: Remove unlicensed attributes
unless ::License.feature_available?(:repository_mirrors)
attrs = attrs.except(*::EE::ApplicationSettingsHelper.repository_mirror_attributes)
end
unless ::License.feature_available?(:external_authorization_service)
attrs = attrs.except(*::EE::ApplicationSettingsHelper.external_authorization_service_attributes)
end
# support legacy names, can be removed in v5
if attrs.has_key?(:signin_enabled)
attrs[:password_authentication_enabled_for_web] = attrs.delete(:signin_enabled)
......
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-22 13:39+0100\n"
"PO-Revision-Date: 2018-02-22 13:39+0100\n"
"POT-Creation-Date: 2018-02-22 17:55+0100\n"
"PO-Revision-Date: 2018-02-22 17:55+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -1312,6 +1312,9 @@ msgstr ""
msgid "December"
msgstr ""
msgid "Default classification label"
msgstr ""
msgid "Define a custom pattern with cron syntax"
msgstr ""
......@@ -1527,6 +1530,21 @@ msgstr ""
msgid "Explore public groups"
msgstr ""
msgid "External Classification Policy Authorization"
msgstr ""
msgid "External authorization denied access to this project"
msgstr ""
msgid "ExternalAuthorizationService|Classification Label"
msgstr ""
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used."
msgstr ""
msgid "Failed Jobs"
msgstr ""
......@@ -2898,6 +2916,9 @@ msgstr ""
msgid "Service Templates"
msgstr ""
msgid "Service URL"
msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
......
......@@ -473,6 +473,7 @@ Project:
- merge_requests_ff_only_enabled
- merge_requests_rebase_enabled
- jobs_cache_index
- external_authorization_classification_label
Author:
- name
ProjectFeature:
......
......@@ -373,21 +373,37 @@ describe Issue do
describe '#related_issues' do
let(:user) { create(:user) }
let(:authorized_project) { create(:project) }
let(:authorized_project2) { create(:project) }
let(:unauthorized_project) { create(:project) }
let(:authorized_issue_a) { create(:issue, project: authorized_project) }
let(:authorized_issue_b) { create(:issue, project: authorized_project) }
let(:authorized_issue_c) { create(:issue, project: authorized_project2) }
let(:unauthorized_issue) { create(:issue, project: unauthorized_project) }
let!(:issue_link_a) { create(:issue_link, source: authorized_issue_a, target: authorized_issue_b) }
let!(:issue_link_b) { create(:issue_link, source: authorized_issue_a, target: unauthorized_issue) }
let!(:issue_link_c) { create(:issue_link, source: authorized_issue_a, target: authorized_issue_c) }
before do
authorized_project.add_developer(user)
authorized_project2.add_developer(user)
end
it 'returns only authorized related issues for given user' do
expect(authorized_issue_a.related_issues(user)).to contain_exactly(authorized_issue_b)
expect(authorized_issue_a.related_issues(user))
.to contain_exactly(authorized_issue_b, authorized_issue_c)
end
describe 'when a user cannot read cross project' do
it 'only returns issues within the same project' do
expect(Ability).to receive(:allowed?).with(user, :read_cross_project)
.and_return(false)
expect(authorized_issue_a.related_issues(user))
.to contain_exactly(authorized_issue_b)
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