Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-09-26 12:06:00 +00:00
parent 759cd6c298
commit 5707f305f4
54 changed files with 1419 additions and 464 deletions

View file

@ -3,8 +3,6 @@
image: ruby:2.6-alpine
stage: qa
dependencies: []
variables:
GIT_DEPTH: "1"
retry: 0
script:
- source scripts/utils.sh

View file

@ -87,9 +87,9 @@ gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
# GraphQL API
gem 'graphql', '~> 1.9.11'
# NOTE: graphiql-rails v1.5+ doesn't work: https://gitlab.com/gitlab-org/gitlab-ce/issues/67293
# NOTE: graphiql-rails v1.5+ doesn't work: https://gitlab.com/gitlab-org/gitlab/issues/31771
# TODO: remove app/views/graphiql/rails/editors/show.html.erb when https://github.com/rmosolgo/graphiql-rails/pull/71 is released:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/67263
# https://gitlab.com/gitlab-org/gitlab/issues/31747
gem 'graphiql-rails', '~> 1.4.10'
gem 'apollo_upload_server', '~> 2.0.0.beta3'
gem 'graphql-docs', '~> 1.6.0', group: [:development, :test]

View file

@ -328,7 +328,7 @@ Thanks for the issue report. This issue has already been fixed in newer versions
Due to the size of this project and our limited resources we are only able to support the
latest stable release as outlined in our [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html).
In order to get this bug fix and enjoy many new features please
[upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update).
[upgrade](https://gitlab.com/gitlab-org/gitlab/tree/master/doc/update).
If you still experience issues at that time please open a new issue following our issue
tracker guidelines found in the [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines).
```
@ -337,14 +337,14 @@ tracker guidelines found in the [contributing guidelines](https://docs.gitlab.co
```
Thanks for your interest in improving the GitLab codebase!
Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/contributing/merge_request_workflow.md#merge-request-guidelines).
Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/development/contributing/merge_request_workflow.md#merge-request-guidelines).
```
### Accepting merge requests
```
Is there an issue on the
[issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
[issue tracker](https://gitlab.com/gitlab-org/gitlab/issues) that is
similar to this? Could you please link it here?
Please be aware that new functionality that is not marked
[`Accepting merge requests`](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#label-for-community-contributors)

View file

@ -23,6 +23,46 @@ export const parseHeaderLine = (line = {}, lineNumber) => ({
lines: [],
});
/**
* Finds the matching header section
* for the section_duration object and adds it to it
*
* {
* isHeader: true,
* line: {
* content: [],
* lineNumber: 0,
* section_duration: "",
* },
* lines: []
* }
*
* @param Array data
* @param Object durationLine
*/
export function addDurationToHeader(data, durationLine) {
data.forEach(el => {
if (el.line && el.line.section === durationLine.section) {
el.line.section_duration = durationLine.section_duration;
}
});
}
/**
* Check is the current section belongs to a collapsible section
*
* @param Array acc
* @param Object last
* @param Object section
*
* @returns Boolean
*/
export const isCollapsibleSection = (acc = [], last = {}, section = {}) =>
acc.length > 0 &&
last.isHeader === true &&
!section.section_duration &&
section.section === last.line.section;
/**
* Parses the job log content into a structure usable by the template
*
@ -34,26 +74,33 @@ export const parseHeaderLine = (line = {}, lineNumber) => ({
* For each line:
* - adds the index as lineNumber
*
* @param {Array} lines
* @returns {Array}
* @param Array lines
* @param Number lineNumberStart
* @param Array accumulator
* @returns Array parsed log lines
*/
export const logLinesParser = (lines = [], lineNumberStart) =>
export const logLinesParser = (lines = [], lineNumberStart, accumulator = []) =>
lines.reduce((acc, line, index) => {
const lineNumber = lineNumberStart ? lineNumberStart + index : index;
const last = acc[acc.length - 1];
// If the object is an header, we parse it into another structure
if (line.section_header) {
acc.push(parseHeaderLine(line, lineNumber));
} else if (acc.length && last.isHeader && !line.section_duration && line.content.length) {
} else if (isCollapsibleSection(acc, last, line)) {
// if the object belongs to a nested section, we append it to the new `lines` array of the
// previously formated header
last.lines.push(parseLine(line, lineNumber));
} else if (acc.length && last.isHeader && line.section_duration) {
last.section_duration = line.section_duration;
} else if (line.content.length) {
} else if (line.section_duration) {
// if the line has section_duration, we look for the correct header to add it
addDurationToHeader(acc, line);
} else {
// otherwise it's a regular line
acc.push(parseLine(line, lineNumber));
}
return acc;
}, []);
}, accumulator);
/**
* Finds the repeated offset, removes the old one

View file

@ -1,8 +1,7 @@
.card-header {
&:first-child {
// intended use case: card with only a header (for example empty related issues)
&.border-0,
&.border-bottom-0 {
&:last-child {
@include border-radius($card-inner-border-radius);
}
}

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
class Admin::SessionsController < ApplicationController
include InternalRedirect
before_action :user_is_admin!
def new
# Renders a form in which the admin can enter their password
end
def create
if current_user_mode.enable_admin_mode!(password: params[:password])
redirect_location = stored_location_for(:redirect) || admin_root_path
redirect_to safe_redirect_path(redirect_location)
else
flash.now[:alert] = _('Invalid Login or password')
render :new
end
end
def destroy
current_user_mode.disable_admin_mode!
redirect_to root_path, status: :found, notice: _('Admin mode disabled')
end
private
def user_is_admin!
render_404 unless current_user&.admin?
end
end

View file

@ -36,6 +36,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception, prepend: true
helper_method :can?
helper_method :current_user_mode
helper_method :import_sources_enabled?, :github_import_enabled?,
:gitea_import_enabled?, :github_import_configured?,
:gitlab_import_enabled?, :gitlab_import_configured?,
@ -533,6 +534,10 @@ class ApplicationController < ActionController::Base
yield
end
end
def current_user_mode
@current_user_mode ||= Gitlab::Auth::CurrentUserMode.new(current_user)
end
end
ApplicationController.prepend_if_ee('EE::ApplicationController')

View file

@ -14,6 +14,16 @@ module EnforcesAdminAuthentication
end
def authenticate_admin!
render_404 unless current_user.admin?
return render_404 unless current_user.admin?
return unless Feature.enabled?(:user_mode_in_session)
unless current_user_mode.admin_mode?
store_location_for(:redirect, request.fullpath) if storable_location?
redirect_to(new_admin_session_path, notice: _('Re-authentication required'))
end
end
def storable_location?
request.path != new_admin_session_path
end
end

View file

@ -5,6 +5,12 @@
# Controller concern to handle PAT, RSS, and static objects token authentication methods
#
module SessionlessAuthentication
extend ActiveSupport::Concern
included do
before_action :enable_admin_mode!, if: :sessionless_user?
end
# This filter handles personal access tokens, atom requests with rss tokens, and static object tokens
def authenticate_sessionless_user!(request_format)
user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user(request_format)
@ -25,4 +31,8 @@ module SessionlessAuthentication
sign_in(user, store: false, message: :sessionless_sign_in)
end
end
def enable_admin_mode!
current_user_mode.enable_admin_mode!(skip_password_validation: true) if Feature.enabled?(:user_mode_in_session)
end
end

View file

@ -20,9 +20,7 @@ class HealthController < ActionController::Base
end
def liveness
results = CHECKS.map { |check| [check.name, check.liveness] }
render_check_results(results)
render json: { status: 'ok' }, status: :ok
end
private

View file

@ -86,6 +86,12 @@ module NavHelper
links << :admin_impersonation
end
if Feature.enabled?(:user_mode_in_session)
if current_user&.admin? && current_user_mode&.admin_mode?
links << :admin_mode
end
end
links
end
end

View file

@ -5,7 +5,13 @@ require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base
desc "User is an instance admin"
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
condition(:admin) do
if Feature.enabled?(:user_mode_in_session)
Gitlab::Auth::CurrentUserMode.new(@user).admin_mode?
else
@user&.admin?
end
end
desc "User is blocked"
with_options scope: :user, score: 0

View file

@ -32,7 +32,7 @@ module Projects
# This is a hack as the registry doesn't support deleting individual
# tags. This code effectively pushes a dummy image and assigns the tag to it.
# This way when the tag is deleted only the dummy image is affected.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/21405 for a discussion
# See https://gitlab.com/gitlab-org/gitlab/issues/15737 for a discussion
def smart_delete(container_repository, tag_names)
# generates the blobs for the dummy image
dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path)

View file

@ -0,0 +1,7 @@
= form_tag(admin_session_path, method: :post, html: { class: 'new_user gl-show-field-errors', 'aria-live': 'assertive'}) do
.form-group
= label_tag :password, _('Password'), class: 'label-bold'
= password_field_tag :password, nil, class: 'form-control', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' }
.submit-container.move-submit-down
= submit_tag _('Enter admin mode'), class: 'btn btn-success', data: { qa_selector: 'sign_in_button' }

View file

@ -0,0 +1,11 @@
- if form_based_providers.any?
- if password_authentication_enabled_for_web?
.login-box.tab-pane{ id: 'login-pane', role: 'tabpanel' }
.login-body
= render 'admin/sessions/new_base'
- elsif password_authentication_enabled_for_web?
.login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
.login-body
= render 'admin/sessions/new_base'

View file

@ -0,0 +1,3 @@
%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
%a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab', qa_selector: 'sign_in_tab' }, role: 'tab' }= _('Enter admin mode')

View file

@ -0,0 +1,15 @@
- @hide_breadcrumbs = true
- page_title _('Enter admin mode')
.row.justify-content-center
.col-6.new-session-forms-container
.login-page
#signin-container
= render 'admin/sessions/tabs_normal'
.tab-content
- if password_authentication_enabled_for_web?
= render 'admin/sessions/signin_box'
- else
-# Show a message if none of the mechanisms above are enabled
.prepend-top-default.center
= _('No authentication methods configured.')

View file

@ -68,6 +68,15 @@
= nav_link(controller: 'admin/dashboard') do
= link_to admin_root_path, class: 'd-lg-none admin-icon qa-admin-area-link' do
= _('Admin Area')
- if Feature.enabled?(:user_mode_in_session)
- if header_link?(:admin_mode)
= nav_link(controller: 'admin/sessions') do
= link_to destroy_admin_session_path, class: 'd-lg-none lock-open-icon' do
= _('Leave admin mode')
- elsif current_user.admin?
= nav_link(controller: 'admin/sessions') do
= link_to new_admin_session_path, class: 'd-lg-none lock-icon' do
= _('Enter admin mode')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, class: 'd-lg-none admin-icon' do
@ -95,6 +104,17 @@
= nav_link(controller: 'admin/dashboard', html_options: { class: "d-none d-lg-block d-xl-block"}) do
= link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('admin', size: 18)
- if Feature.enabled?(:user_mode_in_session)
- if header_link?(:admin_mode)
= nav_link(controller: 'admin/sessions', html_options: { class: "d-none d-lg-block d-xl-block"}) do
= link_to destroy_admin_session_path, title: _('Leave admin mode'), aria: { label: _('Leave admin mode') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= sprite_icon('lock-open', size: 18)
- elsif current_user.admin?
= nav_link(controller: 'admin/sessions', html_options: { class: "d-none d-lg-block d-xl-block"}) do
= link_to new_admin_session_path, title: _('Enter admin mode'), aria: { label: _('Enter admin mode') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= sprite_icon('lock', size: 18)
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, class: 'admin-icon d-none d-lg-block d-xl-block', title: _('Sherlock Transactions'),

View file

@ -44,7 +44,7 @@ if (isJest) {
plugins.push('@babel/plugin-transform-modules-commonjs');
/*
without the following, babel-plugin-istanbul throws an error:
https://gitlab.com/gitlab-org/gitlab-ce/issues/58390
https://gitlab.com/gitlab-org/gitlab-foss/issues/58390
*/
plugins.push('babel-plugin-dynamic-import-node');
}

View file

@ -0,0 +1,6 @@
---
title: Require admins to enter admin-mode by re-authenticating before performing
administrative operations
merge_request: 16981
author: Roger Rüttimann & Diego Louzán
type: added

View file

@ -0,0 +1,5 @@
---
title: Changes response body of liveness check to be more accurate
merge_request: 17655
author:
type: changed

View file

@ -1,5 +1,5 @@
---
title: Redo fix for related issues border radius
merge_request: 17172
merge_request: 17480
author:
type: fixed

View file

@ -6,7 +6,7 @@
# as a workaround until this is resolved.
#
# This can be removed once fog-google and google-api-client can be upgraded.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/66630 for more details.
# See https://gitlab.com/gitlab-org/gitlab/issues/31280 for more details.
#
require 'google/apis/container_v1beta1'

View file

@ -21,6 +21,10 @@ namespace :admin do
end
end
resource :session, only: [:new, :create] do
get 'destroy', action: :destroy, as: :destroy
end
resource :impersonation, only: :destroy
resources :abuse_reports, only: [:index, :destroy]

View file

@ -125,7 +125,7 @@ CAUTION: **Warning:**
**Extended downtime is required** so no new files are created in object storage during
the migration. A configuration setting will be added soon to allow migrating
from object storage to local files with only a brief moment of downtime for configuration changes.
See issue [gitlab-org/gitlab-ce#66144](https://gitlab.com/gitlab-org/gitlab-ce/issues/66144)
See issue [gitlab-org/gitlab#30979](https://gitlab.com/gitlab-org/gitlab/issues/30979)
### All-in-one rake task

View file

@ -116,28 +116,11 @@ curl 'https://gitlab.example.com/-/liveness'
Example response:
On success, the endpoint will return a valid successful HTTP status code, and a response like below.
On success, the endpoint will return a `200` HTTP status code, and a response like below.
```json
{
"db_check":{
"status": "ok"
},
"redis_check":{
"status":"ok"
},
"cache_check":{
"status":"ok"
},
"queues_check":{
"status":"ok"
},
"shared_state_check":{
"status":"ok"
},
"gitaly_check":{
"status":"ok"
}
}
```

View file

@ -4,7 +4,7 @@ type: reference, howto, concepts
# Subgroups
>[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2772) in GitLab 9.0.
>[Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/2772) in GitLab 9.0.
Subgroups, also known as nested groups or hierarchical groups, allow you to have up to 20
levels of groups.

View file

@ -3,7 +3,7 @@ const IS_EE = require('./config/helpers/is_ee_env');
const reporters = ['default'];
// To have consistent date time parsing both in local and CI environments we set
// the timezone of the Node process. https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27738
// the timezone of the Node process. https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27738
process.env.TZ = 'GMT';
if (process.env.CI) {

View file

@ -17,6 +17,8 @@ module API
request.access_token
end
use AdminModeMiddleware
helpers HelperMethods
install_error_responders(base)
@ -52,6 +54,11 @@ module API
forbidden!(api_access_denied_message(user))
end
# Set admin mode for API requests (if admin)
if Feature.enabled?(:user_mode_in_session)
Gitlab::Auth::CurrentUserMode.new(user).enable_admin_mode!(skip_password_validation: true)
end
user
end
@ -141,5 +148,22 @@ module API
end
end
end
class AdminModeMiddleware < ::Grape::Middleware::Base
def initialize(app, **options)
super
end
def call(env)
if Feature.enabled?(:user_mode_in_session)
session = {}
Gitlab::Session.with_session(session) do
app.call(env)
end
else
app.call(env)
end
end
end
end
end

View file

@ -1052,7 +1052,7 @@ module API
expose :job_events
# Expose serialized properties
expose :properties do |service, options|
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
if service.data_fields_present?
service.data_fields.as_json.slice(*service.api_field_names)
else

View file

@ -0,0 +1,66 @@
# frozen_string_literal: true
module Gitlab
module Auth
# Keeps track of the current session user mode
#
# In order to perform administrative tasks over some interfaces,
# an administrator must have explicitly enabled admin-mode
# e.g. on web access require re-authentication
class CurrentUserMode
SESSION_STORE_KEY = :current_user_mode
ADMIN_MODE_START_TIME_KEY = 'admin_mode'
MAX_ADMIN_MODE_TIME = 6.hours
def initialize(user)
@user = user
end
def admin_mode?
return false unless user
Gitlab::SafeRequestStore.fetch(request_store_key) do
user&.admin? && any_session_with_admin_mode?
end
end
def enable_admin_mode!(password: nil, skip_password_validation: false)
return unless user&.admin?
return unless skip_password_validation || user&.valid_password?(password)
current_session_data[ADMIN_MODE_START_TIME_KEY] = Time.now
end
def disable_admin_mode!
current_session_data[ADMIN_MODE_START_TIME_KEY] = nil
Gitlab::SafeRequestStore.delete(request_store_key)
end
private
attr_reader :user
def request_store_key
@request_store_key ||= { res: :current_user_mode, user: user.id }
end
def current_session_data
@current_session ||= Gitlab::NamespacedSessionStore.new(SESSION_STORE_KEY)
end
def any_session_with_admin_mode?
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|
session[ADMIN_MODE_START_TIME_KEY].to_i > MAX_ADMIN_MODE_TIME.ago.to_i
end
end
def all_sessions
@all_sessions ||= ActiveSession.list_sessions(user).lazy.map do |session|
Gitlab::NamespacedSessionStore.new(SESSION_STORE_KEY, session.with_indifferent_access )
end
end
end
end
end

View file

@ -12,7 +12,7 @@
# We use Workhorse to detect the real extension when we serve files with
# the `SendsBlob` helper methods, and ask Workhorse to set the content
# type when it serves the file:
# https://gitlab.com/gitlab-org/gitlab-ce/blob/33e5955/app/helpers/workhorse_helper.rb#L48.
# https://gitlab.com/gitlab-org/gitlab/blob/33e5955/app/helpers/workhorse_helper.rb#L48.
#
# Because Workhorse has access to the content when it is downloaded, if
# the type/extension doesn't match the real type, we adjust the

View file

@ -15,10 +15,6 @@ module Gitlab
raise NotImplementedError
end
def liveness
HealthChecks::Result.new(true)
end
def metrics
[]
end

View file

@ -187,7 +187,7 @@ module Gitlab
.find_in_batches(batch_size: BATCH_SIZE) do |services|
counts = services.group_by do |service|
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
service_url = service.data_fields&.url || (service.properties && service.properties['url'])
service_url&.include?('.atlassian.net') ? :cloud : :server
end

View file

@ -1031,6 +1031,9 @@ msgstr ""
msgid "Admin Section"
msgstr ""
msgid "Admin mode disabled"
msgstr ""
msgid "Admin notes"
msgstr ""
@ -5734,6 +5737,9 @@ msgstr ""
msgid "Enter a number"
msgstr ""
msgid "Enter admin mode"
msgstr ""
msgid "Enter at least three characters to search"
msgstr ""
@ -9123,6 +9129,9 @@ msgstr ""
msgid "Leave"
msgstr ""
msgid "Leave admin mode"
msgstr ""
msgid "Leave edit mode? All unsaved changes will be lost."
msgstr ""
@ -12739,6 +12748,9 @@ msgstr ""
msgid "Raw blob request rate limit per minute"
msgstr ""
msgid "Re-authentication required"
msgstr ""
msgid "Read more"
msgstr ""

View file

@ -8,7 +8,7 @@
# Only some crawlers respect this setting, e.g. Googlebot does not
# Crawl-delay: 1
# Based on details in https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/routes.rb, https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/routing, and using application
# Based on details in https://gitlab.com/gitlab-org/gitlab/blob/master/config/routes.rb, https://gitlab.com/gitlab-org/gitlab/blob/master/spec/routing, and using application
User-Agent: *
Disallow: /autocomplete/users
Disallow: /search

View file

@ -0,0 +1,98 @@
# frozen_string_literal: true
require 'spec_helper'
describe Admin::SessionsController, :do_not_mock_admin_mode do
include_context 'custom session'
let(:user) { create(:user) }
before do
sign_in(user)
end
describe '#new' do
context 'for regular users' do
it 'shows error page' do
get :new
expect(response).to have_gitlab_http_status(:not_found)
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
end
end
context 'for admin users' do
let(:user) { create(:admin) }
it 'renders a password form' do
get :new
expect(response).to render_template :new
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
end
end
end
describe '#create' do
context 'for regular users' do
it 'shows error page' do
post :create
expect(response).to have_gitlab_http_status(:not_found)
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
end
end
context 'for admin users' do
let(:user) { create(:admin) }
it 'sets admin mode with a valid password' do
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
controller.store_location_for(:redirect, admin_root_path)
post :create, params: { password: user.password }
expect(response).to redirect_to admin_root_path
expect(controller.send(:current_user_mode).admin_mode?).to be(true)
end
it 'fails with an invalid password' do
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
controller.store_location_for(:redirect, admin_root_path)
post :create, params: { password: '' }
expect(response).to render_template :new
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
end
end
end
describe '#destroy' do
context 'for regular users' do
it 'shows error page' do
get :destroy
expect(response).to have_gitlab_http_status(404)
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
end
end
context 'for admin users' do
let(:user) { create(:admin) }
it 'disables admin mode and redirects to main page' do
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
post :create, params: { password: user.password }
expect(controller.send(:current_user_mode).admin_mode?).to be(true)
get :destroy
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(root_path)
expect(controller.send(:current_user_mode).admin_mode?).to be(false)
end
end
end
end

View file

@ -777,4 +777,48 @@ describe ApplicationController do
end
end
end
describe '#current_user_mode', :do_not_mock_admin_mode do
include_context 'custom session'
controller(described_class) do
def index
render html: 'authenticated'
end
end
before do
allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session])
sign_in(user)
get :index
end
context 'with a regular user' do
it 'admin mode is not set' do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Auth::CurrentUserMode.new(user).admin_mode?).to be(false)
end
end
context 'with an admin user' do
let(:user) { create(:admin) }
it 'admin mode is not set' do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Auth::CurrentUserMode.new(user).admin_mode?).to be(false)
end
context 'that re-authenticated' do
before do
Gitlab::Auth::CurrentUserMode.new(user).enable_admin_mode!(password: user.password)
end
it 'admin mode is set' do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Auth::CurrentUserMode.new(user).admin_mode?).to be(true)
end
end
end
end
end

