Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c3afdb42dd
commit
f7bc7dc5ea
|
@ -1 +1 @@
|
|||
7807bc4670c11dad1d2981e6d1090fe0ddd9e088
|
||||
37934de32d680c4166ec35dcd47b59fa0cc8c397
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.11.0
|
||||
2.12.0
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -195,6 +195,7 @@ export default {
|
|||
'var',
|
||||
],
|
||||
ALLOWED_ATTR: ['class', 'style', 'href', 'src'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -31,6 +31,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
|
|||
scopes: %w(READ WRITE DELETE),
|
||||
apiVersion: 1,
|
||||
apiMigrations: {
|
||||
'context-qsh': true,
|
||||
gdpr: true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]))
|
||||
end
|
||||
|
||||
def timelogs_available_for_user?
|
||||
group&.user_can_access_group_timelogs?(context[:current_user])
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
def transform_args(args)
|
||||
return args if args.keys == [:start_time, :end_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
|
||||
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
|
||||
|
||||
time_args
|
||||
end
|
||||
|
||||
def time_params_count(args)
|
||||
[:start_time, :end_time, :start_date, :end_date].count { |param| args.key?(param) }
|
||||
end
|
||||
def build_parsed_args(args)
|
||||
if times_provided?(args)
|
||||
@parsed_args = args
|
||||
else
|
||||
@parsed_args = args.except(:start_date, :end_date)
|
||||
|
||||
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'
|
||||
@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 times_provided?(args)
|
||||
args[:start_time] && args[:end_time]
|
||||
end
|
||||
|
||||
def validate_time_difference!
|
||||
return unless end_time_before_start_time?
|
||||
|
||||
raise_argument_error('Start argument must be before End argument')
|
||||
end
|
||||
|
||||
def end_time_before_start_time?
|
||||
times_provided?(parsed_args) && parsed_args[:end_time] < parsed_args[:start_time]
|
||||
end
|
||||
|
||||
def build_timelogs
|
||||
@timelogs = Timelog.in_group(object)
|
||||
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
|
||||
|
|
|
@ -4,7 +4,7 @@ module Types
|
|||
class TimelogType < BaseObject
|
||||
graphql_name 'Timelog'
|
||||
|
||||
authorize :read_group_timelogs
|
||||
authorize :read_issue
|
||||
|
||||
field :spent_at,
|
||||
Types::TimeType,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -80,6 +80,10 @@ module PolicyActor
|
|||
def can_read_all_resources?
|
||||
false
|
||||
end
|
||||
|
||||
def password_expired?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
PolicyActor.prepend_mod_with('PolicyActor')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TimelogPolicy < BasePolicy
|
||||
delegate { @subject.issuable.resource_parent }
|
||||
delegate { @subject.issuable }
|
||||
end
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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}";
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
bdd82fc5cb2bbb322125c153c741002725853e23cd0ae0edbfd80563a4a87f2f
|
|
@ -0,0 +1 @@
|
|||
6c687ffd41f242dcd0ecf1ff82652aba79130d2d54016729a817dafa0bac6184
|
|
@ -0,0 +1 @@
|
|||
88d2c1507503de626dfdb3f2f0eaf0f51fad5fc2279fd147d901c5dcc7ae91eb
|
|
@ -0,0 +1 @@
|
|||
2c5c0756757a181cf8bf7968de5184664004a82c093ae3fc14c5d6931a1ab44f
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 | | ✓ | ✓ | ✓ | ✓ |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -27077,6 +27077,9 @@ msgstr ""
|
|||
msgid "Redirect to SAML provider to test configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Redirecting"
|
||||
msgstr ""
|
||||
|
||||
msgid "Redis"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -70,77 +70,60 @@ 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
|
||||
context 'when the user is confirmed' do
|
||||
let(:confirmed_at) { 1.hour.ago }
|
||||
|
||||
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')
|
||||
|
||||
allow(Doorkeeper.configuration).to receive(:scopes).and_return(scopes)
|
||||
|
||||
create(:oauth_access_token, application: application, resource_owner_id: user.id, scopes: scopes)
|
||||
end
|
||||
|
||||
it 'authorizes the request and shows the user a page that redirects' do
|
||||
subject
|
||||
|
||||
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 'without valid params' do
|
||||
it 'returns 200 code and renders error view' do
|
||||
get :new
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template('doorkeeper/authorizations/error')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
render_views
|
||||
|
||||
it 'returns 200 and renders view with correct info', :aggregate_failures do
|
||||
subject
|
||||
|
||||
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
|
||||
|
||||
subject { get :new, params: params }
|
||||
|
||||
context 'when auth app owner is a user' do
|
||||
context 'with valid params' do
|
||||
it_behaves_like 'render views'
|
||||
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_behaves_like 'render views'
|
||||
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') }
|
||||
|
||||
it_behaves_like 'render views'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no owner associated' do
|
||||
let(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) }
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
context 'without valid params' do
|
||||
it 'returns 200 code and renders error view' do
|
||||
get :new
|
||||
|
||||
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
|
||||
|
||||
expect(request.session['user_return_to']).to be_nil
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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(: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(:short_time_ago) { 5.days.ago.beginning_of_day }
|
||||
let_it_be(:medium_time_ago) { 15.days.ago.beginning_of_day }
|
||||
|
||||
let(:args) { { start_time: 6.days.ago, end_time: 2.days.ago.noon } }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
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
|
||||
|
||||
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 } }
|
||||
|
||||
expect(resolve_timelogs(user: user, **args)).to be_empty
|
||||
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(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
|
||||
|
|
|
@ -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'] }
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(short_text)
|
||||
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(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(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(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)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -624,6 +624,7 @@ metrics_setting:
|
|||
- project
|
||||
protected_environments:
|
||||
- project
|
||||
- group
|
||||
- deploy_access_levels
|
||||
deploy_access_levels:
|
||||
- protected_environment
|
||||
|
|
|
@ -701,6 +701,7 @@ ProjectSetting:
|
|||
ProtectedEnvironment:
|
||||
- id
|
||||
- project_id
|
||||
- group_id
|
||||
- name
|
||||
- created_at
|
||||
- updated_at
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue