Commit 9a73775a authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into 'tc-namespace-license-checks--issue-mr-template'

# Conflicts:
#   app/models/license.rb
parents 6ab1a90d d482d68c
...@@ -141,6 +141,7 @@ $(() => { ...@@ -141,6 +141,7 @@ $(() => {
modal: ModalStore.store, modal: ModalStore.store,
store: Store.state, store: Store.state,
isFullscreen: false, isFullscreen: false,
focusModeAvailable: gl.utils.convertPermissionToBoolean($boardApp.dataset.focusModeAvailable),
}, },
watch: { watch: {
disabled() { disabled() {
...@@ -177,6 +178,8 @@ $(() => { ...@@ -177,6 +178,8 @@ $(() => {
} }
}, },
toggleFocusMode() { toggleFocusMode() {
if (!this.focusModeAvailable) { return; }
$(this.$refs.toggleFocusModeButton).tooltip('hide'); $(this.$refs.toggleFocusModeButton).tooltip('hide');
issueBoardsContent.classList.toggle('is-focused'); issueBoardsContent.classList.toggle('is-focused');
...@@ -206,6 +209,7 @@ $(() => { ...@@ -206,6 +209,7 @@ $(() => {
aria-label="Toggle focus mode" aria-label="Toggle focus mode"
title="Toggle focus mode" title="Toggle focus mode"
ref="toggleFocusModeButton" ref="toggleFocusModeButton"
v-if="focusModeAvailable"
@click="toggleFocusMode"> @click="toggleFocusMode">
<span v-show="isFullscreen"> <span v-show="isFullscreen">
${collapseIcon} ${collapseIcon}
......
module EE
module Groups
module ApplicationController
def check_group_feature_available!(feature)
render_404 unless group.feature_available?(feature)
end
def method_missing(method_sym, *arguments, &block)
case method_sym.to_s
when /\Acheck_group_(.*)_available!\z/
check_group_feature_available!($1.to_sym)
else
super
end
end
end
end
end
class Groups::AnalyticsController < Groups::ApplicationController class Groups::AnalyticsController < Groups::ApplicationController
before_action :group before_action :group
before_action :check_group_contribution_analytics_available!
layout 'group' layout 'group'
......
class Groups::ApplicationController < ApplicationController class Groups::ApplicationController < ApplicationController
include RoutableActions include RoutableActions
prepend EE::Groups::ApplicationController
layout 'group' layout 'group'
......
...@@ -62,13 +62,13 @@ class UsersFinder ...@@ -62,13 +62,13 @@ class UsersFinder
end end
def by_external_identity(users) def by_external_identity(users)
return users unless current_user.admin? && params[:extern_uid] && params[:provider] return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid])) users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end end
def by_external(users) def by_external(users)
return users = users.where.not(external: true) unless current_user.admin? return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external] return users unless params[:external]
users.external users.external
......
module BoardsHelper module BoardsHelper
prepend EE::BoardsHelper
def board_data def board_data
board = @board || @boards.first board = @board || @boards.first
......
module EE
module BoardsHelper
def board_data
super.merge(focus_mode_available: @project.feature_available?(:issue_board_focus_mode).to_s)
end
end
end
...@@ -3,6 +3,7 @@ class License < ActiveRecord::Base ...@@ -3,6 +3,7 @@ class License < ActiveRecord::Base
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
BURNDOWN_CHARTS_FEATURE = 'BurndownCharts'.freeze BURNDOWN_CHARTS_FEATURE = 'BurndownCharts'.freeze
CONTRIBUTION_ANALYTICS_FEATURE = 'ContributionAnalytics'.freeze
DEPLOY_BOARD_FEATURE = 'GitLab_DeployBoard'.freeze DEPLOY_BOARD_FEATURE = 'GitLab_DeployBoard'.freeze
ELASTIC_SEARCH_FEATURE = 'GitLab_ElasticSearch'.freeze ELASTIC_SEARCH_FEATURE = 'GitLab_ElasticSearch'.freeze
EXPORT_ISSUES_FEATURE = 'GitLab_ExportIssues'.freeze EXPORT_ISSUES_FEATURE = 'GitLab_ExportIssues'.freeze
...@@ -10,6 +11,7 @@ class License < ActiveRecord::Base ...@@ -10,6 +11,7 @@ class License < ActiveRecord::Base
FILE_LOCK_FEATURE = 'GitLab_FileLocks'.freeze FILE_LOCK_FEATURE = 'GitLab_FileLocks'.freeze
GEO_FEATURE = 'GitLab_Geo'.freeze GEO_FEATURE = 'GitLab_Geo'.freeze
ISSUABLE_DEFAULT_TEMPLATES_FEATURE = 'GitLab_IssuableDefaultTemplates'.freeze ISSUABLE_DEFAULT_TEMPLATES_FEATURE = 'GitLab_IssuableDefaultTemplates'.freeze
ISSUE_BOARDS_FOCUS_MODE_FEATURE = 'IssueBoardsFocusMode'.freeze
ISSUE_WEIGHTS_FEATURE = 'GitLab_IssueWeights'.freeze ISSUE_WEIGHTS_FEATURE = 'GitLab_IssueWeights'.freeze
MERGE_REQUEST_REBASE_FEATURE = 'GitLab_MergeRequestRebase'.freeze MERGE_REQUEST_REBASE_FEATURE = 'GitLab_MergeRequestRebase'.freeze
MERGE_REQUEST_SQUASH_FEATURE = 'GitLab_MergeRequestSquash'.freeze MERGE_REQUEST_SQUASH_FEATURE = 'GitLab_MergeRequestSquash'.freeze
...@@ -27,11 +29,13 @@ class License < ActiveRecord::Base ...@@ -27,11 +29,13 @@ class License < ActiveRecord::Base
# Features that make sense to Namespace: # Features that make sense to Namespace:
burndown_charts: BURNDOWN_CHARTS_FEATURE, burndown_charts: BURNDOWN_CHARTS_FEATURE,
contribution_analytics: CONTRIBUTION_ANALYTICS_FEATURE,
deploy_board: DEPLOY_BOARD_FEATURE, deploy_board: DEPLOY_BOARD_FEATURE,
export_issues: EXPORT_ISSUES_FEATURE, export_issues: EXPORT_ISSUES_FEATURE,
fast_forward_merge: FAST_FORWARD_MERGE_FEATURE, fast_forward_merge: FAST_FORWARD_MERGE_FEATURE,
file_lock: FILE_LOCK_FEATURE, file_lock: FILE_LOCK_FEATURE,
issuable_default_templates: ISSUABLE_DEFAULT_TEMPLATES_FEATURE, issuable_default_templates: ISSUABLE_DEFAULT_TEMPLATES_FEATURE,
issue_board_focus_mode: ISSUE_BOARDS_FOCUS_MODE_FEATURE,
issue_weights: ISSUE_WEIGHTS_FEATURE, issue_weights: ISSUE_WEIGHTS_FEATURE,
merge_request_rebase: MERGE_REQUEST_REBASE_FEATURE, merge_request_rebase: MERGE_REQUEST_REBASE_FEATURE,
merge_request_squash: MERGE_REQUEST_SQUASH_FEATURE merge_request_squash: MERGE_REQUEST_SQUASH_FEATURE
...@@ -44,10 +48,12 @@ class License < ActiveRecord::Base ...@@ -44,10 +48,12 @@ class License < ActiveRecord::Base
EES_FEATURES = [ EES_FEATURES = [
{ BURNDOWN_CHARTS_FEATURE => 1 }, { BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ ELASTIC_SEARCH_FEATURE => 1 }, { ELASTIC_SEARCH_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 }, { EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 }, { FAST_FORWARD_MERGE_FEATURE => 1 },
{ ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 }, { ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 },
{ ISSUE_BOARDS_FOCUS_MODE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 }, { ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 }, { MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 }, { MERGE_REQUEST_SQUASH_FEATURE => 1 },
...@@ -76,16 +82,16 @@ class License < ActiveRecord::Base ...@@ -76,16 +82,16 @@ class License < ActiveRecord::Base
# Early adopters should not earn new features as they're # Early adopters should not earn new features as they're
# introduced. # introduced.
EARLY_ADOPTER_FEATURES = [ EARLY_ADOPTER_FEATURES = [
# TODO: Add EES features
# https://gitlab.com/gitlab-org/gitlab-ee/issues/2335)
{ AUDITOR_USER_FEATURE => 1 }, { AUDITOR_USER_FEATURE => 1 },
{ BURNDOWN_CHARTS_FEATURE => 1 }, { BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ DEPLOY_BOARD_FEATURE => 1 }, { DEPLOY_BOARD_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 }, { EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 }, { FAST_FORWARD_MERGE_FEATURE => 1 },
{ FILE_LOCK_FEATURE => 1 }, { FILE_LOCK_FEATURE => 1 },
{ GEO_FEATURE => 1 }, { GEO_FEATURE => 1 },
{ ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 }, { ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 },
{ ISSUE_BOARDS_FOCUS_MODE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 }, { ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 }, { MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 }, { MERGE_REQUEST_SQUASH_FEATURE => 1 },
......
require_dependency 'declarative_policy' require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin" desc "User is an instance admin"
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:admin) { @user&.admin? } condition(:admin) { @user&.admin? }
...@@ -11,6 +13,11 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -11,6 +13,11 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group } condition(:can_create_group) { @user&.can_create_group }
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
# EE Extensions # EE Extensions
with_scope :user with_scope :user
condition(:auditor, score: 0) { @user&.auditor? } condition(:auditor, score: 0) { @user&.auditor? }
......
...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy ...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? } condition(:access_locked) { @user.access_locked? }
rule { anonymous }.prevent_all rule { anonymous }.policy do
prevent :log_in
prevent :access_api
prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
prevent :create_group
end
rule { default }.policy do rule { default }.policy do
enable :read_users_list
enable :log_in enable :log_in
enable :access_api enable :access_api
enable :access_git enable :access_git
...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy ...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do rule { access_locked }.policy do
prevent :log_in prevent :log_in
end end
rule { ~restricted_public_level }.policy do
enable :read_users_list
end
end end
class UserPolicy < BasePolicy class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
desc "The current user is the user in question" desc "The current user is the user in question"
condition(:user_is_self, score: 0) { @subject == @user } condition(:user_is_self, score: 0) { @subject == @user }
......
...@@ -24,10 +24,11 @@ ...@@ -24,10 +24,11 @@
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
%span %span
Members Members
= nav_link(path: 'analytics#show') do - if @group.feature_available?(:contribution_analytics)
= link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do = nav_link(path: 'analytics#show') do
%span = link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
Contribution Analytics %span
Contribution Analytics
- if current_user && can?(current_user, :admin_group, @group) - if current_user && can?(current_user, :admin_group, @group)
= nav_link(path: %w[groups#projects groups#edit ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]) do = nav_link(path: %w[groups#projects groups#edit ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]) do
= link_to edit_group_path(@group), title: 'Settings' do = link_to edit_group_path(@group), title: 'Settings' do
......
---
title: Add license checks for focus mode on the issue board
merge_request: 2303
author:
---
title: Add namespace license checks for Contribution Analytics
merge_request: 2302
author:
---
title: Allow unauthenticated access to the /api/v4/users API
merge_request: 12445
author:
...@@ -4,10 +4,13 @@ module API ...@@ -4,10 +4,13 @@ module API
before do before do
allow_access_with_scope :read_user if request.get? allow_access_with_scope :read_user if request.get?
authenticate!
end end
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
before do
authenticate_non_get!
end
helpers do helpers do
def find_user(params) def find_user(params)
id = params[:user_id] || params[:id] id = params[:user_id] || params[:id]
...@@ -57,15 +60,22 @@ module API ...@@ -57,15 +60,22 @@ module API
use :pagination use :pagination
end end
get do get do
unless can?(current_user, :read_users_list)
render_api_error!("Not authorized.", 403)
end
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
users = UsersFinder.new(current_user, params).execute users = UsersFinder.new(current_user, params).execute
entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic authorized = can?(current_user, :read_users_list)
# When `current_user` is not present, require that the `username`
# parameter is passed, to prevent an unauthenticated user from accessing
# a list of all the users on the GitLab instance. `UsersFinder` performs
# an exact match on the `username` parameter, so we are guaranteed to
# get either 0 or 1 `users` here.
authorized &&= params[:username].present? if current_user.blank?
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
present paginate(users), with: entity present paginate(users), with: entity
end end
...@@ -404,6 +414,10 @@ module API ...@@ -404,6 +414,10 @@ module API
end end
resource :user do resource :user do
before do
authenticate!
end
desc 'Get the currently authenticated user' do desc 'Get the currently authenticated user' do
success Entities::UserPublic success Entities::UserPublic
end end
......
...@@ -39,6 +39,14 @@ describe Groups::AnalyticsController do ...@@ -39,6 +39,14 @@ describe Groups::AnalyticsController do
create_push_event(user3, project) create_push_event(user3, project)
end end
it 'returns 404 when feature is not available' do
stub_licensed_features(contribution_analytics: false)
get :show, group_id: group.path
expect(response).to have_http_status(404)
end
it 'sets instance variables properly' do it 'sets instance variables properly' do
get :show, group_id: group.path get :show, group_id: group.path
......
require 'spec_helper'
describe 'issue boards', feature: true, js: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public) }
let!(:board) { create(:board, project: project) }
before do
project.add_developer(user)
login_as(user)
end
context 'issue board focus mode' do
it 'shows the button when the feature is enabled' do
stub_licensed_features(issue_board_focus_mode: true)
visit_board_page
expect(page).to have_link('Toggle focus mode')
end
it 'hides the button when the feature is enabled' do
stub_licensed_features(issue_board_focus_mode: false)
visit_board_page
expect(page).not_to have_link('Toggle focus mode')
end
end
def visit_board_page
visit namespace_project_boards_path(project.namespace, project)
wait_for_requests
end
end
require 'spec_helper'
describe GlobalPolicy, models: true do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
subject { GlobalPolicy.new(current_user, [user]) }
describe "reading the list of users" do
context "for a logged in user" do
it { is_expected.to be_allowed(:read_users_list) }
end
context "for an anonymous user" do
let(:current_user) { nil }
context "when the public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it { is_expected.not_to be_allowed(:read_users_list) }
end
context "when the public level is not restricted" do
before do
stub_application_setting(restricted_visibility_levels: [])
end
it { is_expected.to be_allowed(:read_users_list) }
end
end
end
end
...@@ -13,9 +13,40 @@ describe API::Users do ...@@ -13,9 +13,40 @@ describe API::Users do
describe 'GET /users' do describe 'GET /users' do
context "when unauthenticated" do context "when unauthenticated" do
it "returns authentication error" do it "returns authorization error when the `username` parameter is not passed" do
get api("/users") get api("/users")
expect(response).to have_http_status(401)
expect(response).to have_http_status(403)
end
it "returns the user when a valid `username` parameter is passed" do
user = create(:user)
get api("/users"), username: user.username
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response[0]['id']).to eq(user.id)
expect(json_response[0]['username']).to eq(user.username)
end
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
user = create(:user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
get api("/users"), username: user.username
expect(response).to have_http_status(403)
end
it "returns an empty response when an invalid `username` parameter is passed" do
get api("/users"), username: 'invalid'
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end end
end end
...@@ -150,6 +181,7 @@ describe API::Users do ...@@ -150,6 +181,7 @@ describe API::Users do
describe "GET /users/:id" do describe "GET /users/:id" do
it "returns a user by id" do it "returns a user by id" do
get api("/users/#{user.id}", user) get api("/users/#{user.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username) expect(json_response['username']).to eq(user.username)
end end
...@@ -160,9 +192,22 @@ describe API::Users do ...@@ -160,9 +192,22 @@ describe API::Users do
expect(json_response['is_admin']).to be_nil expect(json_response['is_admin']).to be_nil
end end
it "returns a 401 if unauthenticated" do context 'for an anonymous user' do
get api("/users/9998") it "returns a user by id" do
expect(response).to have_http_status(401) get api("/users/#{user.id}")
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
it "returns a 404 if the target user is present but inaccessible" do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
get api("/users/#{user.id}")
expect(response).to have_http_status(404)
end
end end
it "returns a 404 error if user id not found" do it "returns a 404 error if user id not found" do
......
require 'spec_helper'
describe 'layouts/nav/_group' do
before do
assign(:group, create(:group))
end
describe 'contribution analytics tab' do
it 'is not visible when there is no valid license' do
stub_licensed_features(contribution_analytics: false)
render
expect(rendered).not_to have_text 'Contribution Analytics'
end
it 'is not visible when there is no valid license' do
stub_licensed_features(contribution_analytics: true)
render
expect(rendered).to have_text 'Contribution Analytics'
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