Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue1
-rw-r--r--app/controllers/jira_connect/app_descriptor_controller.rb1
-rw-r--r--app/controllers/oauth/authorizations_controller.rb3
-rw-r--r--app/graphql/resolvers/timelog_resolver.rb108
-rw-r--r--app/graphql/types/timelog_type.rb2
-rw-r--r--app/helpers/markup_helper.rb1
-rw-r--r--app/models/concerns/has_timelogs_report.rb20
-rw-r--r--app/models/group.rb1
-rw-r--r--app/models/members/group_member.rb4
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/timelog.rb8
-rw-r--r--app/policies/concerns/policy_actor.rb4
-rw-r--r--app/policies/global_policy.rb10
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/policies/timelog_policy.rb2
-rw-r--r--app/serializers/member_entity.rb4
-rw-r--r--app/views/doorkeeper/authorizations/redirect.html.haml8
-rw-r--r--app/views/projects/pipelines/index.html.haml5
-rw-r--r--config/feature_flags/development/ci_fix_commit_status_retried.yml2
-rw-r--r--config/feature_flags/development/group_level_protected_environments.yml8
-rw-r--r--config/initializers/lograge.rb1
-rw-r--r--db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb34
-rw-r--r--db/migrate/20210520102039_group_protected_environments_add_column.rb15
-rw-r--r--db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb35
-rw-r--r--db/post_migrate/20210519220019_backfill_escalation_policies_for_oncall_schedules.rb89
-rw-r--r--db/schema_migrations/202105191540581
-rw-r--r--db/schema_migrations/202105192200191
-rw-r--r--db/schema_migrations/202105201020391
-rw-r--r--db/schema_migrations/202106010800391
-rw-r--r--db/structure.sql13
-rw-r--r--doc/user/admin_area/settings/import_export_rate_limits.md2
-rw-r--r--doc/user/group/settings/import_export.md2
-rw-r--r--doc/user/permissions.md1
-rw-r--r--doc/user/project/import/bitbucket.md2
-rw-r--r--doc/user/project/import/bitbucket_server.md2
-rw-r--r--doc/user/project/import/clearcase.md2
-rw-r--r--doc/user/project/import/cvs.md2
-rw-r--r--doc/user/project/import/fogbugz.md2
-rw-r--r--doc/user/project/import/gitea.md2
-rw-r--r--doc/user/project/import/github.md2
-rw-r--r--doc/user/project/import/gitlab_com.md2
-rw-r--r--doc/user/project/import/index.md2
-rw-r--r--doc/user/project/import/manifest.md2
-rw-r--r--doc/user/project/import/perforce.md2
-rw-r--r--doc/user/project/import/phabricator.md2
-rw-r--r--doc/user/project/import/repo_by_url.md2
-rw-r--r--doc/user/project/import/svn.md2
-rw-r--r--doc/user/project/import/tfvc.md6
-rw-r--r--doc/user/project/issues/csv_import.md2
-rw-r--r--doc/user/project/settings/import_export.md2
-rw-r--r--doc/user/project/time_tracking.md4
-rw-r--r--lib/banzai/filter/absolute_link_filter.rb5
-rw-r--r--lib/banzai/filter/ascii_doc_post_processing_filter.rb10
-rw-r--r--lib/banzai/filter/base_relative_link_filter.rb5
-rw-r--r--lib/banzai/filter/color_filter.rb5
-rw-r--r--lib/banzai/filter/custom_emoji_filter.rb2
-rw-r--r--lib/banzai/filter/emoji_filter.rb2
-rw-r--r--lib/banzai/filter/footnote_filter.rb12
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb2
-rw-r--r--lib/banzai/filter/image_lazy_load_filter.rb5
-rw-r--r--lib/banzai/filter/inline_diff_filter.rb2
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb3
-rw-r--r--lib/banzai/filter/kroki_filter.rb5
-rw-r--r--lib/banzai/filter/markdown_post_escape_filter.rb9
-rw-r--r--lib/banzai/filter/math_filter.rb9
-rw-r--r--lib/banzai/filter/mermaid_filter.rb5
-rw-r--r--lib/banzai/filter/plantuml_filter.rb7
-rw-r--r--lib/banzai/filter/suggestion_filter.rb5
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb5
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb5
-rw-r--r--lib/banzai/filter/truncate_source_filter.rb21
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb13
-rw-r--r--lib/gitlab/auth.rb12
-rw-r--r--lib/gitlab/auth/user_access_denied_reason.rb5
-rw-r--r--lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb129
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb5
-rw-r--r--lib/gitlab/utils.rb18
-rw-r--r--lib/gitlab/utils/nokogiri.rb24
-rw-r--r--lib/gitlab/x509/signature.rb10
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/controllers/oauth/authorizations_controller_spec.rb77
-rw-r--r--spec/factories/environments.rb8
-rw-r--r--spec/features/users/login_spec.rb19
-rw-r--r--spec/frontend/notebook/cells/markdown_spec.js13
-rw-r--r--spec/graphql/resolvers/timelog_resolver_spec.rb131
-rw-r--r--spec/graphql/types/timelog_type_spec.rb2
-rw-r--r--spec/helpers/markup_helper_spec.rb7
-rw-r--r--spec/initializers/lograge_spec.rb21
-rw-r--r--spec/lib/banzai/filter/truncate_source_filter_spec.rb70
-rw-r--r--spec/lib/banzai/pipeline/post_process_pipeline_spec.rb4
-rw-r--r--spec/lib/gitlab/auth/user_access_denied_reason_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb84
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_spec.rb14
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/utils/nokogiri_spec.rb34
-rw-r--r--spec/lib/gitlab/utils_spec.rb23
-rw-r--r--spec/lib/gitlab/x509/signature_spec.rb77
-rw-r--r--spec/migrations/20210601080039_group_protected_environments_add_index_and_constraint_spec.rb29
-rw-r--r--spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb100
-rw-r--r--spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb29
-rw-r--r--spec/models/concerns/has_timelogs_report_spec.rb55
-rw-r--r--spec/models/timelog_spec.rb56
-rw-r--r--spec/policies/global_policy_spec.rb24
-rw-r--r--spec/policies/group_policy_spec.rb50
-rw-r--r--spec/policies/project_policy_spec.rb50
-rw-r--r--spec/requests/api/graphql/group/timelogs_spec.rb49
-rw-r--r--spec/requests/git_http_spec.rb61
-rw-r--r--spec/requests/lfs_http_spec.rb4
-rw-r--r--spec/tasks/gitlab/x509/update_rake_spec.rb20
115 files changed, 1354 insertions, 554 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index a0ac141fefd..f483d5261c5 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-7807bc4670c11dad1d2981e6d1090fe0ddd9e088
+37934de32d680c4166ec35dcd47b59fa0cc8c397
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index 46b81d815a2..d8b698973a4 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-2.11.0
+2.12.0
diff --git a/Gemfile.lock b/Gemfile.lock
index 56310ec6b89..f30b1cde549 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -146,7 +146,7 @@ GEM
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
- bindata (2.4.8)
+ bindata (2.4.10)
binding_ninja (0.2.3)
bootsnap (1.4.6)
msgpack (~> 1.0)
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 9bf26e5a182..a7fcce02ab3 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -195,6 +195,7 @@ export default {
'var',
],
ALLOWED_ATTR: ['class', 'style', 'href', 'src'],
+ ALLOW_DATA_ATTR: false,
});
},
},
diff --git a/app/controllers/jira_connect/app_descriptor_controller.rb b/app/controllers/jira_connect/app_descriptor_controller.rb
index a6415daef68..0de42ad2452 100644
--- a/app/controllers/jira_connect/app_descriptor_controller.rb
+++ b/app/controllers/jira_connect/app_descriptor_controller.rb
@@ -31,6 +31,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
scopes: %w(READ WRITE DELETE),
apiVersion: 1,
apiMigrations: {
+ 'context-qsh': true,
gdpr: true
}
}
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index 857f36e3833..ddf70c1892a 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -14,8 +14,9 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
if pre_auth.authorizable?
if skip_authorization? || matching_token?
auth = authorization.authorize
+ parsed_redirect_uri = URI.parse(auth.redirect_uri)
session.delete(:user_return_to)
- redirect_to auth.redirect_uri
+ render "doorkeeper/authorizations/redirect", locals: { redirect_uri: parsed_redirect_uri }, layout: false
else
render "doorkeeper/authorizations/new"
end
diff --git a/app/graphql/resolvers/timelog_resolver.rb b/app/graphql/resolvers/timelog_resolver.rb
index aebd04259ee..8ac30c4cb5d 100644
--- a/app/graphql/resolvers/timelog_resolver.rb
+++ b/app/graphql/resolvers/timelog_resolver.rb
@@ -7,106 +7,88 @@ module Resolvers
type ::Types::TimelogType.connection_type, null: false
argument :start_date, Types::TimeType,
- required: false,
- description: 'List time logs within a date range where the logged date is equal to or after startDate.'
+ required: false,
+ description: 'List time logs within a date range where the logged date is equal to or after startDate.'
argument :end_date, Types::TimeType,
- required: false,
- description: 'List time logs within a date range where the logged date is equal to or before endDate.'
+ required: false,
+ description: 'List time logs within a date range where the logged date is equal to or before endDate.'
argument :start_time, Types::TimeType,
- required: false,
- description: 'List time-logs within a time range where the logged time is equal to or after startTime.'
+ required: false,
+ description: 'List time-logs within a time range where the logged time is equal to or after startTime.'
argument :end_time, Types::TimeType,
- required: false,
- description: 'List time-logs within a time range where the logged time is equal to or before endTime.'
+ required: false,
+ description: 'List time-logs within a time range where the logged time is equal to or before endTime.'
def resolve_with_lookahead(**args)
- return Timelog.none unless timelogs_available_for_user?
+ build_timelogs
- validate_params_presence!(args)
- transformed_args = transform_args(args)
- validate_time_difference!(transformed_args)
+ if args.any?
+ validate_args!(args)
+ build_parsed_args(args)
+ validate_time_difference!
+ apply_time_filter
+ end
- find_timelogs(transformed_args)
+ apply_lookahead(timelogs)
end
private
+ attr_reader :parsed_args, :timelogs
+
def preloads
{
note: [:note]
}
end
- def find_timelogs(args)
- apply_lookahead(group.timelogs(args[:start_time], args[:end_time]))
+ def validate_args!(args)
+ if args[:start_time] && args[:start_date]
+ raise_argument_error('Provide either a start date or time, but not both')
+ elsif args[:end_time] && args[:end_date]
+ raise_argument_error('Provide either an end date or time, but not both')
+ end
end
- def timelogs_available_for_user?
- group&.user_can_access_group_timelogs?(context[:current_user])
- end
+ def build_parsed_args(args)
+ if times_provided?(args)
+ @parsed_args = args
+ else
+ @parsed_args = args.except(:start_date, :end_date)
- def validate_params_presence!(args)
- message = case time_params_count(args)
- when 0
- 'Start and End arguments must be present'
- when 1
- 'Both Start and End arguments must be present'
- when 2
- validate_duplicated_args(args)
- when 3 || 4
- 'Only Time or Date arguments must be present'
- end
-
- raise_argument_error(message) if message
+ @parsed_args[:start_time] = args[:start_date].beginning_of_day if args[:start_date]
+ @parsed_args[:end_time] = args[:end_date].end_of_day if args[:end_date]
+ end
end
- def validate_time_difference!(args)
- message = if args[:end_time] < args[:start_time]
- 'Start argument must be before End argument'
- elsif args[:end_time] - args[:start_time] > 60.days
- 'The time range period cannot contain more than 60 days'
- end
-
- raise_argument_error(message) if message
+ def times_provided?(args)
+ args[:start_time] && args[:end_time]
end
- def transform_args(args)
- return args if args.keys == [:start_time, :end_time]
+ def validate_time_difference!
+ return unless end_time_before_start_time?
- time_args = args.except(:start_date, :end_date)
-
- if time_args.empty?
- time_args[:start_time] = args[:start_date].beginning_of_day
- time_args[:end_time] = args[:end_date].end_of_day
- elsif time_args.key?(:start_time)
- time_args[:end_time] = args[:end_date].end_of_day
- elsif time_args.key?(:end_time)
- time_args[:start_time] = args[:start_date].beginning_of_day
- end
+ raise_argument_error('Start argument must be before End argument')
+ end
- time_args
+ def end_time_before_start_time?
+ times_provided?(parsed_args) && parsed_args[:end_time] < parsed_args[:start_time]
end
- def time_params_count(args)
- [:start_time, :end_time, :start_date, :end_date].count { |param| args.key?(param) }
+ def build_timelogs
+ @timelogs = Timelog.in_group(object)
end
- def validate_duplicated_args(args)
- if args.key?(:start_time) && args.key?(:start_date) ||
- args.key?(:end_time) && args.key?(:end_date)
- 'Both Start and End arguments must be present'
- end
+ def apply_time_filter
+ @timelogs = timelogs.at_or_after(parsed_args[:start_time]) if parsed_args[:start_time]
+ @timelogs = timelogs.at_or_before(parsed_args[:end_time]) if parsed_args[:end_time]
end
def raise_argument_error(message)
raise Gitlab::Graphql::Errors::ArgumentError, message
end
-
- def group
- @group ||= object.respond_to?(:sync) ? object.sync : object
- end
end
end
diff --git a/app/graphql/types/timelog_type.rb b/app/graphql/types/timelog_type.rb
index 99a619f1b1d..925a522629e 100644
--- a/app/graphql/types/timelog_type.rb
+++ b/app/graphql/types/timelog_type.rb
@@ -4,7 +4,7 @@ module Types
class TimelogType < BaseObject
graphql_name 'Timelog'
- authorize :read_group_timelogs
+ authorize :read_issue
field :spent_at,
Types::TimeType,
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 05a55a09271..7525481047e 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -118,6 +118,7 @@ module MarkupHelper
def markup(file_name, text, context = {})
context[:project] ||= @project
+ context[:text_source] ||= :blob
html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
prepare_for_rendering(html, context)
end
diff --git a/app/models/concerns/has_timelogs_report.rb b/app/models/concerns/has_timelogs_report.rb
deleted file mode 100644
index 3af063438bf..00000000000
--- a/app/models/concerns/has_timelogs_report.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module HasTimelogsReport
- extend ActiveSupport::Concern
- include Gitlab::Utils::StrongMemoize
-
- def timelogs(start_time, end_time)
- strong_memoize(:timelogs) { timelogs_for(start_time, end_time) }
- end
-
- def user_can_access_group_timelogs?(current_user)
- Ability.allowed?(current_user, :read_group_timelogs, self)
- end
-
- private
-
- def timelogs_for(start_time, end_time)
- Timelog.between_times(start_time, end_time).in_group(self)
- end
-end
diff --git a/app/models/group.rb b/app/models/group.rb
index 5c991f74208..bbae40f4442 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -16,7 +16,6 @@ class Group < Namespace
include Gitlab::Utils::StrongMemoize
include GroupAPICompatibility
include EachBatch
- include HasTimelogsReport
include BulkMemberAccessLoad
has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 61e89125b5f..c7bc31cde5d 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -36,6 +36,10 @@ class GroupMember < Member
Gitlab::Access.sym_options_with_owner
end
+ def self.pluck_user_ids
+ pluck(:user_id)
+ end
+
def group
source
end
diff --git a/app/models/project.rb b/app/models/project.rb
index fb6e6437964..53054e5304f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -900,6 +900,10 @@ class Project < ApplicationRecord
alias_method :ancestors, :ancestors_upto
+ def ancestors_upto_ids(...)
+ ancestors_upto(...).pluck(:id)
+ end
+
def emails_disabled?
strong_memoize(:emails_disabled) do
# disabling in the namespace overrides the project setting
diff --git a/app/models/timelog.rb b/app/models/timelog.rb
index bd543526685..96fd485b797 100644
--- a/app/models/timelog.rb
+++ b/app/models/timelog.rb
@@ -18,8 +18,12 @@ class Timelog < ApplicationRecord
joins(:project).where(projects: { namespace: group.self_and_descendants })
end
- scope :between_times, -> (start_time, end_time) do
- where('spent_at BETWEEN ? AND ?', start_time, end_time)
+ scope :at_or_after, -> (start_time) do
+ where('spent_at >= ?', start_time)
+ end
+
+ scope :at_or_before, -> (end_time) do
+ where('spent_at <= ?', end_time)
end
def issuable
diff --git a/app/policies/concerns/policy_actor.rb b/app/policies/concerns/policy_actor.rb
index cd19b46ad6c..08a26da6673 100644
--- a/app/policies/concerns/policy_actor.rb
+++ b/app/policies/concerns/policy_actor.rb
@@ -80,6 +80,10 @@ module PolicyActor
def can_read_all_resources?
false
end
+
+ def password_expired?
+ false
+ end
end
PolicyActor.prepend_mod_with('PolicyActor')
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 85263ec7c87..73757891cd6 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -15,6 +15,10 @@ class GlobalPolicy < BasePolicy
@user&.required_terms_not_accepted?
end
+ condition(:password_expired, scope: :user) do
+ @user&.password_expired?
+ end
+
condition(:project_bot, scope: :user) { @user&.project_bot? }
condition(:migration_bot, scope: :user) { @user&.migration_bot? }
@@ -73,6 +77,12 @@ class GlobalPolicy < BasePolicy
prevent :access_git
end
+ rule { password_expired }.policy do
+ prevent :access_api
+ prevent :access_git
+ prevent :use_slash_commands
+ end
+
rule { can_create_group }.policy do
enable :create_group
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 821fabec266..dc8ecfa4333 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -131,7 +131,6 @@ class GroupPolicy < BasePolicy
enable :read_prometheus
enable :read_package
enable :read_package_settings
- enable :read_group_timelogs
end
rule { maintainer }.policy do
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 1ce19511bef..5e9633d1c8a 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -263,7 +263,6 @@ class ProjectPolicy < BasePolicy
enable :read_confidential_issues
enable :read_package
enable :read_product_analytics
- enable :read_group_timelogs
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
diff --git a/app/policies/timelog_policy.rb b/app/policies/timelog_policy.rb
index 0598817d4e0..f71c4204639 100644
--- a/app/policies/timelog_policy.rb
+++ b/app/policies/timelog_policy.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class TimelogPolicy < BasePolicy
- delegate { @subject.issuable.resource_parent }
+ delegate { @subject.issuable }
end
diff --git a/app/serializers/member_entity.rb b/app/serializers/member_entity.rb
index 7559a03bd3b..5100a41638e 100644
--- a/app/serializers/member_entity.rb
+++ b/app/serializers/member_entity.rb
@@ -40,7 +40,9 @@ class MemberEntity < Grape::Entity
expose :valid_level_roles, as: :valid_roles
- expose :user, if: -> (member) { member.user.present? }, using: MemberUserEntity
+ expose :user, if: -> (member) { member.user.present? } do |member, options|
+ MemberUserEntity.represent(member.user, source: options[:source])
+ end
expose :invite, if: -> (member) { member.invite? } do
expose :email do |member|
diff --git a/app/views/doorkeeper/authorizations/redirect.html.haml b/app/views/doorkeeper/authorizations/redirect.html.haml
new file mode 100644
index 00000000000..9580f33c88a
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/redirect.html.haml
@@ -0,0 +1,8 @@
+%h3.page-title= _("Redirecting")
+
+%div
+ %a{ :href => redirect_uri } Click here to redirect to #{redirect_uri}
+
+= javascript_tag do
+ :plain
+ window.location= "#{redirect_uri}";
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 42bb8117766..6a626d445ee 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -5,7 +5,10 @@
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
-#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json, code_quality_walkthrough: params[:code_quality_walkthrough]),
+- list_url = project_pipelines_path(@project, format: :json, code_quality_walkthrough: params[:code_quality_walkthrough])
+- add_page_startup_api_call list_url
+
+#pipelines-list-vue{ data: { endpoint: list_url,
project_id: @project.id,
params: params.to_json,
"artifacts-endpoint" => downloadable_artifacts_project_pipeline_path(@project, artifacts_endpoint_placeholder, format: :json),
diff --git a/config/feature_flags/development/ci_fix_commit_status_retried.yml b/config/feature_flags/development/ci_fix_commit_status_retried.yml
index 85b1836b065..56400c861cd 100644
--- a/config/feature_flags/development/ci_fix_commit_status_retried.yml
+++ b/config/feature_flags/development/ci_fix_commit_status_retried.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321631
milestone: '13.9'
type: development
group: group::pipeline authoring
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/group_level_protected_environments.yml b/config/feature_flags/development/group_level_protected_environments.yml
new file mode 100644
index 00000000000..598513d4283
--- /dev/null
+++ b/config/feature_flags/development/group_level_protected_environments.yml
@@ -0,0 +1,8 @@
+---
+name: group_level_protected_environments
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61575
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331085
+milestone: '14.0'
+type: development
+group: group::release
+default_enabled: false
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index e8479bc6aa4..61e357808d9 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -18,6 +18,7 @@ unless Gitlab::Runtime.sidekiq?
data[:db_duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:db)) if data[:db]
data[:view_duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:view)) if data[:view]
data[:duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:duration)) if data[:duration]
+ data[:location] = Gitlab::Utils.removes_sensitive_data_from_url(data[:location]) if data[:location]
# Remove empty hashes to prevent type mismatches
# These are set to empty hashes in Lograge's ActionCable subscriber
diff --git a/db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb b/db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb
new file mode 100644
index 00000000000..2da84301a72
--- /dev/null
+++ b/db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class ScheduleUpdateUsersWhereTwoFactorAuthRequiredFromGroup < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ MIGRATION = 'UpdateUsersWhereTwoFactorAuthRequiredFromGroup'
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 10_000
+ INDEX_NAME = 'index_users_require_two_factor_authentication_from_group_false'
+
+ disable_ddl_transaction!
+
+ class User < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'users'
+ end
+
+ def up
+ add_concurrent_index :users,
+ :require_two_factor_authentication_from_group,
+ where: 'require_two_factor_authentication_from_group = FALSE',
+ name: INDEX_NAME
+
+ relation = User.where(require_two_factor_authentication_from_group: false)
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ relation, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+
+ def down
+ remove_concurrent_index_by_name :users, INDEX_NAME
+ end
+end
diff --git a/db/migrate/20210520102039_group_protected_environments_add_column.rb b/db/migrate/20210520102039_group_protected_environments_add_column.rb
new file mode 100644
index 00000000000..642e22c5f48
--- /dev/null
+++ b/db/migrate/20210520102039_group_protected_environments_add_column.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class GroupProtectedEnvironmentsAddColumn < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ def up
+ add_column :protected_environments, :group_id, :bigint
+ change_column_null :protected_environments, :project_id, true
+ end
+
+ def down
+ change_column_null :protected_environments, :project_id, false
+ remove_column :protected_environments, :group_id
+ end
+end
diff --git a/db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb b/db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb
new file mode 100644
index 00000000000..611619e496c
--- /dev/null
+++ b/db/migrate/20210601080039_group_protected_environments_add_index_and_constraint.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class GroupProtectedEnvironmentsAddIndexAndConstraint < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ INDEX_NAME = 'index_protected_environments_on_group_id_and_name'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :protected_environments, [:group_id, :name], unique: true,
+ name: INDEX_NAME, where: 'group_id IS NOT NULL'
+ add_concurrent_foreign_key :protected_environments, :namespaces, column: :group_id, on_delete: :cascade
+
+ add_check_constraint :protected_environments,
+ "((project_id IS NULL) != (group_id IS NULL))",
+ :protected_environments_project_or_group_existence
+ end
+
+ def down
+ remove_group_protected_environments!
+
+ remove_check_constraint :protected_environments, :protected_environments_project_or_group_existence
+ remove_foreign_key_if_exists :protected_environments, column: :group_id
+ remove_concurrent_index_by_name :protected_environments, name: INDEX_NAME
+ end
+
+ private
+
+ def remove_group_protected_environments!
+ execute <<-SQL
+ DELETE FROM protected_environments WHERE group_id IS NOT NULL
+ SQL
+ end
+end
diff --git a/db/post_migrate/20210519220019_backfill_escalation_policies_for_oncall_schedules.rb b/db/post_migrate/20210519220019_backfill_escalation_policies_for_oncall_schedules.rb
new file mode 100644
index 00000000000..f972815cf67
--- /dev/null
+++ b/db/post_migrate/20210519220019_backfill_escalation_policies_for_oncall_schedules.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+class BackfillEscalationPoliciesForOncallSchedules < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Creates a single new escalation policy for projects which have
+ # existing on-call schedules. Only one schedule is expected
+ # per project, but it is possible to have multiple.
+ #
+ # An escalation rule is created for each existing schedule,
+ # configured to immediately notify the schedule of an incoming
+ # alert payload unless the alert has already been acknowledged.
+ # For projects with multiple schedules, the name of the first saved
+ # schedule will be used for the policy's description.
+ #
+ # Skips projects which already have escalation policies & schedules.
+ #
+ # EX)
+ # For these existing records:
+ # Project #3
+ # IncidentManagement::OncallSchedules #13
+ # project_id: 3
+ # name: 'Awesome Schedule'
+ # description: null
+ # IncidentManagement::OncallSchedules #14
+ # project_id: 3
+ # name: '2ndary sched'
+ # description: 'Backup on-call'
+ #
+ # These will be inserted:
+ # EscalationPolicy #1
+ # project_id: 3
+ # name: 'On-call Escalation Policy'
+ # description: 'Immediately notify Awesome Schedule'
+ # EscalationRule #1
+ # policy_id: 1,
+ # oncall_schedule_id: 13
+ # status: 1 # Acknowledged status
+ # elapsed_time_seconds: 0
+ # EscalationRule #2
+ # policy_id: 1,
+ # oncall_schedule_id: 14
+ # status: 1 # Acknowledged status
+ # elapsed_time_seconds: 0
+ def up
+ ApplicationRecord.connection.exec_query(<<~SQL.squish)
+ WITH new_escalation_policies AS (
+ INSERT INTO incident_management_escalation_policies (
+ project_id,
+ name,
+ description
+ )
+ SELECT
+ DISTINCT ON (project_id) project_id,
+ 'On-call Escalation Policy',
+ CONCAT('Immediately notify ', name)
+ FROM incident_management_oncall_schedules
+ WHERE project_id NOT IN (
+ SELECT DISTINCT project_id
+ FROM incident_management_escalation_policies
+ )
+ ORDER BY project_id, id
+ RETURNING id, project_id
+ )
+
+ INSERT INTO incident_management_escalation_rules (
+ policy_id,
+ oncall_schedule_id,
+ status,
+ elapsed_time_seconds
+ )
+ SELECT
+ new_escalation_policies.id,
+ incident_management_oncall_schedules.id,
+ 1,
+ 0
+ FROM new_escalation_policies
+ INNER JOIN incident_management_oncall_schedules
+ ON new_escalation_policies.project_id = incident_management_oncall_schedules.project_id
+ SQL
+ end
+
+ # There is no way to distinguish between policies created
+ # via the backfill or as a result of a user creating a new
+ # on-call schedule.
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema_migrations/20210519154058 b/db/schema_migrations/20210519154058
new file mode 100644
index 00000000000..9bd277e92db
--- /dev/null
+++ b/db/schema_migrations/20210519154058
@@ -0,0 +1 @@
+bdd82fc5cb2bbb322125c153c741002725853e23cd0ae0edbfd80563a4a87f2f \ No newline at end of file
diff --git a/db/schema_migrations/20210519220019 b/db/schema_migrations/20210519220019
new file mode 100644
index 00000000000..c0578586e62
--- /dev/null
+++ b/db/schema_migrations/20210519220019
@@ -0,0 +1 @@
+6c687ffd41f242dcd0ecf1ff82652aba79130d2d54016729a817dafa0bac6184 \ No newline at end of file
diff --git a/db/schema_migrations/20210520102039 b/db/schema_migrations/20210520102039
new file mode 100644
index 00000000000..a850a5b07ca
--- /dev/null
+++ b/db/schema_migrations/20210520102039
@@ -0,0 +1 @@
+88d2c1507503de626dfdb3f2f0eaf0f51fad5fc2279fd147d901c5dcc7ae91eb \ No newline at end of file
diff --git a/db/schema_migrations/20210601080039 b/db/schema_migrations/20210601080039
new file mode 100644
index 00000000000..91d517058fe
--- /dev/null
+++ b/db/schema_migrations/20210601080039
@@ -0,0 +1 @@
+2c5c0756757a181cf8bf7968de5184664004a82c093ae3fc14c5d6931a1ab44f \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 40eec742ac1..8f66b4a719a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -17168,10 +17168,12 @@ ALTER SEQUENCE protected_environment_deploy_access_levels_id_seq OWNED BY protec
CREATE TABLE protected_environments (
id integer NOT NULL,
- project_id integer NOT NULL,
+ project_id integer,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
- name character varying NOT NULL
+ name character varying NOT NULL,
+ group_id bigint,
+ CONSTRAINT protected_environments_project_or_group_existence CHECK (((project_id IS NULL) <> (group_id IS NULL)))
);
CREATE SEQUENCE protected_environments_id_seq
@@ -24285,6 +24287,8 @@ CREATE INDEX index_protected_environment_deploy_access_levels_on_group_id ON pro
CREATE INDEX index_protected_environment_deploy_access_levels_on_user_id ON protected_environment_deploy_access_levels USING btree (user_id);
+CREATE UNIQUE INDEX index_protected_environments_on_group_id_and_name ON protected_environments USING btree (group_id, name) WHERE (group_id IS NOT NULL);
+
CREATE INDEX index_protected_environments_on_project_id ON protected_environments USING btree (project_id);
CREATE UNIQUE INDEX index_protected_environments_on_project_id_and_name ON protected_environments USING btree (project_id, name);
@@ -24767,6 +24771,8 @@ CREATE INDEX index_users_ops_dashboard_projects_on_project_id ON users_ops_dashb
CREATE UNIQUE INDEX index_users_ops_dashboard_projects_on_user_id_and_project_id ON users_ops_dashboard_projects USING btree (user_id, project_id);
+CREATE INDEX index_users_require_two_factor_authentication_from_group_false ON users USING btree (require_two_factor_authentication_from_group) WHERE (require_two_factor_authentication_from_group = false);
+
CREATE INDEX index_users_security_dashboard_projects_on_user_id ON users_security_dashboard_projects USING btree (user_id);
CREATE INDEX index_users_star_projects_on_project_id ON users_star_projects USING btree (project_id);
@@ -25718,6 +25724,9 @@ ALTER TABLE ONLY issues
ALTER TABLE ONLY epics
ADD CONSTRAINT fk_9d480c64b2 FOREIGN KEY (start_date_sourcing_epic_id) REFERENCES epics(id) ON DELETE SET NULL;
+ALTER TABLE ONLY protected_environments
+ ADD CONSTRAINT fk_9e112565b7 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY alert_management_alerts
ADD CONSTRAINT fk_9e49e5c2b7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
diff --git a/doc/user/admin_area/settings/import_export_rate_limits.md b/doc/user/admin_area/settings/import_export_rate_limits.md
index c6b965c18d3..b62e6eb0c3d 100644
--- a/doc/user/admin_area/settings/import_export_rate_limits.md
+++ b/doc/user/admin_area/settings/import_export_rate_limits.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Project/Group Import/Export rate limits
+# Project/group import/export rate limits **(FREE SELF)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35728) in GitLab 13.2.
diff --git a/doc/user/group/settings/import_export.md b/doc/user/group/settings/import_export.md
index 5d375e2abd9..f18186711b5 100644
--- a/doc/user/group/settings/import_export.md
+++ b/doc/user/group/settings/import_export.md
@@ -4,7 +4,7 @@ stage: Manage
group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Group Import/Export
+# Group import/export **(FREE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2888) in GitLab 13.0 as an experimental feature. May change in future releases.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index d73e377e771..1b9a4629cb8 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -80,6 +80,7 @@ The following table lists project permissions available for each role:
| Label issues | | ✓ | ✓ | ✓ | ✓ |
| Set issue weight | | ✓ | ✓ | ✓ | ✓ |
| [Set issue estimate and record time spent](project/time_tracking.md) | | ✓ | ✓ | ✓ | ✓ |
+| View a time tracking report | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Lock issue threads | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage linked issues | | ✓ | ✓ | ✓ | ✓ |
diff --git a/doc/user/project/import/bitbucket.md b/doc/user/project/import/bitbucket.md
index e77932c6427..802eb3efc51 100644
--- a/doc/user/project/import/bitbucket.md
+++ b/doc/user/project/import/bitbucket.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import your project from Bitbucket Cloud to GitLab
+# Import your project from Bitbucket Cloud to GitLab **(FREE)**
NOTE:
The Bitbucket Cloud importer works only with Bitbucket.org, not with Bitbucket
diff --git a/doc/user/project/import/bitbucket_server.md b/doc/user/project/import/bitbucket_server.md
index 1e79107d76f..963b9f524ff 100644
--- a/doc/user/project/import/bitbucket_server.md
+++ b/doc/user/project/import/bitbucket_server.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import your project from Bitbucket Server to GitLab
+# Import your project from Bitbucket Server to GitLab **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/20164) in GitLab 11.2.
diff --git a/doc/user/project/import/clearcase.md b/doc/user/project/import/clearcase.md
index 7e07ca6f865..27a84476590 100644
--- a/doc/user/project/import/clearcase.md
+++ b/doc/user/project/import/clearcase.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Migrating from ClearCase
+# Migrating from ClearCase **(FREE)**
[ClearCase](https://www.ibm.com/products/rational-clearcase) is a set of
tools developed by IBM which also include a centralized version control system
diff --git a/doc/user/project/import/cvs.md b/doc/user/project/import/cvs.md
index 61d4d29aa4d..55c5feff1f0 100644
--- a/doc/user/project/import/cvs.md
+++ b/doc/user/project/import/cvs.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Migrating from CVS
+# Migrating from CVS **(FREE)**
[CVS](https://savannah.nongnu.org/projects/cvs) is an old centralized version
control system similar to [SVN](svn.md).
diff --git a/doc/user/project/import/fogbugz.md b/doc/user/project/import/fogbugz.md
index 09505d94a8c..d3d77f16200 100644
--- a/doc/user/project/import/fogbugz.md
+++ b/doc/user/project/import/fogbugz.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import your project from FogBugz to GitLab
+# Import your project from FogBugz to GitLab **(FREE)**
It only takes a few simple steps to import your project from FogBugz.
The importer imports all your cases and comments with original case
diff --git a/doc/user/project/import/gitea.md b/doc/user/project/import/gitea.md
index 41141902468..9364ac4f954 100644
--- a/doc/user/project/import/gitea.md
+++ b/doc/user/project/import/gitea.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import your project from Gitea to GitLab
+# Import your project from Gitea to GitLab **(FREE)**
Import your projects from Gitea to GitLab with minimal effort.
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index c8b973b673e..99b3e1acdcf 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import your project from GitHub to GitLab
+# Import your project from GitHub to GitLab **(FREE)**
Using the importer, you can import your GitHub repositories to GitLab.com or to
your self-managed GitLab instance.
diff --git a/doc/user/project/import/gitlab_com.md b/doc/user/project/import/gitlab_com.md
index 9e63b1cb617..f7eb5e43a79 100644
--- a/doc/user/project/import/gitlab_com.md
+++ b/doc/user/project/import/gitlab_com.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Project importing from GitLab.com to your private GitLab instance
+# Project importing from GitLab.com to your private GitLab instance **(FREE)**
You can import your existing GitLab.com projects to your GitLab instance, but keep in
mind that it is possible only if GitLab.com integration is enabled on your GitLab instance.
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 3728a486070..05fd04f6e48 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Migrate projects to a GitLab instance
+# Migrate projects to a GitLab instance **(FREE)**
See these documents to migrate to GitLab:
diff --git a/doc/user/project/import/manifest.md b/doc/user/project/import/manifest.md
index 94eba319a17..131732d2bae 100644
--- a/doc/user/project/import/manifest.md
+++ b/doc/user/project/import/manifest.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import multiple repositories by uploading a manifest file
+# Import multiple repositories by uploading a manifest file **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28811) in GitLab 11.2.
diff --git a/doc/user/project/import/perforce.md b/doc/user/project/import/perforce.md
index 8040eb07c93..f3843396b79 100644
--- a/doc/user/project/import/perforce.md
+++ b/doc/user/project/import/perforce.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Migrating from Perforce Helix
+# Migrating from Perforce Helix **(FREE)**
[Perforce Helix](https://www.perforce.com/) provides a set of tools which also
include a centralized, proprietary version control system similar to Git.
diff --git a/doc/user/project/import/phabricator.md b/doc/user/project/import/phabricator.md
index 30a63a72cf9..6a1370f3301 100644
--- a/doc/user/project/import/phabricator.md
+++ b/doc/user/project/import/phabricator.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import Phabricator tasks into a GitLab project
+# Import Phabricator tasks into a GitLab project **(FREE SELF)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/60562) in GitLab 12.0.
diff --git a/doc/user/project/import/repo_by_url.md b/doc/user/project/import/repo_by_url.md
index 3ff612c51a7..e504f3678a7 100644
--- a/doc/user/project/import/repo_by_url.md
+++ b/doc/user/project/import/repo_by_url.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Import project from repository by URL
+# Import project from repository by URL **(FREE)**
You can import your existing repositories by providing the Git URL:
diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md
index e39976e00f6..78f683ab675 100644
--- a/doc/user/project/import/svn.md
+++ b/doc/user/project/import/svn.md
@@ -5,7 +5,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Migrating from SVN to GitLab
+# Migrating from SVN to GitLab **(FREE)**
Subversion (SVN) is a central version control system (VCS) while
Git is a distributed version control system. There are some major differences
diff --git a/doc/user/project/import/tfvc.md b/doc/user/project/import/tfvc.md
index 705df686fe0..910be690d0b 100644
--- a/doc/user/project/import/tfvc.md
+++ b/doc/user/project/import/tfvc.md
@@ -1,11 +1,11 @@
---
-stage: none
-group: unassigned
+stage: Manage
+group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
type: concepts
---
-# Migrate from TFVC to Git
+# Migrate from TFVC to Git **(FREE)**
Team Foundation Server (TFS), renamed [Azure DevOps Server](https://azure.microsoft.com/en-us/services/devops/server/)
in 2019, is a set of tools developed by Microsoft which also includes
diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md
index de7a36a4886..02a4f6a4384 100644
--- a/doc/user/project/issues/csv_import.md
+++ b/doc/user/project/issues/csv_import.md
@@ -4,7 +4,7 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Importing issues from CSV
+# Importing issues from CSV **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23532) in GitLab 11.7.
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index ebc77bb5b61..2d0eaf75ac4 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, howto
---
-# Project import/export
+# Project import/export **(FREE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/3050) in GitLab 8.9.
> - From GitLab 10.0, administrators can disable the project export option on the GitLab instance.
diff --git a/doc/user/project/time_tracking.md b/doc/user/project/time_tracking.md
index df76c4682f3..b2eb6c15a81 100644
--- a/doc/user/project/time_tracking.md
+++ b/doc/user/project/time_tracking.md
@@ -82,6 +82,10 @@ To remove all the time spent at once, use `/remove_time_spent`.
You can view a breakdown of time spent on an issue or merge request.
+Prerequisites:
+
+- A minimum of [Reporter](../permissions.md#project-members-permissions) access to a private project in GitLab.
+
To view a time tracking report, go to an issue or a merge request and select **Time tracking report**
in the right sidebar.
diff --git a/lib/banzai/filter/absolute_link_filter.rb b/lib/banzai/filter/absolute_link_filter.rb
index a9bdb004c4b..cc7bf3ed556 100644
--- a/lib/banzai/filter/absolute_link_filter.rb
+++ b/lib/banzai/filter/absolute_link_filter.rb
@@ -6,10 +6,13 @@ module Banzai
module Filter
# HTML filter that converts relative urls into absolute ones.
class AbsoluteLinkFilter < HTML::Pipeline::Filter
+ CSS = 'a.gfm'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
return doc unless context[:only_path] == false
- doc.search('a.gfm').each do |el|
+ doc.xpath(XPATH).each do |el|
process_link_attr el.attribute('href')
end
diff --git a/lib/banzai/filter/ascii_doc_post_processing_filter.rb b/lib/banzai/filter/ascii_doc_post_processing_filter.rb
index 09f0fd7df45..83c729e13b5 100644
--- a/lib/banzai/filter/ascii_doc_post_processing_filter.rb
+++ b/lib/banzai/filter/ascii_doc_post_processing_filter.rb
@@ -3,14 +3,20 @@
module Banzai
module Filter
class AsciiDocPostProcessingFilter < HTML::Pipeline::Filter
+ CSS_MATH = '[data-math-style]'
+ XPATH_MATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_MATH).freeze
+ CSS_MERM = '[data-mermaid-style]'
+ XPATH_MERM = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_MERM).freeze
+
def call
- doc.search('[data-math-style]').each do |node|
+ doc.xpath(XPATH_MATH).each do |node|
node.set_attribute('class', 'code math js-render-math')
end
- doc.search('[data-mermaid-style]').each do |node|
+ doc.xpath(XPATH_MERM).each do |node|
node.set_attribute('class', 'js-render-mermaid')
end
+
doc
end
end
diff --git a/lib/banzai/filter/base_relative_link_filter.rb b/lib/banzai/filter/base_relative_link_filter.rb
index fba41496cad..b2eaeb69f61 100644
--- a/lib/banzai/filter/base_relative_link_filter.rb
+++ b/lib/banzai/filter/base_relative_link_filter.rb
@@ -7,6 +7,9 @@ module Banzai
class BaseRelativeLinkFilter < HTML::Pipeline::Filter
include Gitlab::Utils::StrongMemoize
+ CSS = 'a:not(.gfm), img:not(.gfm), video:not(.gfm), audio:not(.gfm)'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
protected
def linkable_attributes
@@ -35,7 +38,7 @@ module Banzai
def fetch_linkable_attributes
attrs = []
- attrs += doc.search('a:not(.gfm), img:not(.gfm), video:not(.gfm), audio:not(.gfm)').flat_map do |el|
+ attrs += doc.xpath(XPATH).flat_map do |el|
[el.attribute('href'), el.attribute('src'), el.attribute('data-src')]
end
diff --git a/lib/banzai/filter/color_filter.rb b/lib/banzai/filter/color_filter.rb
index 0aca7441638..58e9b8cdba1 100644
--- a/lib/banzai/filter/color_filter.rb
+++ b/lib/banzai/filter/color_filter.rb
@@ -7,8 +7,11 @@ module Banzai
class ColorFilter < HTML::Pipeline::Filter
COLOR_CHIP_CLASS = 'gfm-color_chip'
+ CSS = 'code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.css('code').each do |node|
+ doc.xpath(XPATH).each do |node|
color = ColorParser.parse(node.content)
node << color_chip(color) if color
end
diff --git a/lib/banzai/filter/custom_emoji_filter.rb b/lib/banzai/filter/custom_emoji_filter.rb
index 3171231dc9b..a5f1a22c483 100644
--- a/lib/banzai/filter/custom_emoji_filter.rb
+++ b/lib/banzai/filter/custom_emoji_filter.rb
@@ -11,7 +11,7 @@ module Banzai
return doc unless context[:project]
return doc unless Feature.enabled?(:custom_emoji, context[:project])
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
content = node.to_html
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index 8952a3ff6b4..9d24bf028b6 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -11,7 +11,7 @@ module Banzai
IGNORE_UNICODE_EMOJIS = %w(™ © ®).freeze
def call
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
content = node.to_html
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb
index 5474242e03c..0f856dc0eb9 100644
--- a/lib/banzai/filter/footnote_filter.rb
+++ b/lib/banzai/filter/footnote_filter.rb
@@ -23,17 +23,23 @@ module Banzai
FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}\d+\z/.freeze
FOOTNOTE_START_NUMBER = 1
+ CSS_SECTION = "ol > li[id=#{FOOTNOTE_ID_PREFIX}#{FOOTNOTE_START_NUMBER}]"
+ XPATH_SECTION = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION).freeze
+ CSS_FOOTNOTE = 'sup > a[id]'
+ XPATH_FOOTNOTE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_FOOTNOTE).freeze
+
def call
- return doc unless first_footnote = doc.at_css("ol > li[id=#{fn_id(FOOTNOTE_START_NUMBER)}]")
+ return doc unless first_footnote = doc.at_xpath(XPATH_SECTION)
# Sanitization stripped off the section wrapper - add it back in
first_footnote.parent.wrap('<section class="footnotes">')
rand_suffix = "-#{random_number}"
modified_footnotes = {}
- doc.css('sup > a[id]').each do |link_node|
+ doc.xpath(XPATH_FOOTNOTE).each do |link_node|
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
- footnote_node = doc.at_css("li[id=#{fn_id(ref_num)}]")
+ node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath("li[id=#{fn_id(ref_num)}]")
+ footnote_node = doc.at_xpath(node_xpath)
if INTEGER_PATTERN.match?(ref_num) && (footnote_node || modified_footnotes[ref_num])
link_node[:href] += rand_suffix
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 6de9f2b86f6..0548d5a9997 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -60,7 +60,7 @@ module Banzai
IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
def call
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
next unless node.content =~ TAGS_PATTERN
diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb
index d8b9eb29cf5..916c135b777 100644
--- a/lib/banzai/filter/image_lazy_load_filter.rb
+++ b/lib/banzai/filter/image_lazy_load_filter.rb
@@ -6,8 +6,11 @@ module Banzai
# HTML filter that moves the value of image `src` attributes to `data-src`
# so they can be lazy loaded.
class ImageLazyLoadFilter < HTML::Pipeline::Filter
+ CSS = 'img'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.xpath('descendant-or-self::img').each do |img|
+ doc.xpath(XPATH).each do |img|
img.add_class('lazy')
img['data-src'] = img['src']
img['src'] = LazyImageTagHelper.placeholder_image
diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb
index 5a1c0bee32d..e47ff15e7b7 100644
--- a/lib/banzai/filter/inline_diff_filter.rb
+++ b/lib/banzai/filter/inline_diff_filter.rb
@@ -7,7 +7,7 @@ module Banzai
IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
def call
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
content = node.to_html
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
index 2259115acfc..b256815ae84 100644
--- a/lib/banzai/filter/inline_metrics_redactor_filter.rb
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -8,6 +8,7 @@ module Banzai
include Gitlab::Utils::StrongMemoize
METRICS_CSS_CLASS = '.js-render-metrics'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(METRICS_CSS_CLASS).freeze
EMBED_LIMIT = 100
Route = Struct.new(:regex, :permission)
@@ -41,7 +42,7 @@ module Banzai
# @return [Nokogiri::XML::NodeSet]
def nodes
strong_memoize(:nodes) do
- nodes = doc.css(METRICS_CSS_CLASS)
+ nodes = doc.xpath(XPATH)
nodes.drop(EMBED_LIMIT).each(&:remove)
nodes
diff --git a/lib/banzai/filter/kroki_filter.rb b/lib/banzai/filter/kroki_filter.rb
index dbd4de32a47..3803302c324 100644
--- a/lib/banzai/filter/kroki_filter.rb
+++ b/lib/banzai/filter/kroki_filter.rb
@@ -15,10 +15,11 @@ module Banzai
.map { |diagram_type| %(pre[lang="#{diagram_type}"] > code) }
.join(', ')
- return doc unless doc.at(diagram_selectors)
+ xpath = Gitlab::Utils::Nokogiri.css_to_xpath(diagram_selectors)
+ return doc unless doc.at_xpath(xpath)
diagram_format = "svg"
- doc.css(diagram_selectors).each do |node|
+ doc.xpath(xpath).each do |node|
diagram_type = node.parent['lang']
img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img src="#{create_image_src(diagram_type, diagram_format, node.content)}"/>))
node.parent.replace(img_tag)
diff --git a/lib/banzai/filter/markdown_post_escape_filter.rb b/lib/banzai/filter/markdown_post_escape_filter.rb
index ad32e9afbf5..b69afdcfebe 100644
--- a/lib/banzai/filter/markdown_post_escape_filter.rb
+++ b/lib/banzai/filter/markdown_post_escape_filter.rb
@@ -8,6 +8,11 @@ module Banzai
NOT_LITERAL_REGEX = %r{#{LITERAL_KEYWORD}-((%5C|\\).+?)-#{LITERAL_KEYWORD}}.freeze
SPAN_REGEX = %r{<span>(.*?)</span>}.freeze
+ CSS_A = 'a'
+ XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze
+ CSS_CODE = 'code'
+ XPATH_CODE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_CODE).freeze
+
def call
return doc unless result[:escaped_literals]
@@ -24,12 +29,12 @@ module Banzai
# Banzai::Renderer::CommonMark::HTML. However, we eventually want to use
# the built-in compiled renderer, rather than the ruby version, for speed.
# So let's do this work here.
- doc.css('a').each do |node|
+ doc.xpath(XPATH_A).each do |node|
node.attributes['href'].value = node.attributes['href'].value.gsub(SPAN_REGEX, '\1') if node.attributes['href']
node.attributes['title'].value = node.attributes['title'].value.gsub(SPAN_REGEX, '\1') if node.attributes['title']
end
- doc.css('code').each do |node|
+ doc.xpath(XPATH_CODE).each do |node|
node.attributes['lang'].value = node.attributes['lang'].value.gsub(SPAN_REGEX, '\1') if node.attributes['lang']
end
diff --git a/lib/banzai/filter/math_filter.rb b/lib/banzai/filter/math_filter.rb
index 2247984b86d..53dafe45fb3 100644
--- a/lib/banzai/filter/math_filter.rb
+++ b/lib/banzai/filter/math_filter.rb
@@ -10,6 +10,11 @@ module Banzai
# HTML filter that adds class="code math" and removes the dollar sign in $`2+2`$.
#
class MathFilter < HTML::Pipeline::Filter
+ CSS_MATH = 'pre.code.language-math'
+ XPATH_MATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_MATH).freeze
+ CSS_CODE = 'code'
+ XPATH_CODE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_CODE).freeze
+
# Attribute indicating inline or display math.
STYLE_ATTRIBUTE = 'data-math-style'
@@ -21,7 +26,7 @@ module Banzai
DOLLAR_SIGN = '$'
def call
- doc.css('code').each do |code|
+ doc.xpath(XPATH_CODE).each do |code|
closing = code.next
opening = code.previous
@@ -39,7 +44,7 @@ module Banzai
end
end
- doc.css('pre.code.language-math').each do |el|
+ doc.xpath(XPATH_MATH).each do |el|
el[STYLE_ATTRIBUTE] = 'display'
el[:class] += " #{TAG_CLASS}"
end
diff --git a/lib/banzai/filter/mermaid_filter.rb b/lib/banzai/filter/mermaid_filter.rb
index f0adb83af8a..aaaf851ccf0 100644
--- a/lib/banzai/filter/mermaid_filter.rb
+++ b/lib/banzai/filter/mermaid_filter.rb
@@ -4,8 +4,11 @@
module Banzai
module Filter
class MermaidFilter < HTML::Pipeline::Filter
+ CSS = 'pre[lang="mermaid"] > code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.css('pre[lang="mermaid"] > code').add_class('js-render-mermaid')
+ doc.xpath(XPATH).add_class('js-render-mermaid')
doc
end
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
index 37d4126c1ba..93370178a61 100644
--- a/lib/banzai/filter/plantuml_filter.rb
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -8,12 +8,15 @@ module Banzai
# HTML that replaces all `code plantuml` tags with PlantUML img tags.
#
class PlantumlFilter < HTML::Pipeline::Filter
+ CSS = 'pre > code[lang="plantuml"]'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- return doc unless settings.plantuml_enabled? && doc.at('pre > code[lang="plantuml"]')
+ return doc unless settings.plantuml_enabled? && doc.at_xpath(XPATH)
plantuml_setup
- doc.css('pre > code[lang="plantuml"]').each do |node|
+ doc.xpath(XPATH).each do |node|
img_tag = Nokogiri::HTML::DocumentFragment.parse(
Asciidoctor::PlantUml::Processor.plantuml_content(node.content, {}))
node.parent.replace(img_tag)
diff --git a/lib/banzai/filter/suggestion_filter.rb b/lib/banzai/filter/suggestion_filter.rb
index 56a14ec0737..aa1fcb1021c 100644
--- a/lib/banzai/filter/suggestion_filter.rb
+++ b/lib/banzai/filter/suggestion_filter.rb
@@ -7,10 +7,13 @@ module Banzai
# Class used for tagging elements that should be rendered
TAG_CLASS = 'js-render-suggestion'
+ CSS = 'pre.language-suggestion > code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
return doc unless suggestions_filter_enabled?
- doc.search('pre.language-suggestion > code').each do |node|
+ doc.xpath(XPATH).each do |node|
node.add_class(TAG_CLASS)
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index b16ea689d2e..f1440c13d47 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -14,8 +14,11 @@ module Banzai
PARAMS_DELIMITER = ':'
LANG_PARAMS_ATTR = 'data-lang-params'
+ CSS = 'pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.search('pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code').each do |node|
+ doc.xpath(XPATH).each do |node|
highlight_node(node)
end
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index b362607aed2..13ca9cde567 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -19,6 +19,9 @@ module Banzai
class TableOfContentsFilter < HTML::Pipeline::Filter
include Gitlab::Utils::Markdown
+ CSS = 'h1, h2, h3, h4, h5, h6'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
return doc if context[:no_header_anchors]
@@ -27,7 +30,7 @@ module Banzai
headers = Hash.new(0)
header_root = current_header = HeaderNode.new
- doc.css('h1, h2, h3, h4, h5, h6').each do |node|
+ doc.xpath(XPATH).each do |node|
if header_content = node.children.first
id = string_to_anchor(node.text)
diff --git a/lib/banzai/filter/truncate_source_filter.rb b/lib/banzai/filter/truncate_source_filter.rb
index 44f88b253d9..a21d4a44295 100644
--- a/lib/banzai/filter/truncate_source_filter.rb
+++ b/lib/banzai/filter/truncate_source_filter.rb
@@ -3,12 +3,29 @@
module Banzai
module Filter
class TruncateSourceFilter < HTML::Pipeline::TextFilter
+ CHARACTER_COUNT_LIMIT = 1.megabyte
+ USER_MSG_LIMIT = 10_000
+
def call
- return text unless context.key?(:limit)
+ # don't truncate if it's a :blob and no limit is set
+ return text if context[:text_source] == :blob && !context.key?(:limit)
+
+ limit = context[:limit] || CHARACTER_COUNT_LIMIT
+
+ # no sense in allowing `truncate_bytes` to duplicate a large
+ # string unless it's too big
+ return text if text.bytesize <= limit
# Use three dots instead of the ellipsis Unicode character because
# some clients show the raw Unicode value in the merge commit.
- text.truncate_bytes(context[:limit], omission: '...')
+ trunc = text.truncate_bytes(limit, omission: '...')
+
+ # allows us to indicate to the user that what they see is a truncated copy
+ if limit > USER_MSG_LIMIT
+ trunc.prepend("_The text is longer than #{limit} characters and has been visually truncated._\n\n")
+ end
+
+ trunc
end
end
end
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 44f13612fde..2b95d87ff8e 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -10,14 +10,21 @@ module Banzai
class WikiLinkFilter < HTML::Pipeline::Filter
include Gitlab::Utils::SanitizeNodeLink
+ CSS_A = 'a:not(.gfm)'
+ XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze
+ CSS_VA = 'video, audio'
+ XPATH_VA = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_VA).freeze
+ CSS_IMG = 'img'
+ XPATH_IMG = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_IMG).freeze
+
def call
return doc unless wiki?
- doc.search('a:not(.gfm)').each { |el| process_link(el.attribute('href'), el) }
+ doc.xpath(XPATH_A).each { |el| process_link(el.attribute('href'), el) }
- doc.search('video, audio').each { |el| process_link(el.attribute('src'), el) }
+ doc.xpath(XPATH_VA).each { |el| process_link(el.attribute('src'), el) }
- doc.search('img').each do |el|
+ doc.xpath(XPATH_IMG).each do |el|
attr = el.attribute('data-src') || el.attribute('src')
process_link(attr, el)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index c6997288b65..4489fc9f3b2 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -84,7 +84,7 @@ module Gitlab
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
user = User.by_login(login)
- break if user && !user.can?(:log_in)
+ break if user && !can_user_login_with_non_expired_password?(user)
authenticators = []
@@ -182,7 +182,7 @@ module Gitlab
if valid_oauth_token?(token)
user = User.id_in(token.resource_owner_id).first
- return unless user&.can?(:log_in)
+ return unless user && can_user_login_with_non_expired_password?(user)
Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
end
@@ -200,7 +200,7 @@ module Gitlab
return if project && token.user.project_bot? && !project.bots.include?(token.user)
- if token.user.can?(:log_in) || token.user.project_bot?
+ if can_user_login_with_non_expired_password?(token.user) || token.user.project_bot?
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
end
end
@@ -285,7 +285,7 @@ module Gitlab
return unless build.project.builds_enabled?
if build.user
- return unless build.user.can?(:log_in) || (build.user.project_bot? && build.project.bots&.include?(build.user))
+ return unless can_user_login_with_non_expired_password?(build.user) || (build.user.project_bot? && build.project.bots&.include?(build.user))
# If user is assigned to build, use restricted credentials of user
Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
@@ -380,6 +380,10 @@ module Gitlab
user.increment_failed_attempts!
end
+
+ def can_user_login_with_non_expired_password?(user)
+ user.can?(:log_in) && !user.password_expired?
+ end
end
end
end
diff --git a/lib/gitlab/auth/user_access_denied_reason.rb b/lib/gitlab/auth/user_access_denied_reason.rb
index 36b54ba2e46..6639000dba8 100644
--- a/lib/gitlab/auth/user_access_denied_reason.rb
+++ b/lib/gitlab/auth/user_access_denied_reason.rb
@@ -23,6 +23,9 @@ module Gitlab
"Your primary email address is not confirmed. "\
"Please check your inbox for the confirmation instructions. "\
"In case the link is expired, you can request a new confirmation email at #{Rails.application.routes.url_helpers.new_user_confirmation_url}"
+ when :password_expired
+ "Your password expired. "\
+ "Please access GitLab from a web browser to update your password."
else
"Your account has been blocked."
end
@@ -41,6 +44,8 @@ module Gitlab
:deactivated
elsif !@user.confirmed?
:unconfirmed
+ elsif @user.password_expired?
+ :password_expired
else
:blocked
end
diff --git a/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb b/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
new file mode 100644
index 00000000000..f5ba9e63333
--- /dev/null
+++ b/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class UpdateUsersWhereTwoFactorAuthRequiredFromGroup # rubocop:disable Metrics/ClassLength
+ def perform(start_id, stop_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ UPDATE
+ users
+ SET
+ require_two_factor_authentication_from_group = TRUE
+ WHERE
+ users.id BETWEEN #{start_id}
+ AND #{stop_id}
+ AND users.require_two_factor_authentication_from_group = FALSE
+ AND users.id IN (
+ SELECT
+ DISTINCT users_groups_query.user_id
+ FROM
+ (
+ SELECT
+ users.id AS user_id,
+ members.source_id AS group_ids
+ FROM
+ users
+ LEFT JOIN members ON members.source_type = 'Namespace'
+ AND members.requested_at IS NULL
+ AND members.user_id = users.id
+ AND members.type = 'GroupMember'
+ WHERE
+ users.require_two_factor_authentication_from_group = FALSE
+ AND users.id BETWEEN #{start_id}
+ AND #{stop_id}) AS users_groups_query
+ INNER JOIN LATERAL (
+ WITH RECURSIVE "base_and_ancestors" AS (
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."id" = users_groups_query.group_ids
+ )
+ UNION
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces",
+ "base_and_ancestors"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."id" = "base_and_ancestors"."parent_id"
+ )
+ ),
+ "base_and_descendants" AS (
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."id" = users_groups_query.group_ids
+ )
+ UNION
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces",
+ "base_and_descendants"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."parent_id" = "base_and_descendants"."id"
+ )
+ )
+ SELECT
+ "namespaces".*
+ FROM
+ (
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "base_and_ancestors" AS "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ )
+ UNION
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "base_and_descendants" AS "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ )
+ ) namespaces
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."require_two_factor_authentication" = TRUE
+ ) AS hierarchy_tree ON TRUE
+ );
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
index f3e6fc455ac..6f126147113 100644
--- a/lib/gitlab/diff/suggestions_parser.rb
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -6,6 +6,9 @@ module Gitlab
# Matches for instance "-1", "+1" or "-1+2".
SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze
+ CSS = 'pre.language-suggestion'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
class << self
# Returns an array of Gitlab::Diff::Suggestion which represents each
# suggestion in the given text.
@@ -17,7 +20,7 @@ module Gitlab
no_original_data: true,
suggestions_filter_enabled: supports_suggestion)
doc = Nokogiri::HTML(html)
- suggestion_nodes = doc.search('pre.language-suggestion')
+ suggestion_nodes = doc.xpath(XPATH)
return [] if suggestion_nodes.empty?
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index d70e5c3594c..77e0e2ca96c 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -197,6 +197,24 @@ module Gitlab
rescue Addressable::URI::InvalidURIError, TypeError
end
+ def removes_sensitive_data_from_url(uri_string)
+ uri = parse_url(uri_string)
+
+ return unless uri
+ return uri_string unless uri.fragment
+
+ stripped_params = CGI.parse(uri.fragment)
+ if stripped_params['access_token']
+ stripped_params['access_token'] = 'filtered'
+ filtered_query = Addressable::URI.new
+ filtered_query.query_values = stripped_params
+
+ uri.fragment = filtered_query.query
+ end
+
+ uri.to_s
+ end
+
# Invert a hash, collecting all keys that map to a given value in an array.
#
# Unlike `Hash#invert`, where the last encountered pair wins, and which has the
diff --git a/lib/gitlab/utils/nokogiri.rb b/lib/gitlab/utils/nokogiri.rb
new file mode 100644
index 00000000000..4b37bb7e5ea
--- /dev/null
+++ b/lib/gitlab/utils/nokogiri.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Utils
+ class Nokogiri
+ class << self
+ # Use Nokogiri to convert a css selector into an xpath selector.
+ # Nokogiri can use css selectors with `doc.search()`. However
+ # for large node trees, it is _much_ slower than using xpath,
+ # by several orders of magnitude.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/329186
+ def css_to_xpath(css)
+ xpath = ::Nokogiri::CSS.xpath_for(css)
+
+ # Due to https://github.com/sparklemotion/nokogiri/issues/572,
+ # we remove the leading `//` and add `descendant-or-self::`
+ # in order to ensure we're searching from this node and all
+ # descendants.
+ xpath.map { |t| "descendant-or-self::#{t[2..-1]}" }.join('|')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/x509/signature.rb b/lib/gitlab/x509/signature.rb
index c83213e973b..a6761e211fa 100644
--- a/lib/gitlab/x509/signature.rb
+++ b/lib/gitlab/x509/signature.rb
@@ -23,7 +23,7 @@ module Gitlab
end
def user
- User.find_by_any_email(@email)
+ strong_memoize(:user) { User.find_by_any_email(@email) }
end
def verified_signature
@@ -31,9 +31,13 @@ module Gitlab
end
def verification_status
- return :unverified if x509_certificate.nil? || x509_certificate.revoked?
+ return :unverified if
+ x509_certificate.nil? ||
+ x509_certificate.revoked? ||
+ !verified_signature ||
+ user.nil?
- if verified_signature && certificate_email == @email
+ if user.verified_emails.include?(@email) && certificate_email == @email
:verified
else
:unverified
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0df958aa774..c244c486c5d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -27077,6 +27077,9 @@ msgstr ""
msgid "Redirect to SAML provider to test configuration"
msgstr ""
+msgid "Redirecting"
+msgstr ""
+
msgid "Redis"
msgstr ""
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index 5fc5cdfc9b9..0e25f6a96d7 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -70,76 +70,59 @@ RSpec.describe Oauth::AuthorizationsController do
describe 'GET #new' do
subject { get :new, params: params }
- include_examples 'OAuth Authorizations require confirmed user'
include_examples "Implicit grant can't be used in confidential application"
- context 'rendering of views based on the ownership of the application' do
- shared_examples 'render views' do
- render_views
-
- it 'returns 200 and renders view with correct info', :aggregate_failures do
- subject
+ context 'when the user is confirmed' do
+ let(:confirmed_at) { 1.hour.ago }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to include(application.owner.name)
- expect(response).to render_template('doorkeeper/authorizations/new')
- end
- end
+ context 'when there is already an access token for the application with a matching scope' do
+ before do
+ scopes = Doorkeeper::OAuth::Scopes.from_string('api')
- subject { get :new, params: params }
+ allow(Doorkeeper.configuration).to receive(:scopes).and_return(scopes)
- context 'when auth app owner is a user' do
- context 'with valid params' do
- it_behaves_like 'render views'
+ create(:oauth_access_token, application: application, resource_owner_id: user.id, scopes: scopes)
end
- end
-
- context 'when auth app owner is a group' do
- let(:group) { create(:group) }
- context 'when auth app owner is a root group' do
- let(:application) { create(:oauth_application, owner_id: group.id, owner_type: 'Namespace') }
+ it 'authorizes the request and shows the user a page that redirects' do
+ subject
- it_behaves_like 'render views'
+ expect(request.session['user_return_to']).to be_nil
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('doorkeeper/authorizations/redirect')
end
+ end
- context 'when auth app owner is a subgroup' do
- let(:subgroup) { create(:group, parent: group) }
- let(:application) { create(:oauth_application, owner_id: subgroup.id, owner_type: 'Namespace') }
+ context 'without valid params' do
+ it 'returns 200 code and renders error view' do
+ get :new
- it_behaves_like 'render views'
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('doorkeeper/authorizations/error')
end
end
- context 'when there is no owner associated' do
- let(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) }
+ context 'with valid params' do
+ render_views
- it 'renders view' do
+ it 'returns 200 code and renders view' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('doorkeeper/authorizations/new')
end
- end
- end
- context 'without valid params' do
- it 'returns 200 code and renders error view' do
- get :new
+ it 'deletes session.user_return_to and redirects when skip authorization' do
+ application.update!(trusted: true)
+ request.session['user_return_to'] = 'http://example.com'
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template('doorkeeper/authorizations/error')
- end
- end
-
- it 'deletes session.user_return_to and redirects when skip authorization' do
- application.update!(trusted: true)
- request.session['user_return_to'] = 'http://example.com'
-
- subject
+ subject
- expect(request.session['user_return_to']).to be_nil
- expect(response).to have_gitlab_http_status(:found)
+ expect(request.session['user_return_to']).to be_nil
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('doorkeeper/authorizations/redirect')
+ end
+ end
end
end
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 072a5f1f402..148ee64fb08 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -16,19 +16,19 @@ FactoryBot.define do
end
trait :production do
- tier { :production }
+ name { 'production' }
end
trait :staging do
- tier { :staging }
+ name { 'staging' }
end
trait :testing do
- tier { :testing }
+ name { 'testing' }
end
trait :development do
- tier { :development }
+ name { 'development' }
end
trait :with_review_app do |environment|
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index e60d9d6ab69..1d7099ba443 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -394,6 +394,25 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
end
+
+ context 'when the users password is expired' do
+ before do
+ user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC'))
+ end
+
+ it 'asks for a new password' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+
+ visit new_user_session_path
+
+ fill_in 'user_login', with: user.email
+ fill_in 'user_password', with: '12345678'
+ click_button 'Sign in'
+
+ expect(current_path).to eq(new_profile_password_path)
+ end
+ end
end
context 'with invalid username and password' do
diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js
index d250ffed1a9..deeee5d6589 100644
--- a/spec/frontend/notebook/cells/markdown_spec.js
+++ b/spec/frontend/notebook/cells/markdown_spec.js
@@ -39,7 +39,7 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
- it('sanitizes output', async () => {
+ it('sanitizes Markdown output', async () => {
Object.assign(cell, {
source: [
'[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n',
@@ -50,6 +50,17 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('a').getAttribute('href')).toBeNull();
});
+ it('sanitizes HTML', async () => {
+ const findLink = () => vm.$el.querySelector('.xss-link');
+ Object.assign(cell, {
+ source: ['<a href="test.js" data-remote=true data-type="script" class="xss-link">XSS</a>\n'],
+ });
+
+ await vm.$nextTick();
+ expect(findLink().getAttribute('data-remote')).toBe(null);
+ expect(findLink().getAttribute('data-type')).toBe(null);
+ });
+
describe('tables', () => {
beforeEach(() => {
json = getJSONFixture('blob/notebook/markdown-table.json');
diff --git a/spec/graphql/resolvers/timelog_resolver_spec.rb b/spec/graphql/resolvers/timelog_resolver_spec.rb
index 585cd657e35..bb4938c751f 100644
--- a/spec/graphql/resolvers/timelog_resolver_spec.rb
+++ b/spec/graphql/resolvers/timelog_resolver_spec.rb
@@ -11,26 +11,27 @@ RSpec.describe Resolvers::TimelogResolver do
context "with a group" do
let_it_be(:current_user) { create(:user) }
- let_it_be(:group) { create(:group) }
- let_it_be(:project) { create(:project, :public, group: group) }
-
- before_all do
- group.add_developer(current_user)
- project.add_developer(current_user)
- end
-
- before do
- group.clear_memoization(:timelogs)
- end
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :empty_repo, :public, group: group) }
describe '#resolve' do
+ let_it_be(:short_time_ago) { 5.days.ago.beginning_of_day }
+ let_it_be(:medium_time_ago) { 15.days.ago.beginning_of_day }
+
let_it_be(:issue) { create(:issue, project: project) }
- let_it_be(:issue2) { create(:issue, project: project) }
- let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: 2.days.ago.beginning_of_day) }
- let_it_be(:timelog2) { create(:issue_timelog, issue: issue2, spent_at: 2.days.ago.end_of_day) }
- let_it_be(:timelog3) { create(:issue_timelog, issue: issue2, spent_at: 10.days.ago) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+
+ let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.beginning_of_day) }
+ let_it_be(:timelog2) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.end_of_day) }
+ let_it_be(:timelog3) { create(:merge_request_timelog, merge_request: merge_request, spent_at: medium_time_ago) }
+
+ let(:args) { { start_time: short_time_ago, end_time: short_time_ago.noon } }
+
+ it 'finds all timelogs' do
+ timelogs = resolve_timelogs
- let(:args) { { start_time: 6.days.ago, end_time: 2.days.ago.noon } }
+ expect(timelogs).to contain_exactly(timelog1, timelog2, timelog3)
+ end
it 'finds all timelogs within given dates' do
timelogs = resolve_timelogs(**args)
@@ -38,15 +39,28 @@ RSpec.describe Resolvers::TimelogResolver do
expect(timelogs).to contain_exactly(timelog1)
end
- it 'return nothing when user has insufficient permissions' do
- user = create(:user)
- group.add_guest(current_user)
+ context 'when only start_date is present' do
+ let(:args) { { start_date: short_time_ago } }
+
+ it 'finds timelogs until the end of day of end_date' do
+ timelogs = resolve_timelogs(**args)
+
+ expect(timelogs).to contain_exactly(timelog1, timelog2)
+ end
+ end
+
+ context 'when only end_date is present' do
+ let(:args) { { end_date: medium_time_ago } }
+
+ it 'finds timelogs until the end of day of end_date' do
+ timelogs = resolve_timelogs(**args)
- expect(resolve_timelogs(user: user, **args)).to be_empty
+ expect(timelogs).to contain_exactly(timelog3)
+ end
end
context 'when start_time and end_date are present' do
- let(:args) { { start_time: 6.days.ago, end_date: 2.days.ago } }
+ let(:args) { { start_time: short_time_ago, end_date: short_time_ago } }
it 'finds timelogs until the end of day of end_date' do
timelogs = resolve_timelogs(**args)
@@ -56,7 +70,7 @@ RSpec.describe Resolvers::TimelogResolver do
end
context 'when start_date and end_time are present' do
- let(:args) { { start_date: 6.days.ago, end_time: 2.days.ago.noon } }
+ let(:args) { { start_date: short_time_ago, end_time: short_time_ago.noon } }
it 'finds all timelogs within start_date and end_time' do
timelogs = resolve_timelogs(**args)
@@ -68,95 +82,32 @@ RSpec.describe Resolvers::TimelogResolver do
context 'when arguments are invalid' do
let_it_be(:error_class) { Gitlab::Graphql::Errors::ArgumentError }
- context 'when no time or date arguments are present' do
- let(:args) { {} }
-
- it 'returns correct error' do
- expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Start and End arguments must be present/)
- end
- end
-
- context 'when only start_time is present' do
- let(:args) { { start_time: 6.days.ago } }
-
- it 'returns correct error' do
- expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Both Start and End arguments must be present/)
- end
- end
-
- context 'when only end_time is present' do
- let(:args) { { end_time: 2.days.ago } }
-
- it 'returns correct error' do
- expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Both Start and End arguments must be present/)
- end
- end
-
- context 'when only start_date is present' do
- let(:args) { { start_date: 6.days.ago } }
-
- it 'returns correct error' do
- expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Both Start and End arguments must be present/)
- end
- end
-
- context 'when only end_date is present' do
- let(:args) { { end_date: 2.days.ago } }
-
- it 'returns correct error' do
- expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Both Start and End arguments must be present/)
- end
- end
-
context 'when start_time and start_date are present' do
- let(:args) { { start_time: 6.days.ago, start_date: 6.days.ago } }
+ let(:args) { { start_time: short_time_ago, start_date: short_time_ago } }
it 'returns correct error' do
expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Both Start and End arguments must be present/)
+ .to raise_error(error_class, /Provide either a start date or time, but not both/)
end
end
context 'when end_time and end_date are present' do
- let(:args) { { end_time: 2.days.ago, end_date: 2.days.ago } }
+ let(:args) { { end_time: short_time_ago, end_date: short_time_ago } }
it 'returns correct error' do
expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Both Start and End arguments must be present/)
- end
- end
-
- context 'when three arguments are present' do
- let(:args) { { start_date: 6.days.ago, end_date: 2.days.ago, end_time: 2.days.ago } }
-
- it 'returns correct error' do
- expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /Only Time or Date arguments must be present/)
+ .to raise_error(error_class, /Provide either an end date or time, but not both/)
end
end
context 'when start argument is after end argument' do
- let(:args) { { start_time: 2.days.ago, end_time: 6.days.ago } }
+ let(:args) { { start_time: short_time_ago, end_time: medium_time_ago } }
it 'returns correct error' do
expect { resolve_timelogs(**args) }
.to raise_error(error_class, /Start argument must be before End argument/)
end
end
-
- context 'when time range is more than 60 days' do
- let(:args) { { start_time: 3.months.ago, end_time: 2.days.ago } }
-
- it 'returns correct error' do
- expect { resolve_timelogs(**args) }
- .to raise_error(error_class, /The time range period cannot contain more than 60 days/)
- end
- end
end
end
end
diff --git a/spec/graphql/types/timelog_type_spec.rb b/spec/graphql/types/timelog_type_spec.rb
index 791c2fdb046..1344af89fb6 100644
--- a/spec/graphql/types/timelog_type_spec.rb
+++ b/spec/graphql/types/timelog_type_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Timelog'] do
it { expect(described_class.graphql_name).to eq('Timelog') }
it { expect(described_class).to have_graphql_fields(fields) }
- it { expect(described_class).to require_graphql_authorizations(:read_group_timelogs) }
+ it { expect(described_class).to require_graphql_authorizations(:read_issue) }
describe 'user field' do
subject { described_class.fields['user'] }
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 00a59f037e0..e946857ac77 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -418,6 +418,13 @@ FooBar
describe '#markup' do
let(:content) { 'Noël' }
+ it 'sets the :text_source to :blob in the context' do
+ context = {}
+ helper.markup('foo.md', content, context)
+
+ expect(context).to include(text_source: :blob)
+ end
+
it 'preserves encoding' do
expect(content.encoding.name).to eq('UTF-8')
expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8')
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index abb1673bb88..421f6373eff 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -173,6 +173,27 @@ RSpec.describe 'lograge', type: :request do
end
end
+ describe 'with access token in url' do
+ before do
+ event.payload[:location] = 'http://example.com/auth.html#access_token=secret_token&token_type=Bearer'
+ end
+
+ it 'strips location from sensitive information' do
+ subscriber.redirect_to(event)
+ subscriber.process_action(event)
+
+ expect(log_data['location']).not_to include('secret_token')
+ expect(log_data['location']).to include('filtered')
+ end
+
+ it 'leaves non-sensitive information from location' do
+ subscriber.redirect_to(event)
+ subscriber.process_action(event)
+
+ expect(log_data['location']).to include('&token_type=Bearer')
+ end
+ end
+
context 'with db payload' do
context 'when RequestStore is enabled', :request_store do
it 'includes db counters' do
diff --git a/spec/lib/banzai/filter/truncate_source_filter_spec.rb b/spec/lib/banzai/filter/truncate_source_filter_spec.rb
index d5eb8b738b1..8970aa1d382 100644
--- a/spec/lib/banzai/filter/truncate_source_filter_spec.rb
+++ b/spec/lib/banzai/filter/truncate_source_filter_spec.rb
@@ -8,24 +8,68 @@ RSpec.describe Banzai::Filter::TruncateSourceFilter do
let(:short_text) { 'foo' * 10 }
let(:long_text) { ([short_text] * 10).join(' ') }
- it 'does nothing when limit is unspecified' do
- output = filter(long_text)
-
- expect(output).to eq(long_text)
+ before do
+ stub_const("#{described_class}::CHARACTER_COUNT_LIMIT", 50)
+ stub_const("#{described_class}::USER_MSG_LIMIT", 20)
end
- it 'does nothing to a short-enough text' do
- output = filter(short_text, limit: short_text.bytesize)
+ context 'when markdown belongs to a blob' do
+ it 'does nothing when limit is unspecified' do
+ output = filter(long_text, text_source: :blob)
+
+ expect(output).to eq(long_text)
+ end
+
+ it 'truncates normally when limit specified' do
+ truncated = 'foofoof...'
+
+ output = filter(long_text, text_source: :blob, limit: 10)
- expect(output).to eq(short_text)
+ expect(output).to eq(truncated)
+ end
end
- it 'truncates UTF-8 text by bytes, on a character boundary' do
- utf8_text = '日本語の文字が大きい'
- truncated = '日...'
+ context 'when markdown belongs to a field (non-blob)' do
+ it 'does nothing when limit is greater' do
+ output = filter(long_text, limit: 1.megabyte)
+
+ expect(output).to eq(long_text)
+ end
+
+ it 'truncates to the default when limit is unspecified' do
+ stub_const("#{described_class}::USER_MSG_LIMIT", 200)
+ truncated = 'foofoofoofoofoofoofoofoofoofoo foofoofoofoofoof...'
+
+ output = filter(long_text)
+
+ expect(output).to eq(truncated)
+ end
+
+ it 'prepends the user message' do
+ truncated = <<~TEXT
+ _The text is longer than 50 characters and has been visually truncated._
+
+ foofoofoofoofoofoofoofoofoofoo foofoofoofoofoof...
+ TEXT
+
+ output = filter(long_text)
+
+ expect(output).to eq(truncated.strip)
+ end
+
+ it 'does nothing to a short-enough text' do
+ output = filter(short_text, limit: short_text.bytesize)
+
+ expect(output).to eq(short_text)
+ end
+
+ it 'truncates UTF-8 text by bytes, on a character boundary' do
+ utf8_text = '日本語の文字が大きい'
+ truncated = '日...'
- expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated)
- expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text)
- expect(filter(utf8_text, limit: utf8_text.mb_chars.size)).not_to eq(utf8_text)
+ expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated)
+ expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text)
+ expect(filter(utf8_text, limit: utf8_text.mb_chars.size)).not_to eq(utf8_text)
+ end
end
end
diff --git a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
index 5a3ceccfae1..e8df395564a 100644
--- a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
@@ -36,9 +36,11 @@ RSpec.describe Banzai::Pipeline::PostProcessPipeline do
end
let(:doc) { HTML::Pipeline.parse(html) }
+ let(:non_related_xpath_calls) { 2 }
it 'searches for attributes only once' do
- expect(doc).to receive(:search).once.and_call_original
+ expect(doc).to receive(:xpath).exactly(non_related_xpath_calls + 1).times
+ .and_call_original
subject
end
diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
index d3c6cde5590..102d6fba97f 100644
--- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
+++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
@@ -57,5 +57,13 @@ RSpec.describe Gitlab::Auth::UserAccessDeniedReason do
it { is_expected.to eq('Your account is pending approval from your administrator and hence blocked.') }
end
+
+ context 'when the user has expired password' do
+ before do
+ user.update!(password_expires_at: 2.days.ago)
+ end
+
+ it { is_expected.to eq('Your password expired. Please access GitLab from a web browser to update your password.') }
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb b/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb
new file mode 100644
index 00000000000..e14328b6150
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::UpdateUsersWhereTwoFactorAuthRequiredFromGroup, :migration, schema: 20210519154058 do
+ include MigrationHelpers::NamespacesHelpers
+
+ let(:group_with_2fa_parent) { create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true) }
+ let(:group_with_2fa_child) { create_namespace('child', Gitlab::VisibilityLevel::PRIVATE, parent_id: group_with_2fa_parent.id) }
+ let(:members_table) { table(:members) }
+ let(:users_table) { table(:users) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ context 'with group members' do
+ let(:user_1) { create_user('user@example.com') }
+ let!(:member) { create_group_member(user_1, group_with_2fa_parent) }
+ let!(:user_without_group) { create_user('user_without@example.com') }
+ let(:user_other) { create_user('user_other@example.com') }
+ let!(:member_other) { create_group_member(user_other, group_with_2fa_parent) }
+
+ it 'updates user when user should be required to establish two factor authentication' do
+ subject.perform(user_1.id, user_without_group.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'does not update user who is not in current batch' do
+ subject.perform(user_1.id, user_without_group.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false)
+ end
+
+ it 'updates all users in current batch' do
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'updates user when user is member of group in which parent group requires two factor authentication' do
+ member.destroy!
+
+ subgroup = create_namespace('subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false, parent_id: group_with_2fa_child.id)
+ create_group_member(user_1, subgroup)
+
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'updates user when user is member of a group and the subgroup requires two factor authentication' do
+ member.destroy!
+
+ parent = create_namespace('other_parent', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false)
+ create_namespace('other_subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true, parent_id: parent.id)
+ create_group_member(user_1, parent)
+
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'does not update user when not a member of a group that requires two factor authentication' do
+ member_other.destroy!
+
+ other_group = create_namespace('other_group', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false)
+ create_group_member(user_other, other_group)
+
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false)
+ end
+ end
+ end
+
+ def create_user(email, require_2fa: false)
+ users_table.create!(email: email, projects_limit: 10, require_two_factor_authentication_from_group: require_2fa)
+ end
+
+ def create_group_member(user, group)
+ members_table.create!(user_id: user.id, source_id: group.id, access_level: GroupMember::MAINTAINER, source_type: "Namespace", type: "GroupMember", notification_level: 3)
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
index 175b12637e6..ad89f1f5cda 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -128,7 +128,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do
context 'when environment has already been created' do
before do
- create(:environment, :staging, project: project, name: 'customer-portal')
+ create(:environment, project: project, name: 'customer-portal', tier: :staging)
end
it 'does not overwrite the specified deployment tier' do
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index ae9c697e0b9..3d6c04fd484 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -433,6 +433,13 @@ RSpec.describe Gitlab::GitAccess do
expect { pull_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
end
+ it 'disallows users with expired password to pull' do
+ project.add_maintainer(user)
+ user.update!(password_expires_at: 2.minutes.ago)
+
+ expect { pull_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
+ end
+
context 'when the project repository does not exist' do
before do
project.add_guest(user)
@@ -969,6 +976,13 @@ RSpec.describe Gitlab::GitAccess do
expect { push_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
end
+ it 'disallows users with expired password to push' do
+ project.add_maintainer(user)
+ user.update!(password_expires_at: 2.minutes.ago)
+
+ expect { push_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
+ end
+
it 'cleans up the files' do
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
expect { push_access_check }.not_to raise_error
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index c7e77a34582..dc7fedd6b4f 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -624,6 +624,7 @@ metrics_setting:
- project
protected_environments:
- project
+- group
- deploy_access_levels
deploy_access_levels:
- protected_environment
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 0be1b09851f..2173bee6b4b 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -701,6 +701,7 @@ ProjectSetting:
ProtectedEnvironment:
- id
- project_id
+- group_id
- name
- created_at
- updated_at
diff --git a/spec/lib/gitlab/utils/nokogiri_spec.rb b/spec/lib/gitlab/utils/nokogiri_spec.rb
new file mode 100644
index 00000000000..90f137f53c8
--- /dev/null
+++ b/spec/lib/gitlab/utils/nokogiri_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Utils::Nokogiri do
+ describe '#css_to_xpath' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:css, :xpath) do
+ 'img' | "descendant-or-self::img"
+ 'a.gfm' | "descendant-or-self::a[contains(concat(' ',normalize-space(@class),' '),' gfm ')]"
+ 'a:not(.gfm)' | "descendant-or-self::a[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]"
+ 'video, audio' | "descendant-or-self::video|descendant-or-self::audio"
+ '[data-math-style]' | "descendant-or-self::*[@data-math-style]"
+ '[data-mermaid-style]' | "descendant-or-self::*[@data-mermaid-style]"
+ '.js-render-metrics' | "descendant-or-self::*[contains(concat(' ',normalize-space(@class),' '),' js-render-metrics ')]"
+ 'h1, h2, h3, h4, h5, h6' | "descendant-or-self::h1|descendant-or-self::h2|descendant-or-self::h3|descendant-or-self::h4|descendant-or-self::h5|descendant-or-self::h6"
+ 'pre.code.language-math' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' code ') and contains(concat(' ',normalize-space(@class),' '),' language-math ')]"
+ 'pre > code[lang="plantuml"]' | "descendant-or-self::pre/code[@lang=\"plantuml\"]"
+ 'pre[lang="mermaid"] > code' | "descendant-or-self::pre[@lang=\"mermaid\"]/code"
+ 'pre.language-suggestion' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' language-suggestion ')]"
+ 'pre.language-suggestion > code' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' language-suggestion ')]/code"
+ 'a.gfm[data-reference-type="user"]' | "descendant-or-self::a[contains(concat(' ',normalize-space(@class),' '),' gfm ') and @data-reference-type=\"user\"]"
+ 'a:not(.gfm), img:not(.gfm), video:not(.gfm), audio:not(.gfm)' | "descendant-or-self::a[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::img[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::video[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::audio[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]"
+ 'pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code' | "descendant-or-self::pre[not(@data-math-style) and not(@data-mermaid-style) and not(@data-kroki-style)]/code"
+ end
+
+ with_them do
+ it 'generates the xpath' do
+ expect(described_class.css_to_xpath(css)).to eq xpath
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 11dba610faf..a7ccce0aaab 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -417,6 +417,29 @@ RSpec.describe Gitlab::Utils do
end
end
+ describe '.removes_sensitive_data_from_url' do
+ it 'returns string object' do
+ expect(described_class.removes_sensitive_data_from_url('http://gitlab.com')).to be_instance_of(String)
+ end
+
+ it 'returns nil when URI cannot be parsed' do
+ expect(described_class.removes_sensitive_data_from_url('://gitlab.com')).to be nil
+ end
+
+ it 'returns nil with invalid parameter' do
+ expect(described_class.removes_sensitive_data_from_url(1)).to be nil
+ end
+
+ it 'returns string with filtered access_token param' do
+ expect(described_class.removes_sensitive_data_from_url('http://gitlab.com/auth.html#access_token=secret_token')).to eq('http://gitlab.com/auth.html#access_token=filtered')
+ end
+
+ it 'returns string with filtered access_token param but other params preserved' do
+ expect(described_class.removes_sensitive_data_from_url('http://gitlab.com/auth.html#access_token=secret_token&token_type=Bearer&state=test'))
+ .to include('&token_type=Bearer', '&state=test')
+ end
+ end
+
describe 'multiple_key_invert' do
it 'invert keys with array values' do
hash = {
diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb
index 2ac9c1f3a3b..7ba15faf910 100644
--- a/spec/lib/gitlab/x509/signature_spec.rb
+++ b/spec/lib/gitlab/x509/signature_spec.rb
@@ -12,20 +12,30 @@ RSpec.describe Gitlab::X509::Signature do
end
shared_examples "a verified signature" do
- it 'returns a verified signature if email does match' do
- signature = described_class.new(
+ let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
+
+ subject(:signature) do
+ described_class.new(
X509Helpers::User1.signed_commit_signature,
X509Helpers::User1.signed_commit_base_data,
X509Helpers::User1.certificate_email,
X509Helpers::User1.signed_commit_time
)
+ end
+ it 'returns a verified signature if email does match' do
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified)
end
+ it "returns an unverified signature if the email matches but isn't confirmed" do
+ user.update!(confirmed_at: nil)
+
+ expect(signature.verification_status).to eq(:unverified)
+ end
+
it 'returns an unverified signature if email does not match' do
signature = described_class.new(
X509Helpers::User1.signed_commit_signature,
@@ -55,13 +65,6 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns an unverified signature if certificate is revoked' do
- signature = described_class.new(
- X509Helpers::User1.signed_commit_signature,
- X509Helpers::User1.signed_commit_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.verification_status).to eq(:verified)
signature.x509_certificate.revoked!
@@ -253,23 +256,25 @@ RSpec.describe Gitlab::X509::Signature do
end
describe '#user' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
+ subject do
+ described_class.new(
+ X509Helpers::User1.signed_tag_signature,
+ X509Helpers::User1.signed_tag_base_data,
+ X509Helpers::User1.certificate_email,
+ X509Helpers::User1.signed_commit_time
+ ).user
+ end
context 'if email is assigned to a user' do
let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
it 'returns user' do
- expect(signature.user).to eq(user)
+ is_expected.to eq(user)
end
end
it 'if email is not assigned to a user, return nil' do
- expect(signature.user).to be_nil
+ is_expected.to be_nil
end
end
@@ -292,6 +297,17 @@ RSpec.describe Gitlab::X509::Signature do
end
context 'verified signature' do
+ let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
+
+ subject(:signature) do
+ described_class.new(
+ X509Helpers::User1.signed_tag_signature,
+ X509Helpers::User1.signed_tag_base_data,
+ X509Helpers::User1.certificate_email,
+ X509Helpers::User1.signed_commit_time
+ )
+ end
+
context 'with trusted certificate store' do
before do
store = OpenSSL::X509::Store.new
@@ -301,19 +317,18 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns a verified signature if email does match' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified)
end
+ it "returns an unverified signature if the email matches but isn't confirmed" do
+ user.update!(confirmed_at: nil)
+
+ expect(signature.verification_status).to eq(:unverified)
+ end
+
it 'returns an unverified signature if email does not match' do
signature = described_class.new(
X509Helpers::User1.signed_tag_signature,
@@ -343,13 +358,6 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns an unverified signature if certificate is revoked' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.verification_status).to eq(:verified)
signature.x509_certificate.revoked!
@@ -368,13 +376,6 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns an unverified signature' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_falsey
diff --git a/spec/migrations/20210601080039_group_protected_environments_add_index_and_constraint_spec.rb b/spec/migrations/20210601080039_group_protected_environments_add_index_and_constraint_spec.rb
new file mode 100644
index 00000000000..d3154596b26
--- /dev/null
+++ b/spec/migrations/20210601080039_group_protected_environments_add_index_and_constraint_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!('group_protected_environments_add_index_and_constraint')
+
+RSpec.describe GroupProtectedEnvironmentsAddIndexAndConstraint do
+ let(:migration) { described_class.new }
+ let(:protected_environments) { table(:protected_environments) }
+ let(:group) { table(:namespaces).create!(name: 'group', path: 'group') }
+ let(:project) { table(:projects).create!(name: 'project', path: 'project', namespace_id: group.id) }
+
+ describe '#down' do
+ it 'deletes only group-level configurations' do
+ migration.up
+
+ project_protections = [
+ protected_environments.create!(project_id: project.id, name: 'production'),
+ protected_environments.create!(project_id: project.id, name: 'staging')
+ ]
+ protected_environments.create!(group_id: group.id, name: 'production')
+ protected_environments.create!(group_id: group.id, name: 'staging')
+
+ migration.down
+
+ expect(protected_environments.pluck(:id))
+ .to match_array project_protections.map(&:id)
+ end
+ end
+end
diff --git a/spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb b/spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb
new file mode 100644
index 00000000000..86415b1520e
--- /dev/null
+++ b/spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20210519220019_backfill_escalation_policies_for_oncall_schedules.rb')
+
+RSpec.describe BackfillEscalationPoliciesForOncallSchedules do
+ let_it_be(:projects) { table(:projects) }
+ let_it_be(:schedules) { table(:incident_management_oncall_schedules) }
+ let_it_be(:policies) { table(:incident_management_escalation_policies) }
+ let_it_be(:rules) { table(:incident_management_escalation_rules) }
+
+ # Project with no schedules
+ let_it_be(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab') }
+ let_it_be(:project_a) { projects.create!(namespace_id: namespace.id) }
+
+ context 'with backfill-able schedules' do
+ # Project with one schedule
+ let_it_be(:project_b) { projects.create!(namespace_id: namespace.id) }
+ let_it_be(:schedule_b1) { schedules.create!(project_id: project_b.id, iid: 1, name: 'Schedule B1') }
+
+ # Project with multiple schedules
+ let_it_be(:project_c) { projects.create!(namespace_id: namespace.id) }
+ let_it_be(:schedule_c1) { schedules.create!(project_id: project_c.id, iid: 1, name: 'Schedule C1') }
+ let_it_be(:schedule_c2) { schedules.create!(project_id: project_c.id, iid: 2, name: 'Schedule C2') }
+
+ # Project with a single schedule which already has a policy
+ let_it_be(:project_d) { projects.create!(namespace_id: namespace.id) }
+ let_it_be(:schedule_d1) { schedules.create!(project_id: project_d.id, iid: 1, name: 'Schedule D1') }
+ let_it_be(:policy_d1) { policies.create!(project_id: project_d.id, name: 'Policy D1') }
+ let_it_be(:rule_d1) { rules.create!(policy_id: policy_d1.id, oncall_schedule_id: schedule_d1.id, status: 2, elapsed_time_seconds: 60) }
+
+ # Project with a multiple schedule, one of which already has a policy
+ let_it_be(:project_e) { projects.create!(namespace_id: namespace.id) }
+ let_it_be(:schedule_e1) { schedules.create!(project_id: project_e.id, iid: 1, name: 'Schedule E1') }
+ let_it_be(:schedule_e2) { schedules.create!(project_id: project_e.id, iid: 2, name: 'Schedule E2') }
+ let_it_be(:policy_e1) { policies.create!(project_id: project_e.id, name: 'Policy E1') }
+ let_it_be(:rule_e1) { rules.create!(policy_id: policy_e1.id, oncall_schedule_id: schedule_e2.id, status: 2, elapsed_time_seconds: 60) }
+
+ # Project with a multiple schedule, with multiple policies
+ let_it_be(:project_f) { projects.create!(namespace_id: namespace.id) }
+ let_it_be(:schedule_f1) { schedules.create!(project_id: project_f.id, iid: 1, name: 'Schedule F1') }
+ let_it_be(:schedule_f2) { schedules.create!(project_id: project_f.id, iid: 2, name: 'Schedule F2') }
+ let_it_be(:policy_f1) { policies.create!(project_id: project_f.id, name: 'Policy F1') }
+ let_it_be(:rule_f1) { rules.create!(policy_id: policy_f1.id, oncall_schedule_id: schedule_f1.id, status: 2, elapsed_time_seconds: 60) }
+ let_it_be(:rule_f2) { rules.create!(policy_id: policy_f1.id, oncall_schedule_id: schedule_f2.id, status: 2, elapsed_time_seconds: 60) }
+ let_it_be(:policy_f2) { policies.create!(project_id: project_f.id, name: 'Policy F2') }
+ let_it_be(:rule_f3) { rules.create!(policy_id: policy_f2.id, oncall_schedule_id: schedule_f2.id, status: 1, elapsed_time_seconds: 10) }
+
+ it 'backfills escalation policies correctly' do
+ expect { migrate! }
+ .to change(policies, :count).by(2)
+ .and change(rules, :count).by(3)
+
+ new_policy_b1, new_policy_c1 = new_polices = policies.last(2)
+ new_rules = rules.last(3)
+
+ expect(new_polices).to all have_attributes(name: 'On-call Escalation Policy')
+ expect(new_policy_b1.description).to eq('Immediately notify Schedule B1')
+ expect(new_policy_c1.description).to eq('Immediately notify Schedule C1')
+ expect(policies.pluck(:project_id)).to eq([
+ project_d.id,
+ project_e.id,
+ project_f.id,
+ project_f.id,
+ project_b.id,
+ project_c.id
+ ])
+
+ expect(new_rules).to all have_attributes(status: 1, elapsed_time_seconds: 0)
+ expect(rules.pluck(:policy_id)).to eq([
+ rule_d1.policy_id,
+ rule_e1.policy_id,
+ rule_f1.policy_id,
+ rule_f2.policy_id,
+ rule_f3.policy_id,
+ new_policy_b1.id,
+ new_policy_c1.id,
+ new_policy_c1.id
+ ])
+ expect(rules.pluck(:oncall_schedule_id)).to eq([
+ rule_d1.oncall_schedule_id,
+ rule_e1.oncall_schedule_id,
+ rule_f1.oncall_schedule_id,
+ rule_f2.oncall_schedule_id,
+ rule_f3.oncall_schedule_id,
+ schedule_b1.id,
+ schedule_c1.id,
+ schedule_c2.id
+ ])
+ end
+ end
+
+ context 'with no schedules' do
+ it 'does nothing' do
+ expect { migrate! }
+ .to not_change(policies, :count)
+ .and not_change(rules, :count)
+ end
+ end
+end
diff --git a/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb b/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb
new file mode 100644
index 00000000000..cec141cacc9
--- /dev/null
+++ b/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb')
+
+RSpec.describe ScheduleUpdateUsersWhereTwoFactorAuthRequiredFromGroup do
+ let(:users) { table(:users) }
+ let!(:user_1) { users.create!(require_two_factor_authentication_from_group: false, name: "user1", email: "user1@example.com", projects_limit: 1) }
+ let!(:user_2) { users.create!(require_two_factor_authentication_from_group: true, name: "user2", email: "user2@example.com", projects_limit: 1) }
+ let!(:user_3) { users.create!(require_two_factor_authentication_from_group: false, name: "user3", email: "user3@example.com", projects_limit: 1) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+ end
+
+ it 'schedules jobs for users that do not require two factor authentication' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 2.minutes, user_1.id, user_1.id)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 4.minutes, user_3.id, user_3.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/has_timelogs_report_spec.rb b/spec/models/concerns/has_timelogs_report_spec.rb
deleted file mode 100644
index f0dca47fae1..00000000000
--- a/spec/models/concerns/has_timelogs_report_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe HasTimelogsReport do
- let_it_be(:user) { create(:user) }
-
- let(:group) { create(:group) }
- let(:project) { create(:project, :public, group: group) }
- let(:issue1) { create(:issue, project: project) }
- let(:merge_request1) { create(:merge_request, source_project: project) }
-
- describe '#timelogs' do
- let_it_be(:start_time) { 20.days.ago }
- let_it_be(:end_time) { 8.days.ago }
-
- let!(:timelog1) { create_timelog(15.days.ago, issue: issue1) }
- let!(:timelog2) { create_timelog(10.days.ago, merge_request: merge_request1) }
- let!(:timelog3) { create_timelog(5.days.ago, issue: issue1) }
-
- before do
- group.add_developer(user)
- end
-
- it 'returns collection of timelogs between given times' do
- expect(group.timelogs(start_time, end_time).to_a).to match_array([timelog1, timelog2])
- end
-
- it 'returns empty collection if times are not present' do
- expect(group.timelogs(nil, nil)).to be_empty
- end
-
- it 'returns empty collection if time range is invalid' do
- expect(group.timelogs(end_time, start_time)).to be_empty
- end
- end
-
- describe '#user_can_access_group_timelogs?' do
- it 'returns true if user can access group timelogs' do
- group.add_developer(user)
-
- expect(group).to be_user_can_access_group_timelogs(user)
- end
-
- it 'returns false if user has insufficient permissions' do
- group.add_guest(user)
-
- expect(group).not_to be_user_can_access_group_timelogs(user)
- end
- end
-
- def create_timelog(time, issue: nil, merge_request: nil)
- create(:timelog, issue: issue, merge_request: merge_request, user: user, spent_at: time)
- end
-end
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index c3432907112..bc042f7a639 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -64,25 +64,57 @@ RSpec.describe Timelog do
let_it_be(:subgroup_issue) { create(:issue, project: subgroup_project) }
let_it_be(:subgroup_merge_request) { create(:merge_request, source_project: subgroup_project) }
- let_it_be(:timelog) { create(:issue_timelog, spent_at: 65.days.ago) }
- let_it_be(:timelog1) { create(:issue_timelog, spent_at: 15.days.ago, issue: group_issue) }
- let_it_be(:timelog2) { create(:issue_timelog, spent_at: 5.days.ago, issue: subgroup_issue) }
- let_it_be(:timelog3) { create(:merge_request_timelog, spent_at: 65.days.ago) }
- let_it_be(:timelog4) { create(:merge_request_timelog, spent_at: 15.days.ago, merge_request: group_merge_request) }
- let_it_be(:timelog5) { create(:merge_request_timelog, spent_at: 5.days.ago, merge_request: subgroup_merge_request) }
-
- describe 'in_group' do
+ let_it_be(:short_time_ago) { 5.days.ago }
+ let_it_be(:medium_time_ago) { 15.days.ago }
+ let_it_be(:long_time_ago) { 65.days.ago }
+
+ let_it_be(:timelog) { create(:issue_timelog, spent_at: long_time_ago) }
+ let_it_be(:timelog1) { create(:issue_timelog, spent_at: medium_time_ago, issue: group_issue) }
+ let_it_be(:timelog2) { create(:issue_timelog, spent_at: short_time_ago, issue: subgroup_issue) }
+ let_it_be(:timelog3) { create(:merge_request_timelog, spent_at: long_time_ago) }
+ let_it_be(:timelog4) { create(:merge_request_timelog, spent_at: medium_time_ago, merge_request: group_merge_request) }
+ let_it_be(:timelog5) { create(:merge_request_timelog, spent_at: short_time_ago, merge_request: subgroup_merge_request) }
+
+ describe '.in_group' do
it 'return timelogs created for group issues and merge requests' do
expect(described_class.in_group(group)).to contain_exactly(timelog1, timelog2, timelog4, timelog5)
end
end
- describe 'between_times' do
- it 'returns collection of timelogs within given times' do
- timelogs = described_class.between_times(20.days.ago, 10.days.ago)
+ describe '.at_or_after' do
+ it 'returns timelogs at the time limit' do
+ timelogs = described_class.at_or_after(short_time_ago)
- expect(timelogs).to contain_exactly(timelog1, timelog4)
+ expect(timelogs).to contain_exactly(timelog2, timelog5)
end
+
+ it 'returns timelogs after given time' do
+ timelogs = described_class.at_or_after(just_before(short_time_ago))
+
+ expect(timelogs).to contain_exactly(timelog2, timelog5)
+ end
+ end
+
+ describe '.at_or_before' do
+ it 'returns timelogs at the time limit' do
+ timelogs = described_class.at_or_before(long_time_ago)
+
+ expect(timelogs).to contain_exactly(timelog, timelog3)
+ end
+
+ it 'returns timelogs before given time' do
+ timelogs = described_class.at_or_before(just_after(long_time_ago))
+
+ expect(timelogs).to contain_exactly(timelog, timelog3)
+ end
+ end
+
+ def just_before(time)
+ time - 1.day
+ end
+
+ def just_after(time)
+ time + 1.day
end
end
end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index e677f5558fd..bbbc5d08c07 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -239,6 +239,14 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:access_api) }
end
+ context 'user with expired password' do
+ before do
+ current_user.update!(password_expires_at: 2.minutes.ago)
+ end
+
+ it { is_expected.not_to be_allowed(:access_api) }
+ end
+
context 'when terms are enforced' do
before do
enforce_terms
@@ -418,6 +426,14 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:access_git) }
end
+
+ context 'user with expired password' do
+ before do
+ current_user.update!(password_expires_at: 2.minutes.ago)
+ end
+
+ it { is_expected.not_to be_allowed(:access_git) }
+ end
end
describe 'read instance metadata' do
@@ -494,6 +510,14 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:use_slash_commands) }
end
+
+ context 'user with expired password' do
+ before do
+ current_user.update!(password_expires_at: 2.minutes.ago)
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
end
describe 'create_snippet' do
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index f5e389ff338..ee87a2da189 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -923,54 +923,4 @@ RSpec.describe GroupPolicy do
it { expect(described_class.new(current_user, subgroup)).to be_allowed(:read_label) }
end
end
-
- context 'timelogs' do
- context 'with admin' do
- let(:current_user) { admin }
-
- context 'when admin mode is enabled', :enable_admin_mode do
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'when admin mode is disabled' do
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
- end
-
- context 'with owner' do
- let(:current_user) { owner }
-
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'with maintainer' do
- let(:current_user) { maintainer }
-
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'with reporter' do
- let(:current_user) { reporter }
-
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'with guest' do
- let(:current_user) { guest }
-
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
-
- context 'with non member' do
- let(:current_user) { create(:user) }
-
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
-
- context 'with anonymous' do
- let(:current_user) { nil }
-
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
- end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 46da42a4787..d0abcfbd091 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1385,54 +1385,4 @@ RSpec.describe ProjectPolicy do
end
end
end
-
- context 'timelogs' do
- context 'with admin' do
- let(:current_user) { admin }
-
- context 'when admin mode enabled', :enable_admin_mode do
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'when admin mode disabled' do
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
- end
-
- context 'with owner' do
- let(:current_user) { owner }
-
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'with maintainer' do
- let(:current_user) { maintainer }
-
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'with reporter' do
- let(:current_user) { reporter }
-
- it { is_expected.to be_allowed(:read_group_timelogs) }
- end
-
- context 'with guest' do
- let(:current_user) { guest }
-
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
-
- context 'with non member' do
- let(:current_user) { non_member }
-
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
-
- context 'with anonymous' do
- let(:current_user) { anonymous }
-
- it { is_expected.to be_disallowed(:read_group_timelogs) }
- end
- end
end
diff --git a/spec/requests/api/graphql/group/timelogs_spec.rb b/spec/requests/api/graphql/group/timelogs_spec.rb
index 6e21a73afa9..05b6ee3ff89 100644
--- a/spec/requests/api/graphql/group/timelogs_spec.rb
+++ b/spec/requests/api/graphql/group/timelogs_spec.rb
@@ -17,8 +17,37 @@ RSpec.describe 'Timelogs through GroupQuery' do
let(:timelogs_data) { graphql_data['group']['timelogs']['nodes'] }
- before do
- group.add_developer(user)
+ context 'when the project is private' do
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project2) { create(:project, :private, group: group2) }
+ let_it_be(:issue2) { create(:issue, project: project2) }
+ let_it_be(:timelog3) { create(:timelog, issue: issue2, spent_at: '2019-08-13 14:00:00') }
+
+ subject { post_graphql(query(full_path: group2.full_path), current_user: user) }
+
+ context 'when the user is not a member of the project' do
+ it 'returns no timelogs' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(graphql_errors).to be_nil
+ expect(timelog_array.size).to eq 0
+ end
+ end
+
+ context 'when the user is a member of the project' do
+ before do
+ project2.add_developer(user)
+ end
+
+ it 'returns timelogs' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(graphql_errors).to be_nil
+ expect(timelog_array.size).to eq 1
+ end
+ end
end
context 'when the request is correct' do
@@ -74,18 +103,6 @@ RSpec.describe 'Timelogs through GroupQuery' do
expect(timelogs_data).to be_empty
end
end
-
- context 'when user has no permission to read group timelogs' do
- it 'returns empty result' do
- guest = create(:user)
- group.add_guest(guest)
- post_graphql(query, current_user: guest)
-
- expect(response).to have_gitlab_http_status(:success)
- expect(graphql_errors).to be_nil
- expect(timelogs_data).to be_empty
- end
- end
end
end
@@ -95,7 +112,7 @@ RSpec.describe 'Timelogs through GroupQuery' do
end
end
- def query(timelog_params = params)
+ def query(timelog_params: params, full_path: group.full_path)
timelog_nodes = <<~NODE
nodes {
spentAt
@@ -114,7 +131,7 @@ RSpec.describe 'Timelogs through GroupQuery' do
graphql_query_for(
:group,
- { full_path: group.full_path },
+ { full_path: full_path },
query_graphql_field(:timelogs, timelog_params, timelog_nodes)
)
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index a1e28c18769..279c65fc2f4 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -35,6 +35,26 @@ RSpec.describe 'Git HTTP requests' do
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end
end
+
+ 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, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context "when user is blocked" do
+ let(:user) { create(:user, :blocked) }
+
+ it "responds to downloads with status 401 Unauthorized" do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
context "when authentication succeeds" do
@@ -75,6 +95,15 @@ RSpec.describe 'Git HTTP requests' do
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
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)
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
context "when authentication succeeds" do
@@ -576,6 +605,16 @@ RSpec.describe 'Git HTTP requests' do
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
@@ -649,6 +688,18 @@ RSpec.describe 'Git HTTP requests' do
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
@@ -860,6 +911,16 @@ RSpec.describe 'Git HTTP requests' do
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
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 4e18c9cb4ca..0e3a0252638 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -346,9 +346,7 @@ RSpec.describe 'Git LFS API and storage' do
let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)}
let(:role) { :reporter}
- # TODO: This should return a 404 response
- # https://gitlab.com/gitlab-org/gitlab/-/issues/292006
- it_behaves_like 'LFS http 200 response'
+ it_behaves_like 'LFS http 401 response'
end
context 'when user is blocked' do
diff --git a/spec/tasks/gitlab/x509/update_rake_spec.rb b/spec/tasks/gitlab/x509/update_rake_spec.rb
index 93e97ab38ad..b166e73935a 100644
--- a/spec/tasks/gitlab/x509/update_rake_spec.rb
+++ b/spec/tasks/gitlab/x509/update_rake_spec.rb
@@ -8,12 +8,13 @@ RSpec.describe 'gitlab:x509 namespace rake task' do
end
describe 'update_signatures' do
- subject { run_rake_task('gitlab:x509:update_signatures') }
-
- let(:project) { create :project, :repository, path: X509Helpers::User1.path }
+ let(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
+ let(:project) { create(:project, :repository, path: X509Helpers::User1.path, creator: user) }
let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') }
let(:x509_commit) { Gitlab::X509::Commit.new(x509_signed_commit).signature }
+ subject { run_rake_task('gitlab:x509:update_signatures') }
+
it 'changes from unverified to verified if the certificate store contains the root certificate' do
x509_commit
@@ -22,21 +23,14 @@ RSpec.describe 'gitlab:x509 namespace rake task' do
store.add_cert(certificate)
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
- expect(x509_commit.verification_status).to eq('unverified')
expect_any_instance_of(Gitlab::X509::Commit).to receive(:update_signature!).and_call_original
-
- subject
-
- x509_commit.reload
- expect(x509_commit.verification_status).to eq('verified')
+ expect { subject }.to change { x509_commit.reload.verification_status }.from('unverified').to('verified')
end
it 'returns if no signature is available' do
- expect_any_instance_of(Gitlab::X509::Commit) do |x509_commit|
- expect(x509_commit).not_to receive(:update_signature!)
+ expect_any_instance_of(Gitlab::X509::Commit).not_to receive(:update_signature!)
- subject
- end
+ subject
end
end
end