View file

@ -2,7 +2,9 @@
require 'spec_helper'
describe EnforcesAdminAuthentication do
describe EnforcesAdminAuthentication, :do_not_mock_admin_mode do
include AdminModeHelper
let(:user) { create(:user) }
before do
@ -10,24 +12,37 @@ describe EnforcesAdminAuthentication do
end
controller(ApplicationController) do
# `described_class` is not available in this context
include EnforcesAdminAuthentication # rubocop:disable RSpec/DescribedClass
include EnforcesAdminAuthentication
def index
head :ok
end
end
context 'feature flag :user_mode_in_session is enabled' do
describe 'authenticate_admin!' do
context 'as an admin' do
let(:user) { create(:admin) }
it 'renders redirect for re-authentication and does not set admin mode' do
get :index
expect(response).to redirect_to new_admin_session_path
expect(assigns(:current_user_mode)&.admin_mode?).to be(false)
end
context 'when admin mode is active' do
before do
enable_admin_mode!(user)
end
it 'renders ok' do
get :index
expect(response).to have_gitlab_http_status(200)
end
end
end
context 'as a user' do
it 'renders a 404' do
@ -35,6 +50,49 @@ describe EnforcesAdminAuthentication do
expect(response).to have_gitlab_http_status(404)
end
it 'does not set admin mode' do
get :index
# check for nil too since on 404, current_user_mode might not be initialized
expect(assigns(:current_user_mode)&.admin_mode?).to be_falsey
end
end
end
end
context 'feature flag :user_mode_in_session is disabled' do
before do
stub_feature_flags(user_mode_in_session: false)
end
describe 'authenticate_admin!' do
before do
get :index
end
context 'as an admin' do
let(:user) { create(:admin) }
it 'allows direct access to page' do
expect(response).to have_gitlab_http_status(200)
end
it 'does not set admin mode' do
expect(assigns(:current_user_mode)&.admin_mode?).to be_falsey
end
end
context 'as a user' do
it 'renders a 404' do
expect(response).to have_gitlab_http_status(404)
end
it 'does not set admin mode' do
# check for nil too since on 404, current_user_mode might not be initialized
expect(assigns(:current_user_mode)&.admin_mode?).to be_falsey
end
end
end
end
end

