Commit bcdcff74 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 5277f8e6
......@@ -249,7 +249,7 @@
}
.filtered-search-input-dropdown-menu {
max-height: $dropdown-max-height;
max-height: $dropdown-max-height-lg;
max-width: 280px;
overflow: auto;
......
......@@ -23,6 +23,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
push_frontend_feature_flag(:release_search_filter, @project)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
......
......@@ -1454,7 +1454,7 @@ class User < ApplicationRecord
# Does the user have access to all private groups & projects?
# Overridden in EE to also check auditor?
def full_private_access?
admin?
can?(:read_all_resources)
end
def update_two_factor_requirement
......
......@@ -21,10 +21,6 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:deactivated) { @user&.deactivated? }
desc "User has access to all private groups & projects"
with_options scope: :user, score: 0
condition(:full_private_access) { @user&.full_private_access? }
with_options scope: :user, score: 0
condition(:external_user) { @user.nil? || @user.external? }
......@@ -40,10 +36,12 @@ class BasePolicy < DeclarativePolicy::Base
::Gitlab::ExternalAuthorization.perform_check?
end
rule { external_authorization_enabled & ~full_private_access }.policy do
rule { external_authorization_enabled & ~can?(:read_all_resources) }.policy do
prevent :read_cross_project
end
rule { admin }.enable :read_all_resources
rule { default }.enable :read_cross_project
end
......
......@@ -30,5 +30,5 @@ class PersonalSnippetPolicy < BasePolicy
rule { can?(:create_note) }.enable :award_emoji
rule { full_private_access }.enable :read_personal_snippet
rule { can?(:read_all_resources) }.enable :read_personal_snippet
end
......@@ -28,7 +28,7 @@ class ProjectSnippetPolicy < BasePolicy
all?(private_snippet | (internal_snippet & external_user),
~project.guest,
~is_author,
~full_private_access)
~can?(:read_all_resources))
end.prevent :read_project_snippet
rule { internal_snippet & ~is_author & ~admin }.policy do
......
---
title: Hide projects without access to admin user when admin mode is disabled
merge_request: 18530
author: Diego Louzán
type: changed
---
title: Add "release" filter to merge request search page
merge_request: 19315
author:
type: added
# frozen_string_literal: true
class ReplaceIndexOnMetricsMergedAt < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :merge_request_metrics, :merged_at
remove_concurrent_index :merge_request_metrics, [:merged_at, :id]
end
def down
add_concurrent_index :merge_request_metrics, [:merged_at, :id]
remove_concurrent_index :merge_request_metrics, :merged_at
end
end
......@@ -6,7 +6,7 @@ class ScheduleProductivityAnalyticsBackfill < ActiveRecord::Migration[5.2]
DOWNTIME = false
def up
# no-op since the scheduling times out on GitLab.com
# no-op since the migration was removed
end
def down
......
......@@ -2290,7 +2290,7 @@ ActiveRecord::Schema.define(version: 2019_11_05_094625) do
t.index ["latest_closed_by_id"], name: "index_merge_request_metrics_on_latest_closed_by_id"
t.index ["merge_request_id", "merged_at"], name: "index_merge_request_metrics_on_merge_request_id_and_merged_at", where: "(merged_at IS NOT NULL)"
t.index ["merge_request_id"], name: "index_merge_request_metrics"
t.index ["merged_at", "id"], name: "index_merge_request_metrics_on_merged_at_and_id"
t.index ["merged_at"], name: "index_merge_request_metrics_on_merged_at"
t.index ["merged_by_id"], name: "index_merge_request_metrics_on_merged_by_id"
t.index ["pipeline_id"], name: "index_merge_request_metrics_on_pipeline_id"
end
......
......@@ -89,6 +89,10 @@ your GitLab installation has three repository storages: `default`,
`storage1` and `storage2`. You can use as little as just one server with one
repository storage if desired.
Note: **Note:** The token referred to throughout the Gitaly documentation is
just an arbitrary password selected by the administrator. It is unrelated to
tokens created for the GitLab API or other similar web API tokens.
### 1. Installation
First install Gitaly on each Gitaly server using either
......
......@@ -245,47 +245,29 @@ end
#### Use self-descriptive wrapper methods
When it's not possible/logical to modify the implementation of a
method. Wrap it in a self-descriptive method and use that method.
When it's not possible/logical to modify the implementation of a method, then
wrap it in a self-descriptive method and use that method.
For example, in CE only an `admin` is allowed to access all private
projects/groups, but in EE also an `auditor` has full private
access. It would be incorrect to override the implementation of
`User#admin?`, so instead add a method `full_private_access?` to
`app/models/users.rb`. The implementation in CE will be:
For example, in GitLab-FOSS, the only user created by the system is `User.ghost`
but in EE there are several types of bot-users that aren't really users. It would
be incorrect to override the implementation of `User#ghost?`, so instead we add
a method `#internal?` to `app/models/user.rb`. The implementation will be:
```ruby
def full_private_access?
admin?
def internal?
ghost?
end
```
In EE, the implementation `ee/app/models/ee/users.rb` would be:
```ruby
override :full_private_access?
def full_private_access?
super || auditor?
override :internal?
def internal?
super || bot?
end
```
In `lib/gitlab/visibility_level.rb` this method is used to return the
allowed visibility levels:
```ruby
def levels_for_user(user = nil)
if user.full_private_access?
[PRIVATE, INTERNAL, PUBLIC]
elsif # ...
end
```
See [CE MR][ce-mr-full-private] and [EE MR][ee-mr-full-private] for
full implementation details.
[ce-mr-full-private]: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/12373
[ee-mr-full-private]: https://gitlab.com/gitlab-org/gitlab/merge_requests/2199
### Code in `config/routes`
When we add `draw :admin` in `config/routes.rb`, the application will try to
......
......@@ -184,3 +184,83 @@ class Commit
request_cache(:author) { author_email }
end
```
## `ReactiveCaching`
The `ReactiveCaching` concern is used to fetch some data in the background and
store it in the Rails cache, keeping it up-to-date for as long as it is being
requested. If the data hasn't been requested for `reactive_cache_lifetime`,
it will stop being refreshed, and then be removed.
Example of use:
```ruby
class Foo < ApplicationRecord
include ReactiveCaching
after_save :clear_reactive_cache!
def calculate_reactive_cache
# Expensive operation here. The return value of this method is cached
end
def result
with_reactive_cache do |data|
# ...
end
end
end
```
In this example, the first time `#result` is called, it will return `nil`.
However, it will enqueue a background worker to call `#calculate_reactive_cache`
and set an initial cache lifetime of ten minutes.
The background worker needs to find or generate the object on which
`with_reactive_cache` was called.
The default behaviour can be overridden by defining a custom
`reactive_cache_worker_finder`.
Otherwise, the background worker will use the class name and primary key to get
the object using the ActiveRecord `find_by` method.
```ruby
class Bar
include ReactiveCaching
self.reactive_cache_key = ->() { ["bar", "thing"] }
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
def self.from_cache(var1, var2)
# This method will be called by the background worker with "bar1" and
# "bar2" as arguments.
new(var1, var2)
end
def initialize(var1, var2)
# ...
end
def calculate_reactive_cache
# Expensive operation here. The return value of this method is cached
end
def result
with_reactive_cache("bar1", "bar2") do |data|
# ...
end
end
end
```
Each time the background job completes, it stores the return value of
`#calculate_reactive_cache`. It is also re-enqueued to run again after
`reactive_cache_refresh_interval`, therefore, it will keep the stored value up to date.
Calculations are never run concurrently.
Calling `#result` while a value is cached will call the block given to
`#with_reactive_cache`, yielding the cached value. It will also extend the
lifetime by the `reactive_cache_lifetime` value.
Once the lifetime has expired, no more background jobs will be enqueued and
calling `#result` will again return `nil` - starting the process all over
again.
......@@ -176,7 +176,7 @@ Here's a list of what you can't do with subgroups:
- [GitLab Pages](../../project/pages/index.md) supports projects hosted under
a subgroup, but not subgroup websites.
That means that only the highest-level group supports
[group websites](../../project/pages/getting_started_part_one.md#gitlab-pages-domain-names),
[group websites](../../project/pages/getting_started_part_one.md#gitlab-pages-default-domain-names),
although you can have project websites under a subgroup.
- It is not possible to share a project with a group that's an ancestor of
the group the project is in. That means you can only share as you walk down
......
......@@ -37,12 +37,12 @@ Example request:
```sh
curl --request POST \
--data '{"title": "Incident title"}' \
--header "Authorization: Bearer <autorization_key>" \
--header "Authorization: Bearer <authorization_key>" \
--header "Content-Type: application/json" \
<url>
```
The `<autorization_key>` and `<url>` values can be found when [setting up generic alerts](#setting-up-generic-alerts).
The `<authorization_key>` and `<url>` values can be found when [setting up generic alerts](#setting-up-generic-alerts).
Example payload:
......
......@@ -14,7 +14,7 @@ To do so, follow the steps below.
1. From your **Project**'s **[Dashboard](https://gitlab.com/dashboard/projects)**,
click **New project**, and name it according to the
[Pages domain names](../getting_started_part_one.md#gitlab-pages-domain-names).
[Pages domain names](../getting_started_part_one.md#gitlab-pages-default-domain-names).
1. Clone it to your local computer, add your website
files to your project, add, commit and push to GitLab.
Alternativelly, you can run `git init` in your local directory,
......
......@@ -8,7 +8,7 @@ type: concepts, reference
On this document, learn how to name your project for GitLab Pages
according to your intended website's URL.
## GitLab Pages domain names
## GitLab Pages default domain names
>**Note:**
If you use your own GitLab instance to deploy your
......@@ -80,7 +80,7 @@ To understand Pages domains clearly, read the examples below.
- On your GitLab instance, replace `gitlab.io` above with your
Pages server domain. Ask your sysadmin for this information.
## URLs and Baseurls
## URLs and baseurls
Every Static Site Generator (SSG) default configuration expects
to find your website under a (sub)domain (`example.com`), not
......@@ -108,7 +108,7 @@ example we've just mentioned, you'd have to change Jekyll's `_config.yml` to:
baseurl: ""
```
## Custom Domains
## Custom domains
GitLab Pages supports custom domains and subdomains, served under HTTP or HTTPS.
See [GitLab Pages custom domains and SSL/TLS Certificates](custom_domains_ssl_tls_certification/index.md) for more information.
......@@ -83,7 +83,7 @@ to build your site and publish it to the GitLab Pages server. The sequence of
scripts that GitLab CI/CD runs to accomplish this task is created from a file named
`.gitlab-ci.yml`, which you can [create and modify](getting_started_part_four.md) at will. A specific `job` called `pages` in the configuration file will make GitLab aware that you are deploying a GitLab Pages website.
You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-domain-names),
You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-default-domain-names),
`*.gitlab.io`, or your own domain (`example.com`). In that case, you'll
need admin access to your domain's registrar (or control panel) to set it up with Pages.
......
......@@ -8,10 +8,15 @@
# https://coderwall.com/p/lkcaag/pagination-you-re-probably-doing-it-wrong
#
# It currently supports sorting on two columns, but the last column must
# be the primary key. For example
# be the primary key. If it's not already included, an order on the
# primary key will be added automatically, like `order(id: :desc)`
#
# Issue.order(created_at: :asc).order(:id)
# Issue.order(due_date: :asc).order(:id)
# Issue.order(due_date: :asc)
#
# You can also use `Gitlab::Database.nulls_last_order`:
#
# Issue.reorder(::Gitlab::Database.nulls_last_order('due_date', 'DESC'))
#
# It will tolerate non-attribute ordering, but only attributes determine the cursor.
# For example, this is legitimate:
......
......@@ -5,12 +5,15 @@ module Gitlab
module Connections
module Keyset
class OrderInfo
attr_reader :attribute_name, :sort_direction
def initialize(order_value)
@order_value = order_value
if order_value.is_a?(String)
@attribute_name, @sort_direction = extract_nulls_last_order(order_value)
else
@attribute_name = order_value.expr.name
@sort_direction = order_value.direction
end
def attribute_name
order_value.expr.name
end
def operator_for(before_or_after)
......@@ -22,10 +25,10 @@ module Gitlab
end
end
# Only allow specific node types. For example ignore String nodes
# Only allow specific node types
def self.build_order_list(relation)
order_list = relation.order_values.select do |value|
value.is_a?(Arel::Nodes::Ascending) || value.is_a?(Arel::Nodes::Descending)
supported_order_value?(value)
end
order_list.map { |info| OrderInfo.new(info) }
......@@ -52,12 +55,21 @@ module Gitlab
end
end
def self.supported_order_value?(order_value)
return true if order_value.is_a?(Arel::Nodes::Ascending) || order_value.is_a?(Arel::Nodes::Descending)
return false unless order_value.is_a?(String)
tokens = order_value.downcase.split
tokens.last(2) == %w(nulls last) && tokens.count == 4
end
private
attr_reader :order_value
def extract_nulls_last_order(order_value)
tokens = order_value.downcase.split
def sort_direction
order_value.direction
[tokens.first, (tokens[1] == 'asc' ? :asc : :desc)]
end
end
end
......
......@@ -6271,6 +6271,12 @@ msgstr ""
msgid "Environment:"
msgstr ""
msgid "EnvironmentDashboard|API"
msgstr ""
msgid "EnvironmentDashboard|Created through the Deployment API"
msgstr ""
msgid "Environments"
msgstr ""
......
......@@ -2,7 +2,9 @@
require 'spec_helper'
describe ProjectsFinder do
describe ProjectsFinder, :do_not_mock_admin_mode do
include AdminModeHelper
describe '#execute' do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
......@@ -188,5 +190,21 @@ describe ProjectsFinder do
it { is_expected.to eq([internal_project, public_project]) }
end
describe 'with admin user' do
let(:user) { create(:admin) }
context 'admin mode enabled' do
before do
enable_admin_mode!(current_user)
end
it { is_expected.to match_array([public_project, internal_project, private_project, shared_project]) }
end
context 'admin mode disabled' do
it { is_expected.to match_array([public_project, internal_project]) }
end
end
end
end
......@@ -17,6 +17,26 @@ describe Gitlab::Graphql::Connections::Keyset::OrderInfo do
expect(order_list.last.operator_for(:after)).to eq '>'
end
end
context 'when order contains NULLS LAST' do
let(:relation) { Project.order(Arel.sql('projects.updated_at Asc Nulls Last')).order(:id) }
it 'does not ignore the SQL order' do
expect(order_list.count).to eq 2
expect(order_list.first.attribute_name).to eq 'projects.updated_at'
expect(order_list.first.operator_for(:after)).to eq '>'
expect(order_list.last.attribute_name).to eq 'id'
expect(order_list.last.operator_for(:after)).to eq '>'
end
end
context 'when order contains invalid formatted NULLS LAST ' do
let(:relation) { Project.order(Arel.sql('projects.updated_at created_at Asc Nulls Last')).order(:id) }
it 'ignores the SQL order' do
expect(order_list.count).to eq 1
end
end
end
describe '#validate_ordering' do
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe User do
describe User, :do_not_mock_admin_mode do
include ProjectForksHelper
include TermsHelper
......@@ -2797,12 +2797,28 @@ describe User do
expect(user.full_private_access?).to be_falsy
end
it 'returns true for admin user' do
user = build(:user, :admin)
context 'for admin user' do
include_context 'custom session'
let(:user) { build(:user, :admin) }
context 'when admin mode is disabled' do
it 'returns false' do
expect(user.full_private_access?).to be_falsy
end
end
context 'when admin mode is enabled' do
before do
Gitlab::Auth::CurrentUserMode.new(user).enable_admin_mode!(password: user.password)
end
it 'returns true' do
expect(user.full_private_access?).to be_truthy
end
end
end
end
describe '.ghost' do
it "creates a ghost user if one isn't already present" do
......
......@@ -2,8 +2,9 @@
require 'spec_helper'
describe BasePolicy do
describe BasePolicy, :do_not_mock_admin_mode do
include ExternalAuthorizationServiceHelpers
include AdminModeHelper
describe '.class_for' do
it 'detects policy class based on the subject ancestors' do
......@@ -36,8 +37,42 @@ describe BasePolicy do
it { is_expected.not_to be_allowed(:read_cross_project) }
it 'allows admins' do
expect(described_class.new(build(:admin), nil)).to be_allowed(:read_cross_project)
context 'for admins' do
let(:current_user) { build(:admin) }
subject { described_class.new(current_user, nil) }
it 'allowed when in admin mode' do
enable_admin_mode!(current_user)
is_expected.to be_allowed(:read_cross_project)
end
it 'prevented when not in admin mode' do
is_expected.not_to be_allowed(:read_cross_project)
end
end
end
end
describe 'full private access' do
let(:current_user) { create(: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) }
it 'allowed when in admin mode' do
enable_admin_mode!(current_user)
is_expected.to be_allowed(:read_all_resources)
end
it 'prevented when not in admin mode' do
is_expected.not_to be_allowed(:read_all_resources)
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