Commit b042382b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent eabf8fd7
# When adding a group as a code owner, make sure to invite the group to the
# project here: https://gitlab.com/gitlab-org/gitlab/-/project_members
# As described in https://docs.gitlab.com/ee/user/project/code_owners.html
# Backend Maintainers are the default for all ruby files
*.rb @gitlab-org/maintainers/rails-backend
*.rake @gitlab-org/maintainers/rails-backend
......@@ -28,9 +32,13 @@ lib/gitlab/github_import/ @gitlab-org/maintainers/database
/ee/app/models/project_alias.rb @patrickbajao
/ee/lib/api/project_aliases.rb @patrickbajao
# Quality owned files
/qa/ @gl-quality
# Engineering Productivity owned files
/.gitlab-ci.yml @gl-quality/eng-prod
/.gitlab/ci/ @gl-quality/eng-prod
/.gitlab/CODEOWNERS @gl-quality/eng-prod
Dangerfile @gl-quality/eng-prod
/danger/ @gl-quality/eng-prod
/lib/gitlab/danger/ @gl-quality/eng-prod
......
......@@ -1326,7 +1326,7 @@ DEPENDENCIES
pry-rails (~> 0.3.9)
rack (~> 2.0.7)
rack-attack (~> 6.2.0)
rack-cors (~> 1.0.0)
rack-cors (~> 1.0.6)
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
rack-timeout
......
......@@ -37,6 +37,7 @@ class ApplicationController < ActionController::Base
around_action :set_current_context
around_action :set_locale
around_action :set_session_storage
around_action :set_current_admin
after_action :set_page_title_header, if: :json_request?
after_action :limit_session_time, if: -> { !current_user }
......@@ -473,6 +474,13 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
def set_current_admin(&block)
return yield unless Feature.enabled?(:user_mode_in_session)
return yield unless current_user
Gitlab::Auth::CurrentUserMode.with_current_admin(current_user, &block)
end
def html_request?
request.format.html?
end
......
......@@ -10,9 +10,9 @@ module CycleAnalyticsParams
end
def cycle_analytics_group_params
return {} unless params[:cycle_analytics].present?
return {} unless params.present?
params[:cycle_analytics].permit(:start_date, :created_after, :created_before, project_ids: [])
params.permit(:group_id, :start_date, :created_after, :created_before, project_ids: [])
end
def options(params)
......
......@@ -9,7 +9,10 @@ module MergeRequests
def self.enqueue!
ids = MergeRequestDiff.ids_for_external_storage_migration(limit: MAX_JOBS)
MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] }) # rubocop:disable Scalability/BulkPerformWithContext
# rubocop:disable Scalability/BulkPerformWithContext
# https://gitlab.com/gitlab-org/gitlab/issues/202100
MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] })
# rubocop:enable Scalability/BulkPerformWithContext
end
def initialize(merge_request_diff)
......
......@@ -30,6 +30,27 @@ module Projects
settings = params[:error_tracking_setting_attributes]
return {} if settings.blank?
if error_tracking_params_partial_updates?(settings)
error_tracking_params_for_partial_update(settings)
else
error_tracking_params_for_update(settings)
end
end
def error_tracking_params_partial_updates?(settings)
# Help from @splattael :bow:
# Make sure we're converting to symbols because
# * ActionController::Parameters#keys returns a list of strings
# * in specs we're using hashes with symbols as keys
settings.keys.map(&:to_sym) == %i[enabled]
end
def error_tracking_params_for_partial_update(settings)
{ error_tracking_setting_attributes: settings }
end
def error_tracking_params_for_update(settings)
api_url = ::ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
api_host: settings[:api_host],
project_slug: settings.dig(:project, :slug),
......
......@@ -2,7 +2,12 @@
class ScheduleMigrateExternalDiffsWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
# rubocop:disable Scalability/CronWorkerContext:
# This schedules the `MigrateExternalDiffsWorker`
# issue for adding context: https://gitlab.com/gitlab-org/gitlab/issues/202100
include CronjobQueue
# rubocop:enable Scalability/CronWorkerContext:
include Gitlab::ExclusiveLeaseHelpers
feature_category :source_code_management
......
---
title: Add non_archived param to issues API endpoint to filter issues from archived projects
merge_request: 23785
author:
type: added
---
title: Add non_archived param to group merge requests API endpoint to filter MRs from non archived projects
merge_request: 23809
author:
type: added
---
title: Add API to enable and disable error tracking settings
merge_request: 24220
author: Rajendra Kadam
type: added
---
title: Admin mode support in sidekiq jobs
merge_request: 24388
author: Diego Louzán
type: changed
......@@ -941,7 +941,7 @@ projects.each do |p|
container_repositories.each do |c|
c.tags.each do |t|
project_total_size = project_total_size + t.total_size
project_total_size = project_total_size + t.total_size unless t.total_size.nil?
end
end
......
......@@ -30,3 +30,31 @@ Example response:
"api_url": "https://sentry.io/api/0/projects/myawesomeproject/project"
}
```
### Enable or disable the Error Tracking project settings
The API allows you to enable or disable the Error Tracking settings for a project. Only for project maintainers.
```
PATCH /projects/:id/error_tracking/settings
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `active` | boolean | yes | Pass `true` to enable the already configured error tracking settings or `false` to disable it. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/error_tracking/settings?active=true
```
Example response:
```json
{
"active": true,
"project_name": "sample sentry project",
"sentry_external_url": "https://sentry.io/myawesomeproject/project",
"api_url": "https://sentry.io/api/0/projects/myawesomeproject/project"
}
```
......@@ -222,6 +222,7 @@ GET /groups/:id/issues?confidential=true
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential` | Boolean | no | Filter confidential or public issues. |
| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in` |
| `non_archived` | Boolean | no | Return issues from non archived projects. Default is true. _(Introduced in [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23785))_ |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues
......
......@@ -396,7 +396,8 @@ Parameters:
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
| `search` | string | no | Search merge requests against their `title` and `description` |
| `search` | string | no | Search merge requests against their `title` and `description` |
| `non_archived` | Boolean | no | Return merge requests from non archived projects only. Default is true. _(Introduced in [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23809))_ |
```json
[
......
......@@ -9,7 +9,4 @@ You can import your existing repositories by providing the Git URL:
1. Click **Create project** to begin the import process
1. Once complete, you will be redirected to your newly created project
NOTE: **Note:**
If your password has special characters, you will need to enter them URL encoded, please see the [GitLab issue](https://gitlab.com/gitlab-org/gitlab/issues/29952) for more information.
![Import project by repo URL](img/import_projects_from_repo_url.png)
......@@ -23,6 +23,34 @@ module API
present setting, with: Entities::ErrorTracking::ProjectSetting
end
desc 'Enable or disable error tracking settings for the project' do
detail 'This feature was introduced in GitLab 12.8.'
success Entities::ErrorTracking::ProjectSetting
end
params do
requires :active, type: Boolean, desc: 'Specifying whether to enable or disable error tracking settings', allow_blank: false
end
patch ':id/error_tracking/settings/' do
authorize! :admin_operations, user_project
setting = user_project.error_tracking_setting
not_found!('Error Tracking Setting') unless setting
update_params = {
error_tracking_setting_attributes: { enabled: params[:active] }
}
result = ::Projects::Operations::UpdateService.new(user_project, current_user, update_params).execute
if result[:status] == :success
present setting, with: Entities::ErrorTracking::ProjectSetting
else
result
end
end
end
end
end
......@@ -120,6 +120,7 @@ module API
end
params do
use :issues_params
optional :non_archived, type: Boolean, desc: 'Return issues from non archived projects', default: true
end
get ":id/issues" do
issues = paginate(find_issues(group_id: user_group.id, include_subgroups: true))
......
......@@ -141,6 +141,8 @@ module API
end
params do
use :merge_requests_params
optional :non_archived, type: Boolean, desc: 'Return merge requests from non archived projects',
default: true
end
get ":id/merge_requests" do
merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true)
......
......@@ -10,12 +10,54 @@ module Gitlab
class CurrentUserMode
NotRequestedError = Class.new(StandardError)
# RequestStore entries
CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY = { res: :current_user_mode, data: :bypass_session_admin_id }.freeze
CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY = { res: :current_user_mode, data: :current_admin }.freeze
# SessionStore entries
SESSION_STORE_KEY = :current_user_mode
ADMIN_MODE_START_TIME_KEY = 'admin_mode'
ADMIN_MODE_REQUESTED_TIME_KEY = 'admin_mode_requested'
ADMIN_MODE_START_TIME_KEY = :admin_mode
ADMIN_MODE_REQUESTED_TIME_KEY = :admin_mode_requested
MAX_ADMIN_MODE_TIME = 6.hours
ADMIN_MODE_REQUESTED_GRACE_PERIOD = 5.minutes
class << self
# Admin mode activation requires storing a flag in the user session. Using this
# method when scheduling jobs in Sidekiq will bypass the session check for a
# user that was already in admin mode
def bypass_session!(admin_id)
Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY] = admin_id
Gitlab::AppLogger.debug("Bypassing session in admin mode for: #{admin_id}")
yield
ensure
Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY)
end
def bypass_session_admin_id
Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY]
end
# Store in the current request the provided user model (only if in admin mode)
# and yield
def with_current_admin(admin)
return yield unless self.new(admin).admin_mode?
Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY] = admin
Gitlab::AppLogger.debug("Admin mode active for: #{admin.username}")
yield
ensure
Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY)
end
def current_admin
Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY]
end
end
def initialize(user)
@user = user
end
......@@ -42,7 +84,7 @@ module Gitlab
raise NotRequestedError unless admin_mode_requested?
reset_request_store
reset_request_store_cache_entries
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
current_session_data[ADMIN_MODE_START_TIME_KEY] = Time.now
......@@ -55,7 +97,7 @@ module Gitlab
def disable_admin_mode!
return unless user&.admin?
reset_request_store
reset_request_store_cache_entries
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
current_session_data[ADMIN_MODE_START_TIME_KEY] = nil
......@@ -64,7 +106,7 @@ module Gitlab
def request_admin_mode!
return unless user&.admin?
reset_request_store
reset_request_store_cache_entries
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = Time.now
end
......@@ -73,10 +115,12 @@ module Gitlab
attr_reader :user
# RequestStore entry to cache #admin_mode? result
def admin_mode_rs_key
@admin_mode_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode? }
end
# RequestStore entry to cache #admin_mode_requested? result
def admin_mode_requested_rs_key
@admin_mode_requested_rs_key ||= { res: :current_user_mode, user: user.id, method: :admin_mode_requested? }
end
......@@ -86,6 +130,7 @@ module Gitlab
end
def any_session_with_admin_mode?
return true if bypass_session?
return true if current_session_data.initiated? && current_session_data[ADMIN_MODE_START_TIME_KEY].to_i > MAX_ADMIN_MODE_TIME.ago.to_i
all_sessions.any? do |session|
......@@ -103,7 +148,11 @@ module Gitlab
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY].to_i > ADMIN_MODE_REQUESTED_GRACE_PERIOD.ago.to_i
end
def reset_request_store
def bypass_session?
user&.id && user.id == self.class.bypass_session_admin_id
end
def reset_request_store_cache_entries
Gitlab::SafeRequestStore.delete(admin_mode_rs_key)
Gitlab::SafeRequestStore.delete(admin_mode_requested_rs_key)
end
......
......@@ -17,6 +17,7 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Labkit::Middleware::Sidekiq::Server
chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger
chain.add Gitlab::SidekiqMiddleware::AdminMode::Server
chain.add Gitlab::SidekiqStatus::ServerMiddleware
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Server
end
......@@ -31,6 +32,7 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::ClientMetrics
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Client # needs to be before the Labkit middleware
chain.add Labkit::Middleware::Sidekiq::Client
chain.add Gitlab::SidekiqMiddleware::AdminMode::Client
end
end
end
......
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
module AdminMode
# Checks if admin mode is enabled for the request creating the sidekiq job
# by examining if admin mode has been enabled for the user
# If enabled then it injects a job field that persists through the job execution
class Client
def call(_worker_class, job, _queue, _redis_pool)
return yield unless Feature.enabled?(:user_mode_in_session)
# Admin mode enabled in the original request or in a nested sidekiq job
admin_mode_user_id = find_admin_user_id
if admin_mode_user_id
job['admin_mode_user_id'] ||= admin_mode_user_id
Gitlab::AppLogger.debug("AdminMode::Client injected admin mode for job: #{job.inspect}")
end
yield
end
private
def find_admin_user_id
Gitlab::Auth::CurrentUserMode.current_admin&.id ||
Gitlab::Auth::CurrentUserMode.bypass_session_admin_id
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
module AdminMode
class Server
def call(_worker, job, _queue)
return yield unless Feature.enabled?(:user_mode_in_session)
admin_mode_user_id = job['admin_mode_user_id']
# Do not bypass session if this job was not enabled with admin mode on
return yield unless admin_mode_user_id
Gitlab::Auth::CurrentUserMode.bypass_session!(admin_mode_user_id) do
Gitlab::AppLogger.debug("AdminMode::Server bypasses session for admin mode in job: #{job.inspect}")
yield
end
end
end
end
end
end
......@@ -96,7 +96,7 @@ describe GroupsController do
User.where(id: [admin, owner, maintainer, developer, guest]).update_all(can_create_group: can_create_group_status)
end
[:admin, :owner].each do |member_type|
[:admin, :owner, :maintainer].each do |member_type|
context "and logged in as #{member_type.capitalize}" do
it_behaves_like 'member with ability to create subgroups' do
let(:member) { send(member_type) }
......@@ -104,7 +104,7 @@ describe GroupsController do
end
end
[:guest, :developer, :maintainer].each do |member_type|
[:guest, :developer].each do |member_type|
context "and logged in as #{member_type.capitalize}" do
it_behaves_like 'member without ability to create subgroups' do
let(:member) { send(member_type) }
......
......@@ -8,5 +8,9 @@ FactoryBot.define do
token { 'access_token_123' }
project_name { 'Sentry Project' }
organization_name { 'Sentry Org' }
trait :disabled do
enabled { false }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
# Test an operation that triggers background jobs requiring administrative rights
describe 'Admin mode for workers', :do_not_mock_admin_mode, :request_store, :clean_gitlab_redis_shared_state do
let(:user) { create(:user) }
let(:user_to_delete) { create(:user) }
before do
add_sidekiq_middleware
sign_in(user)
end
context 'as a regular user' do
it 'cannot delete user' do
visit admin_user_path(user_to_delete)
expect(page).to have_gitlab_http_status(:not_found)
end
end
context 'as an admin user' do
let(:user) { create(:admin) }
context 'when admin mode disabled' do
it 'cannot delete user', :js do
visit admin_user_path(user_to_delete)
expect(page).to have_content('Re-authentication required')
end
end
context 'when admin mode enabled', :delete do
before do
gitlab_enable_admin_mode_sign_in(user)
end
it 'can delete user', :sidekiq, :js do
visit admin_user_path(user_to_delete)
click_button 'Delete user'
page.within '.modal-dialog' do
find("input[name='username']").send_keys(user_to_delete.name)
click_button 'Delete user'
wait_for_requests
end
expect(page).to have_content('The user is being deleted.')
# Perform jobs while logged out so that admin mode is only enabled in job metadata
execute_jobs_signed_out(user)
visit admin_user_path(user_to_delete)
expect(page).to have_title('Not Found')
end
end
end
def add_sidekiq_middleware
Sidekiq::Testing.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::AdminMode::Server
end
end
def execute_jobs_signed_out(user)
gitlab_sign_out
Sidekiq::Worker.drain_all
sign_in(user)
gitlab_enable_admin_mode_sign_in(user)
end
end
......@@ -2,46 +2,64 @@
require 'spec_helper'
describe 'Admin uses repository checks' do
describe 'Admin uses repository checks', :request_store, :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
include StubENV
let(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(create(:admin))
sign_in(admin)
end
it 'to trigger a single check' do
project = create(:project)
visit_admin_project_page(project)
context 'when admin mode is disabled' do
it 'admin project page requires admin mode' do
project = create(:project)
visit_admin_project_page(project)
page.within('.repository-check') do
click_button 'Trigger repository check'
expect(page).not_to have_css('.repository-check')
expect(page).to have_content('Enter Admin Mode')
end
expect(page).to have_content('Repository check was triggered')
end
it 'to see a single failed repository check', :js do
project = create(:project)
project.update_columns(
last_repository_check_failed: true,
last_repository_check_at: Time.now
)
visit_admin_project_page(project)
context 'when admin mode is enabled' do
before do
gitlab_enable_admin_mode_sign_in(admin)
end
it 'to trigger a single check', :js do
project = create(:project)
visit_admin_project_page(project)
page.within('.repository-check') do
click_button 'Trigger repository check'
end
page.within('.alert') do
expect(page.text).to match(/Last repository check \(just now\) failed/)
expect(page).to have_content('Repository check was triggered')
end
end
it 'to clear all repository checks', :js do
visit repository_admin_application_settings_path
it 'to see a single failed repository check', :js do
project = create(:project)
project.update_columns(
last_repository_check_failed: true,
last_repository_check_at: Time.now
)
visit_admin_project_page(project)
page.within('.alert') do
expect(page.text).to match(/Last repository check \(just now\) failed/)
end
end
expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
it 'to clear all repository checks', :js do
visit repository_admin_application_settings_path
accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) }
expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
expect(page).to have_content('Started asynchronous removal of all repository check states.')
accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) }
expect(page).to have_content('Started asynchronous removal of all repository check states.')
end
end
def visit_admin_project_page(project)
......
import './get_client_rects';
import './inner_text';
import './window_scroll_to';
This diff is collapsed.
This diff is collapsed.
......@@ -2,10 +2,10 @@
require 'spec_helper'
describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
include_context 'custom session'
let(:user) { build(:user) }
let(:user) { build_stubbed(:user) }
subject { described_class.new(user) }
......@@ -13,54 +13,66 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session])
end
describe '#admin_mode?', :request_store do
context 'when the user is a regular user' do
it 'is false by default' do
expect(subject.admin_mode?).to be(false)
end
shared_examples 'admin mode cannot be enabled' do
it 'is false by default' do
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password)
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(password: nil)
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(password: nil)
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with empty params' do
subject.enable_admin_mode!
it 'cannot be enabled with empty params' do
subject.enable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
it 'disable has no effect' do
subject.enable_admin_mode!
subject.disable_admin_mode!
it 'disable has no effect' do
subject.enable_admin_mode!
subject.disable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
context 'skipping password validation' do
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
expect(subject.admin_mode?).to be(false)
end
context 'skipping password validation' do
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(skip_password_validation: true)
expect(subject.admin_mode?).to be(false)
end
expect(subject.admin_mode?).to be(false)
end
end
end
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(skip_password_validation: true)
describe '#admin_mode?' do
context 'when the user is a regular user' do
it_behaves_like 'admin mode cannot be enabled'
expect(subject.admin_mode?).to be(false)
context 'bypassing session' do
it_behaves_like 'admin mode cannot be enabled' do
around do |example|
described_class.bypass_session!(user.id) { example.run }
end
end
end
end
context 'when the user is an admin' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
context 'when admin mode not requested' do
it 'is false by default' do
......@@ -148,11 +160,36 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
end
end
context 'bypassing session' do
it 'is active by default' do
described_class.bypass_session!(user.id) do
expect(subject.admin_mode?).to be(true)
end
end
it 'enable has no effect' do
described_class.bypass_session!(user.id) do
subject.request_admin_mode!
subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(true)
end
end
it 'disable has no effect' do
described_class.bypass_session!(user.id) do
subject.disable_admin_mode!
expect(subject.admin_mode?).to be(true)
end
end
end
end
end
describe '#enable_admin_mode!' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
it 'creates a timestamp in the session' do
subject.request_admin_mode!
......@@ -163,7 +200,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
describe '#enable_sessionless_admin_mode!' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
it 'enabled admin mode without password' do
subject.enable_sessionless_admin_mode!
......@@ -173,7 +210,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
describe '#disable_admin_mode!' do
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
it 'sets the session timestamp to nil' do
subject.request_admin_mode!
......@@ -183,6 +220,73 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
end
end
describe '.bypass_session!' do
context 'with a regular user' do
it 'admin mode is false' do
described_class.bypass_session!(user.id) do
expect(subject.admin_mode?).to be(false)
expect(described_class.bypass_session_admin_id).to be(user.id)
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
context 'with an admin user' do
let(:user) { build_stubbed(:user, :admin) }
it 'admin mode is true' do
described_class.bypass_session!(user.id) do
expect(subject.admin_mode?).to be(true)
expect(described_class.bypass_session_admin_id).to be(user.id)
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
end
describe '.with_current_request_admin_mode' do
context 'with a regular user' do
it 'user is not available inside nor outside the yielded block' do
described_class.with_current_admin(user) do
expect(described_class.current_admin).to be_nil
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
context 'with an admin user' do
let(:user) { build_stubbed(:user, :admin) }
context 'admin mode is disabled' do
it 'user is not available inside nor outside the yielded block' do
described_class.with_current_admin(user) do
expect(described_class.current_admin).to be_nil
end
expect(described_class.bypass_session_admin_id).to be_nil
end
end
context 'admin mode is enabled' do
before do
subject.request_admin_mode!
subject.enable_admin_mode!(password: user.password)
end
it 'user is available only inside the yielded block' do
described_class.with_current_admin(user) do
expect(described_class.current_admin).to be(user)
end
expect(described_class.current_admin).to be_nil
end
end
end
end
def expected_session_entry(value_matcher)
{
Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including(
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
Class.new do
def perform; end
end
end
let(:job) { {} }
let(:queue) { :test }
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, nil, &b)
end.to yield_control.once
end
context 'user is a regular user' do
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
context 'user is an administrator' do
let(:admin) { create(:admin) }
context 'admin mode disabled' do
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
context 'admin mode enabled' do
before do
enable_admin_mode!(admin)
end
context 'when sidekiq required context not set' do
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
context 'when user stored in current request' do
it 'has admin mode field in payload' do
Gitlab::Auth::CurrentUserMode.with_current_admin(admin) do
subject.call(worker, job, queue, nil) { nil }
expect(job).to include('admin_mode_user_id' => admin.id)
end
end
end
context 'when bypassing session' do
it 'has admin mode field in payload' do
Gitlab::Auth::CurrentUserMode.bypass_session!(admin.id) do
subject.call(worker, job, queue, nil) { nil }
expect(job).to include('admin_mode_user_id' => admin.id)
end
end
end
end
end
context 'admin mode feature disabled' do
before do
stub_feature_flags(user_mode_in_session: false)
end
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, nil, &b)
end.to yield_control.once
end
it 'no admin mode field in payload' do
subject.call(worker, job, queue, nil) { nil }
expect(job).not_to include('admin_mode_user_id')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do
include AdminModeHelper
let(:worker) do
Class.new do
def perform; end
end
end
let(:job) { {} }
let(:queue) { :test }
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, &b)
end.to yield_control.once
end
context 'job has no admin mode field' do
it 'session is not bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
end
end
end
context 'job has admin mode field' do
let(:admin) { create(:admin) }
context 'nil admin mode id' do
let(:job) { { 'admin_mode_user_id' => nil } }
it 'session is not bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
end
end
end
context 'valid admin mode id' do
let(:job) { { 'admin_mode_user_id' => admin.id } }
it 'session is bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be(admin.id)
end
end
end
end
context 'admin mode feature disabled' do
before do
stub_feature_flags(user_mode_in_session: false)
end
it 'yields block' do
expect do |b|
subject.call(worker, job, queue, &b)
end.to yield_control.once
end
it 'session is not bypassed' do
subject.call(worker, job, queue) do
expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil
end
end
end
end
......@@ -45,7 +45,8 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqMiddleware::ArgumentsLogger,
Gitlab::SidekiqMiddleware::MemoryKiller,
Gitlab::SidekiqMiddleware::RequestStoreMiddleware,
Gitlab::SidekiqMiddleware::WorkerContext::Server
Gitlab::SidekiqMiddleware::WorkerContext::Server,
Gitlab::SidekiqMiddleware::AdminMode::Server
]
end
let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares }
......@@ -115,7 +116,8 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqStatus::ClientMiddleware,
Gitlab::SidekiqMiddleware::ClientMetrics,
Gitlab::SidekiqMiddleware::WorkerContext::Client,
Labkit::Middleware::Sidekiq::Client
Labkit::Middleware::Sidekiq::Client,
Gitlab::SidekiqMiddleware::AdminMode::Client
]
end
......
......@@ -2985,9 +2985,9 @@ describe User, :do_not_mock_admin_mode do
end
end
describe '#can_read_all_resources?' do
describe '#can_read_all_resources?', :request_store do
it 'returns false for regular user' do
user = build(:user)
user = build_stubbed(:user)
expect(user.can_read_all_resources?).to be_falsy
end
......@@ -2995,7 +2995,7 @@ describe User, :do_not_mock_admin_mode do
context 'for admin user' do
include_context 'custom session'
let(:user) { build(:user, :admin) }
let(:user) { build_stubbed(:user, :admin) }
context 'when admin mode is disabled' do
it 'returns false' do
......
......@@ -23,8 +23,8 @@ describe BasePolicy, :do_not_mock_admin_mode do
end
describe 'read cross project' do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
let(:current_user) { build_stubbed(:user) }
let(:user) { build_stubbed(:user) }
subject { described_class.new(current_user, [user]) }
......@@ -38,7 +38,7 @@ describe BasePolicy, :do_not_mock_admin_mode do
it { is_expected.not_to be_allowed(:read_cross_project) }
context 'for admins' do
let(:current_user) { build(:admin) }
let(:current_user) { build_stubbed(:admin) }
subject { described_class.new(current_user, nil) }
......@@ -56,14 +56,14 @@ describe BasePolicy, :do_not_mock_admin_mode do
end
describe 'full private access' do
let(:current_user) { create(:user) }
let(:current_user) { build_stubbed(:user) }
subject { described_class.new(current_user, nil) }
it { is_expected.not_to be_allowed(:read_all_resources) }
context 'for admins' do
let(:current_user) { build(:admin) }
let(:current_user) { build_stubbed(:admin) }
it 'allowed when in admin mode' do
enable_admin_mode!(current_user)
......
......@@ -3,11 +3,129 @@
require 'spec_helper'
describe API::ErrorTracking do
describe "GET /projects/:id/error_tracking/settings" do
let(:user) { create(:user) }
let(:setting) { create(:project_error_tracking_setting) }
let(:project) { setting.project }
let(:user) { create(:user) }
let(:setting) { create(:project_error_tracking_setting) }
let(:project) { setting.project }
shared_examples 'returns project settings' do
it 'returns correct project settings' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(
'active' => setting.reload.enabled,
'project_name' => setting.project_name,
'sentry_external_url' => setting.sentry_external_url,
'api_url' => setting.api_url
)
end
end
shared_examples 'returns 404' do
it 'returns correct project settings' do
subject
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message'])
.to eq('404 Error Tracking Setting Not Found')
end
end
describe "PATCH /projects/:id/error_tracking/settings" do
def make_patch_request(**params)
patch api("/projects/#{project.id}/error_tracking/settings", user), params: params
end
context 'when authenticated as maintainer' do
before do
project.add_maintainer(user)
end
context 'patch settings' do
subject do
make_patch_request(active: false)
end
it_behaves_like 'returns project settings'
it 'returns active is invalid if non boolean' do
make_patch_request(active: "randomstring")
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error'])
.to eq('active is invalid')
end
it 'returns 400 if active is empty' do
make_patch_request(active: '')
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error'])
.to eq('active is empty')
end
end
context 'without a project setting' do
let(:project) { create(:project) }
before do
project.add_maintainer(user)
end
context 'patch settings' do
subject do
make_patch_request(active: true)
end
it_behaves_like 'returns 404'
end
end
end
context 'when authenticated as reporter' do
before do
project.add_reporter(user)
end
it 'returns 403 for update request' do
make_patch_request(active: true)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when authenticated as developer' do
before do
project.add_developer(user)
end
it 'returns 403 for update request' do
make_patch_request(active: true)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when authenticated as non-member' do
it 'returns 404 for update request' do
make_patch_request(active: false)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when unauthenticated' do
let(:user) { nil }
it 'returns 401 for update request' do
make_patch_request(active: true)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
describe "GET /projects/:id/error_tracking/settings" do
def make_request
get api("/projects/#{project.id}/error_tracking/settings", user)
end
......@@ -17,16 +135,12 @@ describe API::ErrorTracking do
project.add_maintainer(user)
end
it 'returns project settings' do
make_request
context 'get settings' do
subject do
make_request
end
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(
'active' => setting.enabled,
'project_name' => setting.project_name,
'sentry_external_url' => setting.sentry_external_url,
'api_url' => setting.api_url
)
it_behaves_like 'returns project settings'
end
end
......@@ -37,12 +151,12 @@ describe API::ErrorTracking do
project.add_maintainer(user)
end
it 'returns 404' do
make_request
context 'get settings' do
subject do
make_request
end
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message'])
.to eq('404 Error Tracking Setting Not Found')
it_behaves_like 'returns 404'
end
end
......@@ -58,6 +172,18 @@ describe API::ErrorTracking do
end
end
context 'when authenticated as developer' do
before do
project.add_developer(user)
end
it 'returns 403' do
make_request
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when authenticated as non-member' do
it 'returns 404' do
make_request
......
......@@ -778,6 +778,32 @@ describe API::Issues do
expect(json_response["error"]).to include("mutually exclusive")
end
end
context 'filtering by non_archived' do
let_it_be(:group1) { create(:group) }
let_it_be(:archived_project) { create(:project, :archived, namespace: group1) }
let_it_be(:active_project) { create(:project, namespace: group1) }
let_it_be(:issue1) { create(:issue, project: active_project) }
let_it_be(:issue2) { create(:issue, project: active_project) }
let_it_be(:issue3) { create(:issue, project: archived_project) }
before do
archived_project.add_developer(user)
active_project.add_developer(user)
end
it 'returns issues from non archived projects only by default' do
get api("/groups/#{group1.id}/issues", user), params: { scope: 'all' }
expect_response_contain_exactly(issue2, issue1)
end
it 'returns issues from archived and non archived projects when non_archived is false' do
get api("/groups/#{group1.id}/issues", user), params: { non_archived: false, scope: 'all' }
expect_response_contain_exactly(issue1, issue2, issue3)
end
end
end
context "when returns issue merge_requests_count for different access levels" do
......@@ -862,4 +888,9 @@ describe API::Issues do
include_examples 'time tracking endpoints', 'issue'
end
def expect_response_contain_exactly(*items)
expect(json_response.length).to eq(items.size)
expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id))
end
end
......@@ -807,6 +807,38 @@ describe API::MergeRequests do
end
end
end
context 'with archived projects' do
let(:project2) { create(:project, :public, :archived, namespace: group) }
let!(:merge_request_archived) { create(:merge_request, title: 'archived mr', author: user, source_project: project2, target_project: project2) }
it 'returns an array excluding merge_requests from archived projects' do
get api(endpoint_path, user)
expect_response_contain_exactly(
merge_request_merged,
merge_request_locked,
merge_request_closed,
merge_request
)
end
context 'with non_archived param set as false' do
it 'returns an array including merge_requests from archived projects' do
path = endpoint_path + '?non_archived=false'
get api(path, user)
expect_response_contain_exactly(
merge_request_merged,
merge_request_locked,
merge_request_closed,
merge_request,
merge_request_archived
)
end
end
end
end
describe "GET /projects/:id/merge_requests/:merge_request_iid" do
......
......@@ -4,7 +4,7 @@ require 'spec_helper'
describe Projects::Operations::UpdateService do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:project, refind: true) { create(:project) }
let(:result) { subject.execute }
......@@ -145,6 +145,48 @@ describe Projects::Operations::UpdateService do
end
end
context 'partial_update' do
let(:params) do
{
error_tracking_setting_attributes: {
enabled: true
}
}
end
context 'with setting' do
before do
create(:project_error_tracking_setting, :disabled, project: project)
end
it 'service succeeds' do
expect(result[:status]).to eq(:success)
end
it 'updates attributes' do
expect { result }
.to change { project.reload.error_tracking_setting.enabled }
.from(false)
.to(true)
end
it 'only updates enabled attribute' do
result
expect(project.error_tracking_setting.previous_changes.keys)
.to contain_exactly('enabled')
end
end
context 'without setting' do
it 'does not create a setting' do
expect(result[:status]).to eq(:error)
expect(project.reload.error_tracking_setting).to be_nil
end
end
end
context 'with masked param token' do
let(:params) do
{
......
# frozen_string_literal: true
module ControllerHelpers
# It seems Devise::Test::ControllerHelpers#sign_in doesn't clear out the @current_user
# variable of the controller, hence it's not overwritten on retries.
# This should be fixed in Devise:
# - https://github.com/heartcombo/devise/issues/5190
# - https://github.com/heartcombo/devise/pull/5191
def sign_in(resource, deprecated = nil, scope: nil)
super
scope ||= Devise::Mapping.find_scope!(resource)
@controller.instance_variable_set(:"@current_#{scope}", nil)
end
end
Devise::Test::ControllerHelpers.prepend(ControllerHelpers)
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