View file

@ -76,10 +76,7 @@ describe HealthController do
it 'responds with liveness checks data' do
subject
expect(json_response['db_check']['status']).to eq('ok')
expect(json_response['cache_check']['status']).to eq('ok')
expect(json_response['queues_check']['status']).to eq('ok')
expect(json_response['shared_state_check']['status']).to eq('ok')
expect(json_response['status']).to eq('ok')
end
end

View file

@ -117,7 +117,7 @@ FactoryBot.define do
end
# this is for testing storing values inside properties, which is deprecated and will be removed in
# https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
# https://gitlab.com/gitlab-org/gitlab/issues/29404
trait :without_properties_callback do
jira_tracker_data nil
issue_tracker_data nil

View file

@ -2,15 +2,17 @@
require 'spec_helper'
describe 'Admin updates settings' do
describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do
include StubENV
include TermsHelper
let(:admin) { create(:admin) }
context 'feature flag :user_mode_in_session is enabled' do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
end
context 'General page' do
@ -450,6 +452,83 @@ describe 'Admin updates settings' do
end
end
context 'when in admin_mode' do
it 'contains link to leave admin mode' do
page.within('.navbar-sub-nav') do
expect(page).to have_link(href: destroy_admin_session_path)
end
end
it 'can leave admin mode' do
page.within('.navbar-sub-nav') do
# Select first, link is also included in mobile view list
click_on 'Leave admin mode', match: :first
expect(page).to have_link(href: new_admin_session_path)
end
end
it 'can open pages not in admin scope' do
page.within('.navbar-sub-nav') do
find_all('a', text: 'Projects').first.click
expect(page).to have_current_path(dashboard_projects_path)
end
end
end
context 'when not in admin mode' do
before do
page.within('.navbar-sub-nav') do
# Select first, link is also included in mobile view list
click_on 'Leave admin mode', match: :first
end
end
it 'has no leave admin mode button' do
page.within('.navbar-sub-nav') do
expect(page).not_to have_link(href: destroy_admin_session_path)
end
end
it 'is necessary to provide credentials again before opening admin settings' do
visit admin_application_settings_path # admin logged out because not in admin_mode
expect(page).to have_current_path(new_admin_session_path)
end
it 'can open pages not in admin scope' do
page.within('.navbar-sub-nav') do
find_all('a', text: 'Projects').first.click
end
expect(page).to have_current_path(dashboard_projects_path)
end
end
end
context 'feature flag :user_mode_in_session is disabled' do
before do
stub_feature_flags(user_mode_in_session: false)
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
visit admin_application_settings_path
end
it 'loads admin settings page without redirect for reauthentication' do
expect(current_path).to eq admin_application_settings_path
end
it 'shows no admin mode buttons in navbar' do
page.within('.navbar-sub-nav') do
expect(page).not_to have_link(href: new_admin_session_path)
expect(page).not_to have_link(href: destroy_admin_session_path)
end
end
end
def check_all_events
page.check('Active')
page.check('Push')

