Commit 6e4e4023 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/security/gitlab@13-12-stable-ee

parent 63a19a71
......@@ -31,6 +31,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
scopes: %w(READ WRITE DELETE),
apiVersion: 1,
apiMigrations: {
'context-qsh': true,
gdpr: true
}
}
......
......@@ -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
......
# 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
bdd82fc5cb2bbb322125c153c741002725853e23cd0ae0edbfd80563a4a87f2f
\ No newline at end of file
......@@ -24646,6 +24646,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);
......@@ -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
......
# 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
......@@ -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')
......
......@@ -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
# 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
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment