Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-07 00:07:23 +00:00
parent c47ade2adb
commit 3462d7613f
42 changed files with 1115 additions and 323 deletions

View File

@ -1 +1 @@
d12fb69a841d91d843f392a124865f6d47d3bc22
747d61c17a51f361cc883c9d4700e174219088a5

View File

@ -93,12 +93,12 @@ export default {
return {
name,
list,
title: this.getAwardListTitle(list),
title: this.getAwardListTitle(list, name),
classes: this.getAwardClassBindings(list),
html: glEmojiTag(name),
};
},
getAwardListTitle(awardsList) {
getAwardListTitle(awardsList, name) {
if (!awardsList.length) {
return '';
}
@ -128,7 +128,7 @@ export default {
// We have 10+ awarded user, join them with comma and add `and x more`.
if (remainingAwardList.length) {
title = sprintf(
__(`%{listToShow}, and %{awardsListLength} more.`),
__(`%{listToShow}, and %{awardsListLength} more`),
{
listToShow: namesToShow.join(', '),
awardsListLength: remainingAwardList.length,
@ -146,7 +146,7 @@ export default {
title = namesToShow.join(__(' and '));
}
return title;
return title + sprintf(__(' reacted with :%{name}:'), { name });
},
handleAward(awardName) {
if (!this.canAwardEmoji) {

View File

@ -16,6 +16,7 @@ module Environments
environments = project.environments
environments = by_name(environments)
environments = by_search(environments)
environments = by_ids(environments)
# Raises InvalidStatesError if params[:states] contains invalid states.
by_states(environments)
@ -47,6 +48,14 @@ module Environments
end
end
def by_ids(environments)
if params[:environment_ids].present?
environments.for_id(params[:environment_ids])
else
environments
end
end
def environments_with_states(environments)
# Convert to array of strings
states = Array(params[:states]).map(&:to_s)

View File

@ -0,0 +1,8 @@
---
name: one_megabyte_file_size_limit
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65167
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334916
milestone: '14.1'
type: development
group: group::code review
default_enabled: false

View File

@ -49,7 +49,7 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
Gitlab::Metrics::Samplers::PumaSampler.instance.start
end
Gitlab::Metrics.gauge(:deployments, 'GitLab Version', {}, :max).set({ version: Gitlab::VERSION }, 1)
Gitlab::Metrics.gauge(:deployments, 'GitLab Version', {}, :max).set({ version: Gitlab::VERSION, revision: Gitlab.revision }, 1)
unless Gitlab::Runtime.sidekiq?
Gitlab::Metrics::RequestsRackMiddleware.initialize_metrics

View File

@ -0,0 +1 @@
1b74312f59f6f8937cd0dd754d22dc72e9bdc7302e6254a2fda5762afebe303c

View File

@ -1,35 +1,11 @@
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.ar_internal_metadata (
CREATE TABLE ar_internal_metadata (
key character varying NOT NULL,
value character varying,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);
--
-- Name: ci_instance_variables; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.ci_instance_variables (
CREATE TABLE ci_instance_variables (
id bigint NOT NULL,
variable_type smallint DEFAULT 1 NOT NULL,
masked boolean DEFAULT false,
@ -42,80 +18,28 @@ CREATE TABLE public.ci_instance_variables (
CONSTRAINT check_956afd70f1 CHECK ((char_length(encrypted_value) <= 13579))
);
--
-- Name: ci_instance_variables_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.ci_instance_variables_id_seq
CREATE SEQUENCE ci_instance_variables_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_instance_variables_id_seq OWNED BY ci_instance_variables.id;
--
-- Name: ci_instance_variables_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.ci_instance_variables_id_seq OWNED BY public.ci_instance_variables.id;
--
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.schema_migrations (
CREATE TABLE schema_migrations (
version character varying NOT NULL
);
ALTER TABLE ONLY ci_instance_variables ALTER COLUMN id SET DEFAULT nextval('ci_instance_variables_id_seq'::regclass);
--
-- Name: ci_instance_variables id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.ci_instance_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_instance_variables_id_seq'::regclass);
--
-- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.ar_internal_metadata
ALTER TABLE ONLY ar_internal_metadata
ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
--
-- Name: ci_instance_variables ci_instance_variables_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.ci_instance_variables
ALTER TABLE ONLY ci_instance_variables
ADD CONSTRAINT ci_instance_variables_pkey PRIMARY KEY (id);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.schema_migrations
ALTER TABLE ONLY schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: index_ci_instance_variables_on_key; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON public.ci_instance_variables USING btree (key);
--
-- PostgreSQL database dump complete
--
SET search_path TO "$user", public;
INSERT INTO "schema_migrations" (version) VALUES
('20210617101848');
CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON ci_instance_variables USING btree (key);

View File

@ -9,6 +9,8 @@ class FinalizePushEventPayloadsBigintConversion < ActiveRecord::Migration[6.1]
INDEX_NAME = 'index_push_event_payloads_on_event_id_convert_to_bigint'
def up
return unless should_run?
ensure_batched_background_migration_is_finished(
job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
table_name: TABLE_NAME,
@ -20,11 +22,17 @@ class FinalizePushEventPayloadsBigintConversion < ActiveRecord::Migration[6.1]
end
def down
return unless should_run?
swap_columns
end
private
def should_run?
Gitlab.dev_or_test_env? || Gitlab.com?
end
def swap_columns
add_concurrent_index TABLE_NAME, :event_id_convert_to_bigint, unique: true, name: INDEX_NAME

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require Rails.root.join('db', 'post_migrate', '20210622041846_finalize_push_event_payloads_bigint_conversion')
class MigratePushEventPayloadsEventIdBackToIntegerForGitlabCom < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
def up
FinalizePushEventPayloadsBigintConversion.new.down
end
def down
FinalizePushEventPayloadsBigintConversion.new.up
end
end

View File

@ -0,0 +1 @@
71ad8c8f2419721f8fdf6c6bbd1265c4a7ca277972c59319e155bc6dfc46aa48

View File

@ -17376,7 +17376,7 @@ ALTER SEQUENCE protected_tags_id_seq OWNED BY protected_tags.id;
CREATE TABLE push_event_payloads (
commit_count bigint NOT NULL,
event_id_convert_to_bigint integer DEFAULT 0 NOT NULL,
event_id integer NOT NULL,
action smallint NOT NULL,
ref_type smallint NOT NULL,
commit_from bytea,
@ -17384,7 +17384,7 @@ CREATE TABLE push_event_payloads (
ref text,
commit_title character varying(70),
ref_count integer,
event_id bigint NOT NULL
event_id_convert_to_bigint bigint DEFAULT 0 NOT NULL
);
CREATE TABLE push_rules (
@ -27974,6 +27974,4 @@ ALTER TABLE ONLY user_follow_users
ADD CONSTRAINT user_follow_users_followee_id_fkey FOREIGN KEY (followee_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_follow_users
ADD CONSTRAINT user_follow_users_follower_id_fkey FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE;-- schema_migrations.version information is no longer stored in this file,
-- but instead tracked in the db/schema_migrations directory
-- see https://gitlab.com/gitlab-org/gitlab/-/issues/218590 for details
ADD CONSTRAINT user_follow_users_follower_id_fkey FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE;

View File

@ -10949,6 +10949,7 @@ Represents the network policy.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="networkpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
| <a id="networkpolicyenvironments"></a>`environments` | [`EnvironmentConnection`](#environmentconnection) | Environments where this policy is applied. (see [Connections](#connections)) |
| <a id="networkpolicyfromautodevops"></a>`fromAutoDevops` | [`Boolean!`](#boolean) | Indicates whether this policy is created from AutoDevops. |
| <a id="networkpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
| <a id="networkpolicynamespace"></a>`namespace` | [`String!`](#string) | Namespace of the policy. |

View File

@ -7,10 +7,7 @@ module Gitlab
extend ActiveSupport::Concern
def dump_schema_information # :nodoc:
return super unless ActiveRecord::Base.configurations.primary?(pool.db_config.name)
versions = schema_migration.all_versions
Gitlab::Database::SchemaVersionFiles.touch_all(versions) if versions.any?
Gitlab::Database::SchemaMigrations.touch_all(self)
nil
end

View File

@ -7,13 +7,9 @@ module Gitlab
extend ActiveSupport::Concern
def structure_load(...)
result = super(...)
super(...)
if ActiveRecord::Base.configurations.primary?(connection.pool.db_config.name)
Gitlab::Database::SchemaVersionFiles.load_all
else
result
end
Gitlab::Database::SchemaMigrations.load_all(connection)
end
end
end

View File

@ -30,11 +30,7 @@ module Gitlab
structure.gsub!(/\n{3,}/, "\n\n")
io << structure.strip
io << <<~MSG
-- schema_migrations.version information is no longer stored in this file,
-- but instead tracked in the db/schema_migrations directory
-- see https://gitlab.com/gitlab-org/gitlab/-/issues/218590 for details
MSG
io << "\n"
nil
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaMigrations
def self.touch_all(connection)
context = Gitlab::Database::SchemaMigrations::Context.new(connection)
Gitlab::Database::SchemaMigrations::Migrations.new(context).touch_all
end
def self.load_all(connection)
context = Gitlab::Database::SchemaMigrations::Context.new(connection)
Gitlab::Database::SchemaMigrations::Migrations.new(context).load_all
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaMigrations
class Context
attr_reader :connection
def initialize(connection)
@connection = connection
end
def schema_directory
@schema_directory ||=
if ActiveRecord::Base.configurations.primary?(database_name)
File.join(db_dir, 'schema_migrations')
else
File.join(db_dir, "#{database_name}_schema_migrations")
end
end
def versions_to_create
versions_from_database = @connection.schema_migration.all_versions
versions_from_migration_files = @connection.migration_context.migrations.map { |m| m.version.to_s }
versions_from_database & versions_from_migration_files
end
private
def database_name
@database_name ||= @connection.pool.db_config.name
end
def db_dir
@db_dir ||= Rails.application.config.paths["db"].first
end
end
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaMigrations
class Migrations
MIGRATION_VERSION_GLOB = '20[0-9][0-9]*'
def initialize(context)
@context = context
end
def touch_all
return unless @context.versions_to_create.any?
version_filepaths = version_filenames.map { |f| File.join(schema_directory, f) }
FileUtils.rm(version_filepaths)
@context.versions_to_create.each do |version|
version_filepath = File.join(schema_directory, version)
File.open(version_filepath, 'w') do |file|
file << Digest::SHA256.hexdigest(version)
end
end
end
def load_all
return if version_filenames.empty?
values = version_filenames.map { |vf| "('#{@context.connection.quote_string(vf)}')" }
@context.connection.execute(<<~SQL)
INSERT INTO schema_migrations (version)
VALUES #{values.join(',')}
ON CONFLICT DO NOTHING
SQL
end
private
def schema_directory
@context.schema_directory
end
def version_filenames
@version_filenames ||= Dir.glob(MIGRATION_VERSION_GLOB, base: schema_directory)
end
end
end
end
end

View File

@ -1,64 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Database
class SchemaVersionFiles
SCHEMA_DIRECTORY = 'db/schema_migrations'
MIGRATION_DIRECTORIES = %w[db/migrate db/post_migrate].freeze
MIGRATION_VERSION_GLOB = '20[0-9][0-9]*'
def self.touch_all(versions_from_database)
versions_from_migration_files = find_versions_from_migration_files
version_filepaths = find_version_filenames.map { |f| schema_directory.join(f) }
FileUtils.rm(version_filepaths)
versions_to_create = versions_from_database & versions_from_migration_files
versions_to_create.each do |version|
version_filepath = schema_directory.join(version)
File.open(version_filepath, 'w') do |file|
file << Digest::SHA256.hexdigest(version)
end
end
end
def self.load_all
version_filenames = find_version_filenames
return if version_filenames.empty?
values = version_filenames.map { |vf| "('#{connection.quote_string(vf)}')" }
connection.execute(<<~SQL)
INSERT INTO schema_migrations (version)
VALUES #{values.join(',')}
ON CONFLICT DO NOTHING
SQL
end
def self.schema_directory
@schema_directory ||= Rails.root.join(SCHEMA_DIRECTORY)
end
def self.migration_directories
@migration_directories ||= MIGRATION_DIRECTORIES.map { |dir| Rails.root.join(dir) }
end
def self.find_version_filenames
Dir.glob(MIGRATION_VERSION_GLOB, base: schema_directory)
end
def self.find_versions_from_migration_files
migration_directories.each_with_object([]) do |directory, migration_versions|
directory_migrations = Dir.glob(MIGRATION_VERSION_GLOB, base: directory)
directory_versions = directory_migrations.map! { |m| m.split('_').first }
migration_versions.concat(directory_versions)
end
end
def self.connection
ActiveRecord::Base.connection
end
end
end
end

View File

@ -11,11 +11,9 @@ module Gitlab
end
def self.too_large?(size)
file_size_limit = Gitlab.config.extra['maximum_text_highlight_size_kilobytes']
return false unless size.to_i > self.file_size_limit
return false unless size.to_i > file_size_limit
over_highlight_size_limit.increment(source: "file size: #{file_size_limit}") if Feature.enabled?(:track_file_size_over_highlight_limit)
over_highlight_size_limit.increment(source: "file size: #{self.file_size_limit}") if Feature.enabled?(:track_file_size_over_highlight_limit)
true
end
@ -51,6 +49,16 @@ module Gitlab
attr_reader :context
def self.file_size_limit
if Feature.enabled?(:one_megabyte_file_size_limit)
1024.kilobytes
else
Gitlab.config.extra['maximum_text_highlight_size_kilobytes']
end
end
private_class_method :file_size_limit
def custom_language
return unless @language

View File

@ -12,7 +12,7 @@ module Gitlab
# We are modeling existing kubernetes resource and don't have
# control over amount of parameters.
# rubocop:disable Metrics/ParameterLists
def initialize(name:, namespace:, selector:, ingress:, resource_version: nil, description: nil, labels: nil, creation_timestamp: nil, egress: nil, annotations: nil)
def initialize(name:, namespace:, selector:, ingress:, resource_version: nil, description: nil, labels: nil, creation_timestamp: nil, egress: nil, annotations: nil, environment_ids: [])
@name = name
@description = description
@namespace = namespace
@ -23,6 +23,7 @@ module Gitlab
@ingress = ingress
@egress = egress
@annotations = annotations
@environment_ids = environment_ids
end
# rubocop:enable Metrics/ParameterLists
@ -49,7 +50,7 @@ module Gitlab
nil
end
def self.from_resource(resource)
def self.from_resource(resource, environment_ids = [])
return unless resource
return if !resource[:metadata] || !resource[:spec]
@ -65,7 +66,8 @@ module Gitlab
creation_timestamp: metadata[:creationTimestamp],
selector: spec[:endpointSelector],
ingress: spec[:ingress],
egress: spec[:egress]
egress: spec[:egress],
environment_ids: environment_ids
)
end
@ -83,7 +85,7 @@ module Gitlab
private
attr_reader :name, :description, :namespace, :labels, :creation_timestamp, :resource_version, :ingress, :egress, :annotations
attr_reader :name, :description, :namespace, :labels, :creation_timestamp, :resource_version, :ingress, :egress, :annotations, :environment_ids
def selector
@selector ||= {}

View File

@ -8,7 +8,8 @@ module Gitlab
KIND = 'NetworkPolicy'
def initialize(name:, namespace:, selector:, ingress:, labels: nil, creation_timestamp: nil, policy_types: ["Ingress"], egress: nil)
# rubocop:disable Metrics/ParameterLists
def initialize(name:, namespace:, selector:, ingress:, labels: nil, creation_timestamp: nil, policy_types: ["Ingress"], egress: nil, environment_ids: [])
@name = name
@namespace = namespace
@labels = labels
@ -17,7 +18,9 @@ module Gitlab
@policy_types = policy_types
@ingress = ingress
@egress = egress
@environment_ids = environment_ids
end
# rubocop:enable Metrics/ParameterLists
def self.from_yaml(manifest)
return unless manifest
@ -40,7 +43,7 @@ module Gitlab
nil
end
def self.from_resource(resource)
def self.from_resource(resource, environment_ids = [])
return unless resource
return if !resource[:metadata] || !resource[:spec]
@ -54,7 +57,8 @@ module Gitlab
selector: spec[:podSelector],
policy_types: spec[:policyTypes],
ingress: spec[:ingress],
egress: spec[:egress]
egress: spec[:egress],
environment_ids: environment_ids
)
end
@ -69,7 +73,7 @@ module Gitlab
private
attr_reader :name, :namespace, :labels, :creation_timestamp, :policy_types, :ingress, :egress
attr_reader :name, :namespace, :labels, :creation_timestamp, :policy_types, :ingress, :egress, :environment_ids
def selector
@selector ||= {}

View File

@ -16,7 +16,8 @@ module Gitlab
creation_timestamp: creation_timestamp,
manifest: manifest,
is_autodevops: autodevops?,
is_enabled: enabled?
is_enabled: enabled?,
environment_ids: environment_ids
}
end

View File

@ -177,7 +177,7 @@ module Gitlab
end
def repository_route_regex
@repository_route_regex ||= /#{full_namespace_route_regex}|#{personal_snippet_repository_path_regex}/.freeze
@repository_route_regex ||= /(#{full_namespace_route_regex}|#{personal_snippet_repository_path_regex})\.*/.freeze
end
def repository_git_route_regex
@ -185,7 +185,7 @@ module Gitlab
end
def repository_wiki_git_route_regex
@repository_wiki_git_route_regex ||= /#{full_namespace_route_regex}\.wiki\.git/.freeze
@repository_wiki_git_route_regex ||= /#{full_namespace_route_regex}\.*\.wiki\.git/.freeze
end
def full_namespace_path_regex

View File

@ -90,11 +90,14 @@ namespace :gitlab do
desc 'This adjusts and cleans db/structure.sql - it runs after db:structure:dump'
task :clean_structure_sql do |task_name|
structure_file = 'db/structure.sql'
schema = File.read(structure_file)
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
structure_file = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.name)
File.open(structure_file, 'wb+') do |io|
Gitlab::Database::SchemaCleaner.new(schema).clean(io)
schema = File.read(structure_file)
File.open(structure_file, 'wb+') do |io|
Gitlab::Database::SchemaCleaner.new(schema).clean(io)
end
end
# Allow this task to be called multiple times, as happens when running db:migrate:redo

View File

@ -77,6 +77,9 @@ msgstr ""
msgid " or references (e.g. path/to/project!merge_request_id)"
msgstr ""
msgid " reacted with :%{name}:"
msgstr ""
msgid "\"%{path}\" did not exist on \"%{ref}\""
msgstr ""
@ -687,7 +690,7 @@ msgstr ""
msgid "%{link_start}Start the title with %{draft_snippet}%{link_end} to prevent a merge request that is a work in progress from being merged before it's ready."
msgstr ""
msgid "%{listToShow}, and %{awardsListLength} more."
msgid "%{listToShow}, and %{awardsListLength} more"
msgstr ""
msgid "%{location} is missing required keys: %{keys}"

View File

@ -63,7 +63,7 @@ RSpec.describe 'User interacts with awards' do
page.within('.awards') do
expect(page).to have_selector('[data-testid="award-button"]')
expect(page.find('[data-testid="award-button"].selected .js-counter')).to have_content('1')
expect(page).to have_css('[data-testid="award-button"].selected[title="You"]')
expect(page).to have_css('[data-testid="award-button"].selected[title="You reacted with :8ball:"]')
expect do
page.find('[data-testid="award-button"].selected').click

View File

@ -3,9 +3,11 @@
require 'spec_helper'
RSpec.describe Environments::EnvironmentsFinder do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:environment) { create(:environment, :available, project: project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.creator }
let_it_be(:environment) { create(:environment, :available, project: project) }
let_it_be(:environment_stopped) { create(:environment, :stopped, name: 'test2', project: project) }
let_it_be(:environment_available) { create(:environment, :available, name: 'test3', project: project) }
before do
project.add_maintainer(user)
@ -13,18 +15,18 @@ RSpec.describe Environments::EnvironmentsFinder do
describe '#execute' do
context 'with states parameter' do
let(:stopped_environment) { create(:environment, :stopped, project: project) }
let_it_be(:stopped_environment) { create(:environment, :stopped, project: project) }
it 'returns environments with the requested state' do
result = described_class.new(project, user, states: 'available').execute
expect(result).to contain_exactly(environment)
expect(result).to contain_exactly(environment, environment_available)
end
it 'returns environments with any of the requested states' do
result = described_class.new(project, user, states: %w(available stopped)).execute
expect(result).to contain_exactly(environment, stopped_environment)
expect(result).to contain_exactly(environment, environment_stopped, environment_available, stopped_environment)
end
it 'raises exception when requested state is invalid' do
@ -37,25 +39,30 @@ RSpec.describe Environments::EnvironmentsFinder do
it 'returns environments with the requested state' do
result = described_class.new(project, user, states: :available).execute
expect(result).to contain_exactly(environment)
expect(result).to contain_exactly(environment, environment_available)
end
it 'returns environments with any of the requested states' do
result = described_class.new(project, user, states: [:available, :stopped]).execute
expect(result).to contain_exactly(environment, stopped_environment)
expect(result).to contain_exactly(environment, environment_stopped, environment_available, stopped_environment)
end
end
end
context 'with search and states' do
let(:environment2) { create(:environment, :stopped, name: 'test2', project: project) }
let(:environment3) { create(:environment, :available, name: 'test3', project: project) }
it 'searches environments by name and state' do
result = described_class.new(project, user, search: 'test', states: :available).execute
expect(result).to contain_exactly(environment3)
expect(result).to contain_exactly(environment_available)
end
end
context 'with id' do
it 'searches environments by name and state' do
result = described_class.new(project, user, search: 'test', environment_ids: [environment_available.id]).execute
expect(result).to contain_exactly(environment_available)
end
end
end

View File

@ -23,6 +23,4 @@ ALTER TABLE ONLY abuse_reports ALTER COLUMN id SET DEFAULT nextval('abuse_report
ALTER TABLE ONLY abuse_reports
ADD CONSTRAINT abuse_reports_pkey PRIMARY KEY (id);
CREATE INDEX index_abuse_reports_on_user_id ON abuse_reports USING btree (user_id);-- schema_migrations.version information is no longer stored in this file,
-- but instead tracked in the db/schema_migrations directory
-- see https://gitlab.com/gitlab-org/gitlab/-/issues/218590 for details
CREATE INDEX index_abuse_reports_on_user_id ON abuse_reports USING btree (user_id);

View File

@ -7,7 +7,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada, Leonardo, and Marie"
title="Ada, Leonardo, and Marie reacted with :thumbsup:"
type="button"
>
<!---->
@ -37,7 +37,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, and Marie"
title="You, Ada, and Marie reacted with :thumbsdown:"
type="button"
>
<!---->
@ -67,7 +67,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
data-testid="award-button"
title="Ada and Jane"
title="Ada and Jane reacted with :smile:"
type="button"
>
<!---->
@ -97,7 +97,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You, Ada, Jane, and Leonardo"
title="You, Ada, Jane, and Leonardo reacted with :ok_hand:"
type="button"
>
<!---->
@ -127,7 +127,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
title="You reacted with :cactus:"
type="button"
>
<!---->
@ -157,7 +157,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
data-testid="award-button"
title="Marie"
title="Marie reacted with :a:"
type="button"
>
<!---->
@ -187,7 +187,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
data-testid="award-button"
title="You"
title="You reacted with :b:"
type="button"
>
<!---->

View File

@ -98,43 +98,43 @@ describe('vue_shared/components/awards_list', () => {
classes: REACTION_CONTROL_CLASSES,
count: 3,
html: matchingEmojiTag(EMOJI_THUMBSUP),
title: 'Ada, Leonardo, and Marie',
title: `Ada, Leonardo, and Marie reacted with :${EMOJI_THUMBSUP}:`,
},
{
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 3,
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: 'You, Ada, and Marie',
title: `You, Ada, and Marie reacted with :${EMOJI_THUMBSDOWN}:`,
},
{
classes: REACTION_CONTROL_CLASSES,
count: 2,
html: matchingEmojiTag(EMOJI_SMILE),
title: 'Ada and Jane',
title: `Ada and Jane reacted with :${EMOJI_SMILE}:`,
},
{
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 4,
html: matchingEmojiTag(EMOJI_OK),
title: 'You, Ada, Jane, and Leonardo',
title: `You, Ada, Jane, and Leonardo reacted with :${EMOJI_OK}:`,
},
{
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1,
html: matchingEmojiTag(EMOJI_CACTUS),
title: 'You',
title: `You reacted with :${EMOJI_CACTUS}:`,
},
{
classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_A),
title: 'Marie',
title: `Marie reacted with :${EMOJI_A}:`,
},
{
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
count: 1,
html: matchingEmojiTag(EMOJI_B),
title: 'You',
title: `You reacted with :${EMOJI_B}:`,
},
]);
});
@ -246,13 +246,13 @@ describe('vue_shared/components/awards_list', () => {
classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_100),
title: 'Marie',
title: `Marie reacted with :${EMOJI_100}:`,
},
{
classes: REACTION_CONTROL_CLASSES,
count: 1,
html: matchingEmojiTag(EMOJI_SMILE),
title: 'Marie',
title: `Marie reacted with :${EMOJI_SMILE}:`,
},
]);
});

View File

@ -3,10 +3,6 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::PostgresqlAdapter::DumpSchemaVersionsMixin do
let(:schema_migration) { double('schema_migration', all_versions: versions) }
let(:db_name) { 'primary' }
let(:versions) { %w(5 2 1000 200 4 93 2) }
let(:instance_class) do
klass = Class.new do
def dump_schema_information
@ -24,43 +20,10 @@ RSpec.describe Gitlab::Database::PostgresqlAdapter::DumpSchemaVersionsMixin do
let(:instance) { instance_class.new }
before do
allow(instance).to receive(:schema_migration).and_return(schema_migration)
it 'calls SchemaMigrations touch_all and skips original implementation' do
expect(Gitlab::Database::SchemaMigrations).to receive(:touch_all).with(instance)
expect(instance).not_to receive(:original_dump_schema_information)
# pool is from ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
allow(instance).to receive_message_chain(:pool, :db_config, :name).and_return(db_name)
end
context 'when database name is primary' do
context 'when version files exist' do
it 'touches version files' do
expect(Gitlab::Database::SchemaVersionFiles).to receive(:touch_all).with(versions)
expect(instance).not_to receive(:original_dump_schema_information)
instance.dump_schema_information
end
end
context 'when version files do not exist' do
let(:versions) { [] }
it 'does not touch version files' do
expect(Gitlab::Database::SchemaVersionFiles).not_to receive(:touch_all)
expect(instance).not_to receive(:original_dump_schema_information)
instance.dump_schema_information
end
end
end
context 'when database name is ci' do
let(:db_name) { 'ci' }
it 'does not touch version files' do
expect(Gitlab::Database::SchemaVersionFiles).not_to receive(:touch_all)
expect(instance).to receive(:original_dump_schema_information)
instance.dump_schema_information
end
instance.dump_schema_information
end
end

View File

@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::PostgresqlDatabaseTasks::LoadSchemaVersionsMixin do
let(:db_name) { 'primary' }
let(:instance_class) do
klass = Class.new do
def structure_load
@ -22,28 +20,13 @@ RSpec.describe Gitlab::Database::PostgresqlDatabaseTasks::LoadSchemaVersionsMixi
let(:instance) { instance_class.new }
before do
# connection is available in ActiveRecord::Tasks::PostgreSQLDatabaseTasks
allow(instance).to receive_message_chain(:connection, :pool, :db_config, :name).and_return(db_name)
end
it 'calls SchemaMigrations load_all' do
connection = double('connection')
allow(instance).to receive(:connection).and_return(connection)
context 'when database is primary' do
it 'loads version files' do
expect(Gitlab::Database::SchemaVersionFiles).to receive(:load_all)
expect(instance).to receive(:original_structure_load)
expect(instance).to receive(:original_structure_load).ordered
expect(Gitlab::Database::SchemaMigrations).to receive(:load_all).with(connection).ordered
instance.structure_load
end
end
context 'when the database is ci' do
let(:db_name) { 'ci' }
it 'does not load version files' do
expect(Gitlab::Database::SchemaVersionFiles).not_to receive(:load_all)
expect(instance).to receive(:original_structure_load)
instance.structure_load
end
instance.structure_load
end
end

View File

@ -0,0 +1,78 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaMigrations::Context do
let(:connection) { ActiveRecord::Base.connection }
let(:context) { described_class.new(connection) }
describe '#schema_directory' do
it 'returns db/schema_migrations' do
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/schema_migrations'))
end
context 'multiple databases' do
let(:connection) { Ci::BaseModel.connection }
it 'returns a directory path that is database specific' do
skip_if_multiple_databases_not_setup
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations'))
end
end
end
describe '#versions_to_create' do
before do
allow(connection).to receive_message_chain(:schema_migration, :all_versions).and_return(migrated_versions)
migrations_struct = Struct.new(:version)
migrations = file_versions.map { |version| migrations_struct.new(version) }
allow(connection).to receive_message_chain(:migration_context, :migrations).and_return(migrations)
end
let(:version1) { '20200123' }
let(:version2) { '20200410' }
let(:version3) { '20200602' }
let(:version4) { '20200809' }
let(:migrated_versions) { file_versions }
let(:file_versions) { [version1, version2, version3, version4] }
context 'migrated versions is the same as migration file versions' do
it 'returns migrated versions' do
expect(context.versions_to_create).to eq(migrated_versions)
end
end
context 'migrated versions is subset of migration file versions' do
let(:migrated_versions) { [version1, version2] }
it 'returns migrated versions' do
expect(context.versions_to_create).to eq(migrated_versions)
end
end
context 'migrated versions is superset of migration file versions' do
let(:migrated_versions) { file_versions + ['20210809'] }
it 'returns file versions' do
expect(context.versions_to_create).to eq(file_versions)
end
end
context 'migrated versions has slightly different versions to migration file versions' do
let(:migrated_versions) { [version1, version2, version3, version4, '20210101'] }
let(:file_versions) { [version1, version2, version3, version4, '20210102'] }
it 'returns the common set' do
expect(context.versions_to_create).to eq([version1, version2, version3, version4])
end
end
end
def skip_if_multiple_databases_not_setup
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
end
end

View File

@ -2,43 +2,37 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaVersionFiles do
describe '.touch_all' do
RSpec.describe Gitlab::Database::SchemaMigrations::Migrations do
let(:connection) { ApplicationRecord.connection }
let(:context) { Gitlab::Database::SchemaMigrations::Context.new(connection) }
let(:migrations) { described_class.new(context) }
describe '#touch_all' do
let(:version1) { '20200123' }
let(:version2) { '20200410' }
let(:version3) { '20200602' }
let(:version4) { '20200809' }
let(:relative_schema_directory) { 'db/schema_migrations' }
let(:relative_migrate_directory) { 'db/migrate' }
let(:relative_post_migrate_directory) { 'db/post_migrate' }
it 'creates a file containing a checksum for each version with a matching migration' do
Dir.mktmpdir do |tmpdir|
schema_directory = Pathname.new(tmpdir).join(relative_schema_directory)
migrate_directory = Pathname.new(tmpdir).join(relative_migrate_directory)
post_migrate_directory = Pathname.new(tmpdir).join(relative_post_migrate_directory)
FileUtils.mkdir_p(migrate_directory)
FileUtils.mkdir_p(post_migrate_directory)
FileUtils.mkdir_p(schema_directory)
migration1_filepath = migrate_directory.join("#{version1}_migration.rb")
FileUtils.touch(migration1_filepath)
migration2_filepath = post_migrate_directory.join("#{version2}_post_migration.rb")
FileUtils.touch(migration2_filepath)
old_version_filepath = schema_directory.join('20200101')
FileUtils.touch(old_version_filepath)
expect(File.exist?(old_version_filepath)).to be(true)
allow(described_class).to receive(:schema_directory).and_return(schema_directory)
allow(described_class).to receive(:migration_directories).and_return([migrate_directory, post_migrate_directory])
allow(context).to receive(:schema_directory).and_return(schema_directory)
allow(context).to receive(:versions_to_create).and_return([version1, version2])
described_class.touch_all([version1, version2, version3, version4])
migrations.touch_all
expect(File.exist?(old_version_filepath)).to be(false)
[version1, version2].each do |version|
version_filepath = schema_directory.join(version)
expect(File.exist?(version_filepath)).to be(true)
@ -55,12 +49,9 @@ RSpec.describe Gitlab::Database::SchemaVersionFiles do
end
end
describe '.load_all' do
let(:connection) { double('connection') }
describe '#load_all' do
before do
allow(described_class).to receive(:connection).and_return(connection)
allow(described_class).to receive(:find_version_filenames).and_return(filenames)
allow(migrations).to receive(:version_filenames).and_return(filenames)
end
context 'when there are no version files' do
@ -70,7 +61,7 @@ RSpec.describe Gitlab::Database::SchemaVersionFiles do
expect(connection).not_to receive(:quote_string)
expect(connection).not_to receive(:execute)
described_class.load_all
migrations.load_all
end
end
@ -88,7 +79,7 @@ RSpec.describe Gitlab::Database::SchemaVersionFiles do
ON CONFLICT DO NOTHING
SQL
described_class.load_all
migrations.load_all
end
end
end

View File

@ -50,9 +50,16 @@ RSpec.describe Gitlab::Highlight do
let(:result) { described_class.highlight(file_name, content) } # content is 44 bytes
before do
stub_feature_flags(one_megabyte_file_size_limit: false)
stub_config(extra: { 'maximum_text_highlight_size_kilobytes' => 0.0001 } ) # 1.024 bytes
end
it 'confirm file size is 1MB when `one_megabyte_file_size_limit` is enabled' do
stub_feature_flags(one_megabyte_file_size_limit: true)
expect(described_class.too_large?(1024.kilobytes)).to eq(false)
expect(described_class.too_large?(1025.kilobytes)).to eq(true)
end
it 'increments the metric for oversized files' do
expect { result }.to change { over_highlight_size_limit('file size: 0.0001') }.by(1)
end

View File

@ -206,6 +206,14 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
it { is_expected.to be_nil }
end
context 'with environment_ids' do
subject { Gitlab::Kubernetes::CiliumNetworkPolicy.from_resource(resource, [1, 2, 3]) }
it 'includes environment_ids in as_json result' do
expect(subject.as_json).to include(environment_ids: [1, 2, 3])
end
end
end
describe '#resource' do

View File

@ -196,6 +196,14 @@ RSpec.describe Gitlab::Kubernetes::NetworkPolicy do
it { is_expected.to be_nil }
end
context 'with environment_ids' do
subject { Gitlab::Kubernetes::NetworkPolicy.from_resource(resource, [1, 2, 3]) }
it 'includes environment_ids in as_json result' do
expect(subject.as_json).to include(environment_ids: [1, 2, 3])
end
end
end
describe '#resource' do

View File

@ -445,6 +445,8 @@ RSpec.describe Gitlab::PathRegex do
[
'gitlab-org',
'gitlab-org/gitlab-test',
'gitlab-org/foo.',
'gitlab-org/bar..',
'gitlab-org/gitlab-test/snippets/1',
'gitlab-org/gitlab-test/snippets/foo', # ambiguous, we allow creating a sub-group called 'snippets'
'snippets/1'

View File

@ -966,6 +966,722 @@ RSpec.describe 'Git HTTP requests' do
end
end
end
context "when the project path ends with a dot" do
let(:path) { "#{project.full_path}.git" }
context "when the project is public" do
let(:project) { create(:project, :repository, :public, path: 'foo.') }
it_behaves_like 'pushes require Basic HTTP Authentication'
context 'when not authenticated' do
let(:env) { {} }
it_behaves_like 'pulls are allowed'
end
context "when authenticated" do
let(:env) { { user: user.username, password: user.password } }
context 'as a developer on the team' do
before do
project.add_developer(user)
end
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
context 'but git-receive-pack over HTTP is disabled in config' do
before do
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
end
it 'rejects pushes with 403 Forbidden' do
upload(path, **env) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:receive_pack_disabled_over_http))
end
end
end
context 'but git-upload-pack over HTTP is disabled in config' do
it "rejects pushes with 403 Forbidden" do
allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
download(path, **env) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:upload_pack_disabled_over_http))
end
end
end
context 'but the service parameter is missing' do
it 'rejects clones with 403 Forbidden' do
get("/#{path}/info/refs", headers: auth_env(*env.values_at(:user, :password), nil))
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'and not a member of the team' do
it_behaves_like 'pulls are allowed'
it 'rejects pushes with 403 Forbidden' do
upload(path, **env) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to eq('You are not allowed to push code to this project.')
end
end
context 'when merge requests are open that allow maintainer access' do
let(:canonical_project) { create(:project, :public, :repository) }
let(:project) { fork_project(canonical_project, nil, repository: true) }
before do
canonical_project.add_maintainer(user)
create(:merge_request,
source_project: project,
target_project: canonical_project,
source_branch: 'fixes',
allow_collaboration: true)
end
it_behaves_like 'pushes are allowed'
end
context 'but the service parameter is missing' do
it 'rejects clones with 401 Unauthorized' do
get("/#{path}/info/refs")
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
end
context 'when the request is not from gitlab-workhorse' do
it 'raises an exception' do
expect do
get("/#{project.full_path}.git/info/refs?service=git-upload-pack")
end.to raise_error(JWT::DecodeError)
end
end
context 'when the repo is public' do
context 'but the repo is disabled' do
let(:project) { create(:project, :public, :repository, :repository_disabled) }
let(:path) { "#{project.full_path}.git" }
let(:env) { {} }
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
end
context 'but the repo is enabled' do
let(:project) { create(:project, :public, :repository, :repository_enabled) }
let(:path) { "#{project.full_path}.git" }
let(:env) { {} }
it_behaves_like 'pulls are allowed'
end
context 'but only project members are allowed' do
let(:project) { create(:project, :public, :repository, :repository_private) }
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
end
end
context 'and the user requests a redirected path' do
let!(:redirect) { project.route.create_redirect('foo/bar') }
let(:path) { "#{redirect.path}.git" }
it 'downloads get status 200 for redirects' do
clone_get(path)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context "when the project is private" do
let(:project) { create(:project, :repository, :private, path: 'foo.') }
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
context "when username and password are provided" do
let(:env) { { user: user.username, password: 'nope' } }
context "when authentication fails" do
context "when the user is IP banned" do
before do
stub_rack_attack_setting(enabled: true, ip_whitelist: [])
end
it "responds with status 403" do
expect(Rack::Attack::Allow2Ban).to receive(:banned?).and_return(true)
expect(Gitlab::AuthLogger).to receive(:error).with({
message: 'Rack_Attack',
env: :blocklist,
remote_ip: '127.0.0.1',
request_method: 'GET',
path: "/#{path}/info/refs?service=git-upload-pack"
})
clone_get(path, **env)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context "when authentication succeeds" do
let(:env) { { user: user.username, password: user.password } }
context "when the user has access to the project" do
before do
project.add_maintainer(user)
end
context "when the user is blocked" do
it "rejects pulls with 401 Unauthorized" do
user.block
project.add_maintainer(user)
download(path, **env) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
it "rejects pulls with 401 Unauthorized for unknown projects (no project existence information leak)" do
user.block
download('doesnt/exist.git', **env) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
context "when the user isn't blocked" do
before do
stub_rack_attack_setting(enabled: true, bantime: 1.minute, findtime: 5.minutes, maxretry: 2, ip_whitelist: [])
end
it "resets the IP in Rack Attack on download" do
expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
download(path, **env) do
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
it "resets the IP in Rack Attack on upload" do
expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
upload(path, **env) do
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
it 'updates the user last activity', :clean_gitlab_redis_shared_state do
expect(user.last_activity_on).to be_nil
download(path, **env) do |response|
expect(user.reload.last_activity_on).to eql(Date.today)
end
end
end
context "when an oauth token is provided" do
before do
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
@token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
end
let(:path) { "#{project.full_path}.git" }
let(:env) { { user: 'oauth2', password: @token.token } }
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
context "when password is expired" do
it "responds to downloads with status 401 unauthorized" do
user.update!(password_expires_at: 2.days.ago)
download(path, **env) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
end
context 'when user has 2FA enabled' do
let(:user) { create(:user, :two_factor) }
let(:access_token) { create(:personal_access_token, user: user) }
let(:path) { "#{project.full_path}.git" }
before do
project.add_maintainer(user)
end
context 'when username and password are provided' do
it 'rejects pulls with personal access token error message' do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
it 'rejects the push attempt with personal access token error message' do
upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
end
context 'when username and personal access token are provided' do
let(:env) { { user: user.username, password: access_token.token } }
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
it 'rejects the push attempt for read_repository scope' do
read_access_token = create(:personal_access_token, user: user, scopes: [:read_repository])
upload(path, user: user.username, password: read_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to include('You are not allowed to upload code')
end
end
it 'accepts the push attempt for write_repository scope' do
write_access_token = create(:personal_access_token, user: user, scopes: [:write_repository])
upload(path, user: user.username, password: write_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'accepts the pull attempt for read_repository scope' do
read_access_token = create(:personal_access_token, user: user, scopes: [:read_repository])
download(path, user: user.username, password: read_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'accepts the pull attempt for api scope' do
read_access_token = create(:personal_access_token, user: user, scopes: [:api])
download(path, user: user.username, password: read_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'accepts the push attempt for api scope' do
write_access_token = create(:personal_access_token, user: user, scopes: [:api])
upload(path, user: user.username, password: write_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
context "when password is expired" do
it "responds to uploads with status 401 unauthorized" do
user.update!(password_expires_at: 2.days.ago)
write_access_token = create(:personal_access_token, user: user, scopes: [:write_repository])
upload(path, user: user.username, password: write_access_token.token) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
end
end
context 'when internal auth is disabled' do
before do
allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled_for_git?) { false }
end
it 'rejects pulls with personal access token error message' do
download(path, user: 'foo', password: 'bar') do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
it 'rejects pushes with personal access token error message' do
upload(path, user: 'foo', password: 'bar') do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
context 'when LDAP is configured' do
before do
allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
allow_any_instance_of(Gitlab::Auth::Ldap::Authentication)
.to receive(:login).and_return(nil)
end
it 'does not display the personal access token error message' do
upload(path, user: 'foo', password: 'bar') do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response.body).not_to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
end
end
end
end
context "when blank password attempts follow a valid login" do
def attempt_login(include_password)
password = include_password ? user.password : ""
clone_get path, user: user.username, password: password
response.status
end
include_context 'rack attack cache store'
it "repeated attempts followed by successful attempt" do
options = Gitlab.config.rack_attack.git_basic_auth
maxretry = options[:maxretry]
ip = '1.2.3.4'
allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
Rack::Attack::Allow2Ban.reset(ip, options)
maxretry.times.each do
expect(attempt_login(false)).to eq(401)
end
expect(attempt_login(true)).to eq(200)
expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
end
end
context 'and the user requests a redirected path' do
let!(:redirect) { project.route.create_redirect('foo/bar') }
let(:path) { "#{redirect.path}.git" }
let(:project_moved_message) do
<<-MSG.strip_heredoc
Project '#{redirect.path}' was moved to '#{project.full_path}'.
Please update your Git remote:
git remote set-url origin #{project.http_url_to_repo}.
MSG
end
it 'downloads get status 200' do
clone_get(path, **env)
expect(response).to have_gitlab_http_status(:ok)
end
it 'uploads get status 404 with "project was moved" message' do
upload(path, **env) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
context "when the user doesn't have access to the project" do
it "pulls get status 404" do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:not_found)
end
end
it "uploads get status 404" do
upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end
context "when a gitlab ci token is provided" do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, :running) }
let(:other_project) { create(:project, :repository) }
before do
build.update!(project: project) # can't associate it on factory create
end
context 'when build created by system is authenticated' do
let(:path) { "#{project.full_path}.git" }
let(:env) { { user: 'gitlab-ci-token', password: build.token } }
it_behaves_like 'pulls are allowed'
# A non-401 here is not an information leak since the system is
# "authenticated" as CI using the correct token. It does not have
# push access, so pushes should be rejected as forbidden, and giving
# a reason is fine.
#
# We know for sure it is not an information leak since pulls using
# the build token must be allowed.
it "rejects pushes with 403 Forbidden" do
push_get(path, **env)
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:auth_upload))
end
# We are "authenticated" as CI using a valid token here. But we are
# not authorized to see any other project, so return "not found".
it "rejects pulls for other project with 404 Not Found" do
clone_get("#{other_project.full_path}.git", **env)
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to eq(git_access_error(:project_not_found))
end
end
context 'and build created by' do
before do
build.update!(user: user)
project.add_reporter(user)
end
shared_examples 'can download code only' do
let(:path) { "#{project.full_path}.git" }
let(:env) { { user: 'gitlab-ci-token', password: build.token } }
it_behaves_like 'pulls are allowed'
context 'when the repo does not exist' do
let(:project) { create(:project) }
it 'rejects pulls with 404 Not Found' do
clone_get(path, **env)
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to eq(git_access_error(:no_repo))
end
end
it 'rejects pushes with 403 Forbidden' do
push_get(path, **env)
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:auth_upload))
end
end
context 'administrator' do
let(:user) { create(:admin) }
context 'when admin mode is enabled', :enable_admin_mode do
it_behaves_like 'can download code only'
it 'downloads from other project get status 404' do
clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when admin mode is disabled' do
it_behaves_like 'can download code only'
it 'downloads from other project get status 404' do
clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'regular user' do
let(:user) { create(:user) }
it_behaves_like 'can download code only'
it 'downloads from other project get status 404' do
clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_gitlab_http_status(:not_found)
end
context 'when users password is expired' do
it 'rejects pulls with 401 unauthorized' do
user.update!(password_expires_at: 2.days.ago)
download(path, user: 'gitlab-ci-token', password: build.token) do |response|
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
end
end
end
end
it_behaves_like 'project path without .git suffix' do
let(:repository_path) { create(:project, :repository, :public, path: 'project.').full_path }
end
context "retrieving an info/refs file" do
let(:project) { create(:project, :repository, :public, path: 'project.') }
context "when the file exists" do
before do
# Provide a dummy file in its place
allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do
Blob.decorate(Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt'), project)
end
get "/#{project.full_path}/-/blob/master/info/refs"
end
it "returns the file" do
expect(response).to have_gitlab_http_status(:ok)
end
end
context "when the file does not exist" do
before do
get "/#{project.full_path}/-/blob/master/info/refs"
end
it "redirects" do
expect(response).to have_gitlab_http_status(:found)
end
end
end
end
context "when the Wiki path ends with a dot" do
let(:wiki) { ProjectWiki.new(project) }
let(:path) { "/#{wiki.repository.full_path}.git" }
context "when the project is public" do
let(:project) { create(:project, :wiki_repo, :public, :wiki_enabled, path: 'foo.') }
it_behaves_like 'pushes require Basic HTTP Authentication'
context 'when unauthenticated' do
let(:env) { {} }
it_behaves_like 'pulls are allowed'
it "responds to pulls with the wiki's repo" do
download(path) do |response|
json_body = ActiveSupport::JSON.decode(response.body)
expect(json_body['Repository']['relative_path']).to eq(wiki.repository.relative_path)
end
end
end
context 'when authenticated' do
let(:env) { { user: user.username, password: user.password } }
context 'and as a developer on the team' do
before do
project.add_developer(user)
end
context 'but the repo is disabled' do
let(:project) { create(:project, :wiki_repo, :public, :repository_disabled, :wiki_enabled, path: 'foo.') }
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
end
end
context 'and not on the team' do
it_behaves_like 'pulls are allowed'
it 'rejects pushes with 403 Forbidden' do
upload(path, **env) do |response|
expect(response).to have_gitlab_http_status(:forbidden)
expect(response.body).to eq(git_access_wiki_error(:write_to_wiki))
end
end
end
end
end
context "when the project is private" do
let(:project) { create(:project, :wiki_repo, :private, :wiki_enabled, path: 'foo.') }
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
it_behaves_like 'operations are not allowed with expired password'
context 'when authenticated' do
context 'and as a developer on the team' do
before do
project.add_developer(user)
end
context 'when user is using credentials with special characters' do
context 'with password with special characters' do
before do
user.update!(password: 'RKszEwéC5kFnû∆f243fycGu§Gh9ftDj!U')
end
it 'allows clones' do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
context 'but the repo is disabled' do
let(:project) { create(:project, :wiki_repo, :private, :repository_disabled, :wiki_enabled, path: 'foo.') }
it 'allows clones' do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'pushes are allowed' do
upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
context 'and not on the team' do
it 'rejects clones with 404 Not Found' do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to eq(git_access_wiki_error(:not_found))
end
end
it 'rejects pushes with 404 Not Found' do
upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to eq(git_access_wiki_error(:not_found))
end
end
end
end
end
end
end
describe "User with LDAP identity" do

View File

@ -19,7 +19,8 @@ RSpec.shared_examples 'network policy common specs' do
creation_timestamp: nil,
manifest: YAML.dump(policy.resource.deep_stringify_keys),
is_autodevops: false,
is_enabled: true
is_enabled: true,
environment_ids: []
}
end

View File

@ -124,14 +124,19 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
describe 'clean_structure_sql' do
let_it_be(:clean_rake_task) { 'gitlab:db:clean_structure_sql' }
let_it_be(:test_task_name) { 'gitlab:db:_test_multiple_structure_cleans' }
let_it_be(:structure_file) { 'db/structure.sql' }
let_it_be(:input) { 'this is structure data' }
let(:output) { StringIO.new }
before do
stub_file_read(structure_file, content: input)
allow(File).to receive(:open).with(structure_file, any_args).and_yield(output)
structure_files = %w[db/structure.sql db/ci_structure.sql]
allow(File).to receive(:open).and_call_original
structure_files.each do |structure_file|
stub_file_read(structure_file, content: input)
allow(File).to receive(:open).with(Rails.root.join(structure_file).to_s, any_args).and_yield(output)
end
end
after do
@ -139,8 +144,10 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
end
it 'can be executed multiple times within another rake task' do
expect_multiple_executions_of_task(test_task_name, clean_rake_task) do
expect_next_instance_of(Gitlab::Database::SchemaCleaner) do |cleaner|
expect_multiple_executions_of_task(test_task_name, clean_rake_task, count: 2) do
database_count = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).size
expect_next_instances_of(Gitlab::Database::SchemaCleaner, database_count) do |cleaner|
expect(cleaner).to receive(:clean).with(output)
end
end