View file

@ -3,6 +3,8 @@ import {
updateIncrementalTrace,
parseHeaderLine,
parseLine,
addDurationToHeader,
isCollapsibleSection,
findOffsetAndRemove,
} from '~/jobs/store/utils';
import {
@ -43,6 +45,127 @@ describe('Jobs Store Utils', () => {
});
});
describe('addDurationToHeader', () => {
const duration = {
offset: 106,
content: [],
section: 'prepare-script',
section_duration: '00:03',
};
it('adds the section duration to the correct header', () => {
const parsed = [
{
isClosed: true,
isHeader: true,
line: {
section: 'prepare-script',
content: [{ text: 'foo' }],
},
lines: [],
},
{
isClosed: true,
isHeader: true,
line: {
section: 'foo-bar',
content: [{ text: 'foo' }],
},
lines: [],
},
];
addDurationToHeader(parsed, duration);
expect(parsed[0].line.section_duration).toEqual(duration.section_duration);
expect(parsed[1].line.section_duration).toEqual(undefined);
});
it('does not add the section duration when the headers do not match', () => {
const parsed = [
{
isClosed: true,
isHeader: true,
line: {
section: 'bar-foo',
content: [{ text: 'foo' }],
},
lines: [],
},
{
isClosed: true,
isHeader: true,
line: {
section: 'foo-bar',
content: [{ text: 'foo' }],
},
lines: [],
},
];
addDurationToHeader(parsed, duration);
expect(parsed[0].line.section_duration).toEqual(undefined);
expect(parsed[1].line.section_duration).toEqual(undefined);
});
it('does not add when content has no headers', () => {
const parsed = [
{
section: 'bar-foo',
content: [{ text: 'foo' }],
lineNumber: 1,
},
{
section: 'foo-bar',
content: [{ text: 'foo' }],
lineNumber: 2,
},
];
addDurationToHeader(parsed, duration);
expect(parsed[0].line).toEqual(undefined);
expect(parsed[1].line).toEqual(undefined);
});
});
describe('isCollapsibleSection', () => {
const header = {
isHeader: true,
line: {
section: 'foo',
},
};
const line = {
lineNumber: 1,
section: 'foo',
content: [],
};
it('returns true when line belongs to the last section', () => {
expect(isCollapsibleSection([header], header, { section: 'foo', content: [] })).toEqual(true);
});
it('returns false when last line was not an header', () => {
expect(isCollapsibleSection([line], line, { section: 'bar' })).toEqual(false);
});
it('returns false when accumulator is empty', () => {
expect(isCollapsibleSection([], { isHeader: true }, { section: 'bar' })).toEqual(false);
});
it('returns false when section_duration is defined', () => {
expect(isCollapsibleSection([header], header, { section_duration: '10:00' })).toEqual(false);
});
it('returns false when `section` is not a match', () => {
expect(isCollapsibleSection([header], header, { section: 'bar' })).toEqual(false);
});
it('returns false when no parameters are provided', () => {
expect(isCollapsibleSection()).toEqual(false);
});
});
describe('logLinesParser', () => {
let result;
@ -75,7 +198,7 @@ describe('Jobs Store Utils', () => {
describe('section duration', () => {
it('adds the section information to the header section', () => {
expect(result[1].section_duration).toEqual(utilsMockData[4].section_duration);
expect(result[1].line.section_duration).toEqual(utilsMockData[4].section_duration);
});
it('does not add section duration as a line', () => {

View file

@ -89,7 +89,7 @@ export const release = {
id: 2,
name: 'my second link',
url:
'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
external: false,
},
],

View file

@ -1,16 +1,20 @@
require 'spec_helper'
describe NavHelper do
describe NavHelper, :do_not_mock_admin_mode do
describe '#header_links' do
include_context 'custom session'
before do
allow(helper).to receive(:session) { {} }
allow(helper).to receive(:session).and_return(session)
end
context 'when the user is logged in' do
let(:user) { build(:user) }
let(:user) { create(:user) }
let(:current_user_mode) { Gitlab::Auth::CurrentUserMode.new(user) }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:current_user_mode).and_return(current_user_mode)
allow(helper).to receive(:can?) { true }
end
@ -26,6 +30,46 @@ describe NavHelper do
expect(helper.header_links).to include(:admin_impersonation)
end
context 'as admin' do
let(:user) { create(:user, :admin) }
context 'feature flag :user_mode_in_session is enabled' do
it 'does not contain the admin mode link by default' do
expect(helper.header_links).not_to include(:admin_mode)
end
context 'with admin mode enabled' do
before do
current_user_mode.enable_admin_mode!(password: user.password)
end
it 'contains the admin mode link' do
expect(helper.header_links).to include(:admin_mode)
end
end
end
context 'feature flag :user_mode_in_session is disabled' do
before do
stub_feature_flags(user_mode_in_session: false)
end
it 'does not contain the admin mode link' do
expect(helper.header_links).not_to include(:admin_mode)
end
context 'with admin mode enabled' do
before do
current_user_mode.enable_admin_mode!(password: user.password)
end
it 'has no effect on header links' do
expect(helper.header_links).not_to include(:admin_mode)
end
end
end
end
context 'when the user cannot read cross project' do
before do
allow(helper).to receive(:can?).with(user, :read_cross_project) { false }

View file

@ -0,0 +1,159 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do
include_context 'custom session'
let(:user) { build(:user) }
subject { described_class.new(user) }
before 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
it 'cannot be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(password: nil)
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with empty params' do
subject.enable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
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
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
end
end
context 'when the user is an admin' do
let(:user) { build(:user, :admin) }
it 'is false by default' do
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with an invalid password' do
subject.enable_admin_mode!(password: nil)
expect(subject.admin_mode?).to be(false)
end
it 'can be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(true)
end
it 'can be disabled' do
subject.enable_admin_mode!(password: user.password)
subject.disable_admin_mode!
expect(subject.admin_mode?).to be(false)
end
it 'will expire in the future' do
subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present'
Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do
# in the future this will be a new request, simulate by clearing the RequestStore
Gitlab::SafeRequestStore.clear!
expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future'
end
end
context 'skipping password validation' do
it 'can be enabled with a valid password' do
subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
expect(subject.admin_mode?).to be(true)
end
it 'can be enabled with an invalid password' do
subject.enable_admin_mode!(skip_password_validation: true)
expect(subject.admin_mode?).to be(true)
end
end
context 'with two independent sessions' do
let(:another_session) { {} }
let(:another_subject) { described_class.new(user) }
before do
allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session])
end
it 'can be enabled in one and seen in the other' do
Gitlab::Session.with_session(another_session) do
another_subject.enable_admin_mode!(password: user.password)
end
expect(subject.admin_mode?).to be(true)
end
end
end
end
describe '#enable_admin_mode!' do
let(:user) { build(:user, :admin) }
it 'creates a timestamp in the session' do
subject.enable_admin_mode!(password: user.password)
expect(session).to include(expected_session_entry(be_within(1.second).of Time.now))
end
end
describe '#disable_admin_mode!' do
let(:user) { build(:user, :admin) }
it 'sets the session timestamp to nil' do
subject.disable_admin_mode!
expect(session).to include(expected_session_entry(be_nil))
end
end
def expected_session_entry(value_matcher)
{
Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including(
Gitlab::Auth::CurrentUserMode::ADMIN_MODE_START_TIME_KEY => value_matcher)
}
end
end

View file

@ -58,9 +58,4 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to have_attributes(success: false, message: "#{described_class.human_name} check timed out") }
end
end
describe '#liveness' do
subject { described_class.readiness }
it { is_expected.to eq(Gitlab::HealthChecks::Result.new(true)) }
end
end

View file

@ -343,6 +343,8 @@ describe API::Helpers do
end
context 'sudo' do
include_context 'custom session'
shared_examples 'successful sudo' do
it 'sets current_user' do
expect(current_user).to eq(user)

View file

@ -3,9 +3,14 @@ require 'spec_helper'
describe BuildActionEntity do
let(:job) { create(:ci_build, name: 'test_job') }
let(:request) { double('request') }
let(:user) { create(:user) }
let(:entity) do
described_class.new(job, request: spy('request'))
described_class.new(job, request: request)
end
before do
allow(request).to receive(:current_user).and_return(user)
end
describe '#as_json' do

View file

@ -160,6 +160,25 @@ RSpec.configure do |config|
allow(Gitlab::Git::KeepAround).to receive(:execute)
Gitlab::ThreadMemoryCache.cache_backend.clear
# Temporary patch to force admin mode to be active by default in tests when
# using the feature flag :user_mode_in_session, since this will require
# modifying a significant number of specs to test both states for admin
# mode enabled / disabled.
#
# See https://gitlab.com/gitlab-org/gitlab/issues/31511
# See gitlab/spec/support/helpers/admin_mode_helpers.rb
#
# If it is required to have the real behaviour that an admin is signed in
# with normal user mode and needs to switch to admin mode, it is possible to
# mark such tests with the `do_not_mock_admin_mode` metadata tag, e.g:
#
# context 'some test with normal user mode', :do_not_mock_admin_mode do ... end
unless example.metadata[:do_not_mock_admin_mode]
allow_any_instance_of(Gitlab::Auth::CurrentUserMode).to receive(:admin_mode?) do |current_user_mode|
current_user_mode.send(:user)&.admin?
end
end
end
config.around(:example, :quarantine) do |example|

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
# Helper for enabling admin mode in tests
module AdminModeHelper
# Users are logged in by default in user mode and have to switch to admin
# mode for accessing any administrative functionality. This helper lets a user
# be in admin mode without requiring a second authentication step (provided
# the user is an admin)
def enable_admin_mode!(user)
fake_user_mode = instance_double(Gitlab::Auth::CurrentUserMode)
allow(Gitlab::Auth::CurrentUserMode).to receive(:new).with(user).and_return(fake_user_mode)
allow(fake_user_mode).to receive(:admin_mode?).and_return(user&.admin?)
end
end

View file

@ -48,6 +48,14 @@ module LoginHelpers
@current_user = user
end
def gitlab_enable_admin_mode_sign_in(user)
visit new_admin_session_path
fill_in 'password', with: user.password
click_button 'Enter admin mode'
end
def gitlab_sign_in_via(provider, user, uid, saml_response = nil)
mock_auth_hash_with_saml_xml(provider, uid, user.email, saml_response)
visit new_user_session_path

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
# the session is empty by default; you can overwrite it by defining your own
# let(:session) variable
# we do not use a parameter such as |session| because it does not play nice
# with let variables
shared_context 'custom session' do
let!(:session) { {} }
around do |example|
Gitlab::Session.with_session(session) do
example.run
end
end
end

View file

@ -0,0 +1,29 @@
require 'spec_helper'
describe 'admin/sessions/new.html.haml' do
context 'admin has password set' do
before do
allow(view).to receive(:password_authentication_enabled_for_web?).and_return(true)
end
it "shows enter password form" do
render
expect(rendered).to have_css('#login-pane.active')
expect(rendered).to have_selector('input[name="password"]')
end
end
context 'admin has no password set' do
before do
allow(view).to receive(:password_authentication_enabled_for_web?).and_return(false)
end
it "warns authentication not possible" do
render
expect(rendered).not_to have_css('#login-pane')
expect(rendered).to have_content 'No authentication methods configured'
end
end
end