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 @@ $(() => {
modal: ModalStore.store,
store: Store.state,
isFullscreen: false,
focusModeAvailable: gl.utils.convertPermissionToBoolean($boardApp.dataset.focusModeAvailable),
},
watch: {
disabled() {
......@@ -177,6 +178,8 @@ $(() => {
}
},
toggleFocusMode() {
if (!this.focusModeAvailable) { return; }
$(this.$refs.toggleFocusModeButton).tooltip('hide');
issueBoardsContent.classList.toggle('is-focused');
......@@ -206,6 +209,7 @@ $(() => {
aria-label="Toggle focus mode"
title="Toggle focus mode"
ref="toggleFocusModeButton"
v-if="focusModeAvailable"
@click="toggleFocusMode">
<span v-show="isFullscreen">
${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
before_action :group
before_action :check_group_contribution_analytics_available!
layout 'group'
......
class Groups::ApplicationController < ApplicationController
include RoutableActions
prepend EE::Groups::ApplicationController
layout 'group'
......
......@@ -62,13 +62,13 @@ class UsersFinder
end
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]))
end
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]
users.external
......
module BoardsHelper
prepend EE::BoardsHelper
def board_data
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
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
BURNDOWN_CHARTS_FEATURE = 'BurndownCharts'.freeze
CONTRIBUTION_ANALYTICS_FEATURE = 'ContributionAnalytics'.freeze
DEPLOY_BOARD_FEATURE = 'GitLab_DeployBoard'.freeze
ELASTIC_SEARCH_FEATURE = 'GitLab_ElasticSearch'.freeze
EXPORT_ISSUES_FEATURE = 'GitLab_ExportIssues'.freeze
......@@ -10,6 +11,7 @@ class License < ActiveRecord::Base
FILE_LOCK_FEATURE = 'GitLab_FileLocks'.freeze
GEO_FEATURE = 'GitLab_Geo'.freeze
ISSUABLE_DEFAULT_TEMPLATES_FEATURE = 'GitLab_IssuableDefaultTemplates'.freeze
ISSUE_BOARDS_FOCUS_MODE_FEATURE = 'IssueBoardsFocusMode'.freeze
ISSUE_WEIGHTS_FEATURE = 'GitLab_IssueWeights'.freeze
MERGE_REQUEST_REBASE_FEATURE = 'GitLab_MergeRequestRebase'.freeze
MERGE_REQUEST_SQUASH_FEATURE = 'GitLab_MergeRequestSquash'.freeze
......@@ -27,11 +29,13 @@ class License < ActiveRecord::Base
# Features that make sense to Namespace:
burndown_charts: BURNDOWN_CHARTS_FEATURE,
contribution_analytics: CONTRIBUTION_ANALYTICS_FEATURE,
deploy_board: DEPLOY_BOARD_FEATURE,
export_issues: EXPORT_ISSUES_FEATURE,
fast_forward_merge: FAST_FORWARD_MERGE_FEATURE,
file_lock: FILE_LOCK_FEATURE,
issuable_default_templates: ISSUABLE_DEFAULT_TEMPLATES_FEATURE,
issue_board_focus_mode: ISSUE_BOARDS_FOCUS_MODE_FEATURE,
issue_weights: ISSUE_WEIGHTS_FEATURE,
merge_request_rebase: MERGE_REQUEST_REBASE_FEATURE,
merge_request_squash: MERGE_REQUEST_SQUASH_FEATURE
......@@ -44,10 +48,12 @@ class License < ActiveRecord::Base
EES_FEATURES = [
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ ELASTIC_SEARCH_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 },
{ ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 },
{ ISSUE_BOARDS_FOCUS_MODE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
......@@ -76,16 +82,16 @@ class License < ActiveRecord::Base
# Early adopters should not earn new features as they're
# introduced.
EARLY_ADOPTER_FEATURES = [
# TODO: Add EES features
# https://gitlab.com/gitlab-org/gitlab-ee/issues/2335)
{ AUDITOR_USER_FEATURE => 1 },
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ DEPLOY_BOARD_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 },
{ FILE_LOCK_FEATURE => 1 },
{ GEO_FEATURE => 1 },
{ ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 },
{ ISSUE_BOARDS_FOCUS_MODE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
......
require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin"
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
......@@ -11,6 +13,11 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
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
with_scope :user
condition(:auditor, score: 0) { @user&.auditor? }
......
......@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0
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
enable :read_users_list
enable :log_in
enable :access_api
enable :access_git
......@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do
prevent :log_in
end
rule { ~restricted_public_level }.policy do
enable :read_users_list
end
end
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"
condition(:user_is_self, score: 0) { @subject == @user }
......
......@@ -24,10 +24,11 @@
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
= nav_link(path: 'analytics#show') do
= link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
%span
Contribution Analytics
- if @group.feature_available?(:contribution_analytics)
= nav_link(path: 'analytics#show') do
= link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
%span
Contribution Analytics
- 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
= 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
before do
allow_access_with_scope :read_user if request.get?
authenticate!
end
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
before do
authenticate_non_get!
end
helpers do
def find_user(params)
id = params[:user_id] || params[:id]
......@@ -57,15 +60,22 @@ module API
use :pagination
end
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?)
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
end
......@@ -404,6 +414,10 @@ module API
end
resource :user do
before do
authenticate!
end
desc 'Get the currently authenticated user' do
success Entities::UserPublic
end
......
......@@ -39,6 +39,14 @@ describe Groups::AnalyticsController do
create_push_event(user3, project)
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
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
describe 'GET /users' 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")
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
......@@ -150,6 +181,7 @@ describe API::Users do
describe "GET /users/:id" do
it "returns a user by id" do
get api("/users/#{user.id}", user)
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
......@@ -160,9 +192,22 @@ describe API::Users do
expect(json_response['is_admin']).to be_nil
end
it "returns a 401 if unauthenticated" do
get api("/users/9998")
expect(response).to have_http_status(401)
context 'for an anonymous user' do
it "returns a user by id" do
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
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