summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2021-06-01 18:25:56 +0000
committerGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2021-06-01 18:25:56 +0000
commitf45007ed211f97a416bd9362b88ab450bd618e02 (patch)
tree80cd24b9c0ed31e1f65bef353561580b286484d2
parent46446d3c712db4f42babb21c454df43f2167847a (diff)
parente1e95e4aa59a01ea53c41f68ec6a099d41d2a838 (diff)
downloadgitlab-ce-13-10-stable.tar.gz
Merge remote-tracking branch 'dev/13-10-stable' into 13-10-stable13-10-stable
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml27
-rw-r--r--CHANGELOG.md15
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile.lock2
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue1
-rw-r--r--app/controllers/jira_connect/app_descriptor_controller.rb1
-rw-r--r--app/controllers/oauth/authorizations_controller.rb3
-rw-r--r--app/helpers/markup_helper.rb1
-rw-r--r--app/policies/concerns/policy_actor.rb4
-rw-r--r--app/policies/global_policy.rb10
-rw-r--r--app/views/doorkeeper/authorizations/redirect.html.haml10
-rw-r--r--config/initializers/lograge.rb1
-rw-r--r--db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb35
-rw-r--r--db/schema_migrations/202105191540581
-rw-r--r--db/structure.sql2
-rw-r--r--lib/api/lint.rb2
-rw-r--r--lib/banzai/filter/absolute_link_filter.rb5
-rw-r--r--lib/banzai/filter/ascii_doc_post_processing_filter.rb10
-rw-r--r--lib/banzai/filter/base_relative_link_filter.rb15
-rw-r--r--lib/banzai/filter/color_filter.rb5
-rw-r--r--lib/banzai/filter/custom_emoji_filter.rb2
-rw-r--r--lib/banzai/filter/emoji_filter.rb2
-rw-r--r--lib/banzai/filter/footnote_filter.rb12
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb2
-rw-r--r--lib/banzai/filter/image_lazy_load_filter.rb5
-rw-r--r--lib/banzai/filter/inline_diff_filter.rb2
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb3
-rw-r--r--lib/banzai/filter/kroki_filter.rb5
-rw-r--r--lib/banzai/filter/markdown_post_escape_filter.rb9
-rw-r--r--lib/banzai/filter/math_filter.rb9
-rw-r--r--lib/banzai/filter/mermaid_filter.rb5
-rw-r--r--lib/banzai/filter/plantuml_filter.rb7
-rw-r--r--lib/banzai/filter/suggestion_filter.rb5
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb5
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb5
-rw-r--r--lib/banzai/filter/truncate_source_filter.rb21
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb13
-rw-r--r--lib/gitlab/auth.rb12
-rw-r--r--lib/gitlab/auth/user_access_denied_reason.rb5
-rw-r--r--lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb129
-rw-r--r--lib/gitlab/current_settings.rb4
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb5
-rw-r--r--lib/gitlab/utils.rb18
-rw-r--r--lib/gitlab/utils/nokogiri.rb24
-rw-r--r--lib/gitlab/x509/signature.rb10
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/controllers/oauth/authorizations_controller_spec.rb22
-rw-r--r--spec/features/users/login_spec.rb19
-rw-r--r--spec/frontend/notebook/cells/markdown_spec.js13
-rw-r--r--spec/helpers/markup_helper_spec.rb7
-rw-r--r--spec/initializers/lograge_spec.rb21
-rw-r--r--spec/lib/banzai/filter/truncate_source_filter_spec.rb70
-rw-r--r--spec/lib/gitlab/auth/user_access_denied_reason_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb84
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb36
-rw-r--r--spec/lib/gitlab/git_access_spec.rb14
-rw-r--r--spec/lib/gitlab/utils/nokogiri_spec.rb34
-rw-r--r--spec/lib/gitlab/utils_spec.rb23
-rw-r--r--spec/lib/gitlab/x509/signature_spec.rb77
-rw-r--r--spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb29
-rw-r--r--spec/policies/global_policy_spec.rb24
-rw-r--r--spec/requests/api/lint_spec.rb28
-rw-r--r--spec/requests/git_http_spec.rb61
-rw-r--r--spec/requests/lfs_http_spec.rb4
-rw-r--r--spec/tasks/gitlab/x509/update_rake_spec.rb20
66 files changed, 920 insertions, 150 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index e1ddefca99e..bc1fcc1af16 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -358,33 +358,6 @@ db:check-migrations:
- scripts/validate_migration_schema
allow_failure: true
-db:migrate-from-v12.10.0:
- extends: .db-job-base
- variables:
- SETUP_DB: "false"
- script:
- - export PROJECT_TO_CHECKOUT="gitlab"
- - export TAG_TO_CHECKOUT="v12.10.0-ee"
- - '[[ -d "ee/" ]] || export PROJECT_TO_CHECKOUT="gitlab-foss"'
- - '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="v12.10.0"'
- - retry 'git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT'
- - git checkout -f FETCH_HEAD
- - sed -i -e "s/gem 'grpc', '~> 1.24.0'/gem 'grpc', '~> 1.30.2'/" Gemfile # Update gRPC for Ruby 2.7
- - sed -i -e "s/gem 'google-protobuf', '~> 3.8.0'/gem 'google-protobuf', '~> 3.12.0'/" Gemfile
- - gem install bundler:1.17.3
- - bundle update google-protobuf grpc bootsnap
- - bundle install $BUNDLE_INSTALL_FLAGS
- - date
- - cp config/gitlab.yml.example config/gitlab.yml
- - bundle exec rake db:drop db:create db:structure:load db:seed_fu
- - date
- - git checkout -f $CI_COMMIT_SHA
- - bundle install $BUNDLE_INSTALL_FLAGS
- - date
- - . scripts/prepare_build.sh
- - date
- - bundle exec rake db:migrate
-
db:rollback:
extends: .db-job-base
script:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9436b873d6..3816661966d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,21 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 13.10.5 (2021-06-01)
+
+### Security (10 changes)
+
+- [Bump BinData version](gitlab-org/security/gitlab@784ebe7cbbad4fceef3e5438a7a537003f7840d2) ([merge request](gitlab-org/security/gitlab!1402))
+- [Updates authorization for lint](gitlab-org/security/gitlab@c588e94585f3f9666729cae4ba7be700bfd073f0) ([merge request](gitlab-org/security/gitlab!1449))
+- [Adds redirect page to OAuth](gitlab-org/security/gitlab@7881e5bc040919d165e51a09dcb8895b42bc036a) ([merge request](gitlab-org/security/gitlab!1443))
+- [Block access to GitLab for users with expired password](gitlab-org/security/gitlab@aaee1c5ac0eaac9a4eb4c89fca70c12a81381171) ([merge request](gitlab-org/security/gitlab!1444))
+- [Update users two factor required from group](gitlab-org/security/gitlab@a5ae9d82fc9088ab971efb2adc0366efbf33f5bd) ([merge request](gitlab-org/security/gitlab!1434))
+- [Opt in to Atlassians new context qsh](gitlab-org/security/gitlab@0ea617b799c80a15f7d02ddcb4d89cc2e38894d4) ([merge request](gitlab-org/security/gitlab!1406))
+- [Only verify commit signatures if the user email is verified](gitlab-org/security/gitlab@416d70aa3be490f9bf07e7a381e5526515193dec) ([merge request](gitlab-org/security/gitlab!1387))
+- [Prevent XSS on notebooks](gitlab-org/security/gitlab@9029bfc7bd83e3d69e825b16d6f5d08c2b97fd8e) ([merge request](gitlab-org/security/gitlab!1423))
+- [Use xpath search of Nokogiri instead of css search](gitlab-org/security/gitlab@bad2794269c233e11cc7171416a2234d46fa166f) ([merge request](gitlab-org/security/gitlab!1417))
+- [Truncate all non-blob markdown to 1MB by default](gitlab-org/security/gitlab@2e9f9506f8d0f1c2be0a7b0a7b894a888cd98518) ([merge request](gitlab-org/security/gitlab!1418))
+
## 13.10.4 (2021-04-27)
### Security (6 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 402deed3074..f70f014e475 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-13.10.4 \ No newline at end of file
+13.10.5 \ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index b5bbd5a61d6..c0a95042e01 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -142,7 +142,7 @@ GEM
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
- bindata (2.4.8)
+ bindata (2.4.10)
binding_ninja (0.2.3)
bootsnap (1.4.6)
msgpack (~> 1.0)
diff --git a/VERSION b/VERSION
index 402deed3074..f70f014e475 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-13.10.4 \ No newline at end of file
+13.10.5 \ No newline at end of file
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index e4cde0d4ff3..0a881843970 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -157,6 +157,7 @@ export default {
'var',
],
ALLOWED_ATTR: ['class', 'style', 'href', 'src'],
+ ALLOW_DATA_ATTR: false,
});
},
},
diff --git a/app/controllers/jira_connect/app_descriptor_controller.rb b/app/controllers/jira_connect/app_descriptor_controller.rb
index 137f830e40b..fee8b43aa6b 100644
--- a/app/controllers/jira_connect/app_descriptor_controller.rb
+++ b/app/controllers/jira_connect/app_descriptor_controller.rb
@@ -31,6 +31,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
scopes: %w(READ WRITE DELETE),
apiVersion: 1,
apiMigrations: {
+ 'context-qsh': true,
gdpr: true
}
}
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index 857f36e3833..ddf70c1892a 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -14,8 +14,9 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
if pre_auth.authorizable?
if skip_authorization? || matching_token?
auth = authorization.authorize
+ parsed_redirect_uri = URI.parse(auth.redirect_uri)
session.delete(:user_return_to)
- redirect_to auth.redirect_uri
+ render "doorkeeper/authorizations/redirect", locals: { redirect_uri: parsed_redirect_uri }, layout: false
else
render "doorkeeper/authorizations/new"
end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index ad206d0e5b5..6c19fcc9121 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -118,6 +118,7 @@ module MarkupHelper
def markup(file_name, text, context = {})
context[:project] ||= @project
+ context[:text_source] ||= :blob
html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
prepare_for_rendering(html, context)
end
diff --git a/app/policies/concerns/policy_actor.rb b/app/policies/concerns/policy_actor.rb
index 75849fb10c8..d254b87ae1f 100644
--- a/app/policies/concerns/policy_actor.rb
+++ b/app/policies/concerns/policy_actor.rb
@@ -80,6 +80,10 @@ module PolicyActor
def can_read_all_resources?
false
end
+
+ def password_expired?
+ false
+ end
end
PolicyActor.prepend_if_ee('EE::PolicyActor')
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index d16c4734b2c..2bdccf83044 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -15,6 +15,10 @@ class GlobalPolicy < BasePolicy
@user&.required_terms_not_accepted?
end
+ condition(:password_expired, scope: :user) do
+ @user&.password_expired?
+ end
+
condition(:project_bot, scope: :user) { @user&.project_bot? }
condition(:migration_bot, scope: :user) { @user&.migration_bot? }
@@ -73,6 +77,12 @@ class GlobalPolicy < BasePolicy
prevent :access_git
end
+ rule { password_expired }.policy do
+ prevent :access_api
+ prevent :access_git
+ prevent :use_slash_commands
+ end
+
rule { can_create_group }.policy do
enable :create_group
end
diff --git a/app/views/doorkeeper/authorizations/redirect.html.haml b/app/views/doorkeeper/authorizations/redirect.html.haml
new file mode 100644
index 00000000000..0643615480a
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/redirect.html.haml
@@ -0,0 +1,10 @@
+%h3.page-title= _("Redirecting")
+
+%div
+ -# haml-lint:disable NoPlainNodes
+ %a{ :href => redirect_uri } Click here to redirect to #{redirect_uri}
+ -# haml-lint:enable NoPlainNodes
+
+= javascript_tag do
+ :plain
+ window.location= "#{redirect_uri}";
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index e8479bc6aa4..61e357808d9 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -18,6 +18,7 @@ unless Gitlab::Runtime.sidekiq?
data[:db_duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:db)) if data[:db]
data[:view_duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:view)) if data[:view]
data[:duration_s] = Gitlab::Utils.ms_to_round_sec(data.delete(:duration)) if data[:duration]
+ data[:location] = Gitlab::Utils.removes_sensitive_data_from_url(data[:location]) if data[:location]
# Remove empty hashes to prevent type mismatches
# These are set to empty hashes in Lograge's ActionCable subscriber
diff --git a/db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb b/db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb
new file mode 100644
index 00000000000..a25ef0b0b33
--- /dev/null
+++ b/db/migrate/20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class ScheduleUpdateUsersWhereTwoFactorAuthRequiredFromGroup < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION = 'UpdateUsersWhereTwoFactorAuthRequiredFromGroup'
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 10_000
+ INDEX_NAME = 'index_users_require_two_factor_authentication_from_group_false'
+
+ disable_ddl_transaction!
+
+ class User < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'users'
+ end
+
+ def up
+ add_concurrent_index :users,
+ :require_two_factor_authentication_from_group,
+ where: 'require_two_factor_authentication_from_group = FALSE',
+ name: INDEX_NAME
+
+ relation = User.where(require_two_factor_authentication_from_group: false)
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ relation, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+
+ def down
+ remove_concurrent_index_by_name :users, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20210519154058 b/db/schema_migrations/20210519154058
new file mode 100644
index 00000000000..9bd277e92db
--- /dev/null
+++ b/db/schema_migrations/20210519154058
@@ -0,0 +1 @@
+bdd82fc5cb2bbb322125c153c741002725853e23cd0ae0edbfd80563a4a87f2f \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 8936087bd2b..3d235d7b241 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -23929,6 +23929,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);
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index e0806674c6a..945cdf3edb2 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -11,7 +11,7 @@ module API
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
end
post '/lint' do
- unauthorized! if Gitlab::CurrentSettings.signup_disabled? && current_user.nil?
+ unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil?
result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute
diff --git a/lib/banzai/filter/absolute_link_filter.rb b/lib/banzai/filter/absolute_link_filter.rb
index a9bdb004c4b..cc7bf3ed556 100644
--- a/lib/banzai/filter/absolute_link_filter.rb
+++ b/lib/banzai/filter/absolute_link_filter.rb
@@ -6,10 +6,13 @@ module Banzai
module Filter
# HTML filter that converts relative urls into absolute ones.
class AbsoluteLinkFilter < HTML::Pipeline::Filter
+ CSS = 'a.gfm'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
return doc unless context[:only_path] == false
- doc.search('a.gfm').each do |el|
+ doc.xpath(XPATH).each do |el|
process_link_attr el.attribute('href')
end
diff --git a/lib/banzai/filter/ascii_doc_post_processing_filter.rb b/lib/banzai/filter/ascii_doc_post_processing_filter.rb
index 09f0fd7df45..83c729e13b5 100644
--- a/lib/banzai/filter/ascii_doc_post_processing_filter.rb
+++ b/lib/banzai/filter/ascii_doc_post_processing_filter.rb
@@ -3,14 +3,20 @@
module Banzai
module Filter
class AsciiDocPostProcessingFilter < HTML::Pipeline::Filter
+ CSS_MATH = '[data-math-style]'
+ XPATH_MATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_MATH).freeze
+ CSS_MERM = '[data-mermaid-style]'
+ XPATH_MERM = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_MERM).freeze
+
def call
- doc.search('[data-math-style]').each do |node|
+ doc.xpath(XPATH_MATH).each do |node|
node.set_attribute('class', 'code math js-render-math')
end
- doc.search('[data-mermaid-style]').each do |node|
+ doc.xpath(XPATH_MERM).each do |node|
node.set_attribute('class', 'js-render-mermaid')
end
+
doc
end
end
diff --git a/lib/banzai/filter/base_relative_link_filter.rb b/lib/banzai/filter/base_relative_link_filter.rb
index fd526df4c48..60d09b69a10 100644
--- a/lib/banzai/filter/base_relative_link_filter.rb
+++ b/lib/banzai/filter/base_relative_link_filter.rb
@@ -7,23 +7,20 @@ 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
strong_memoize(:linkable_attributes) do
attrs = []
- attrs += doc.search('a:not(.gfm)').map do |el|
- el.attribute('href')
- end
-
- attrs += doc.search('img:not(.gfm), video:not(.gfm), audio:not(.gfm)').flat_map do |el|
- [el.attribute('src'), el.attribute('data-src')]
+ attrs += doc.xpath(XPATH).flat_map do |el|
+ [el.attribute('href'), el.attribute('src'), el.attribute('data-src')]
end
- attrs.reject do |attr|
- attr.blank? || attr.value.start_with?('//')
- end
+ attrs.reject { |attr| attr.blank? || attr.value.start_with?('//') }
end
end
diff --git a/lib/banzai/filter/color_filter.rb b/lib/banzai/filter/color_filter.rb
index 0aca7441638..58e9b8cdba1 100644
--- a/lib/banzai/filter/color_filter.rb
+++ b/lib/banzai/filter/color_filter.rb
@@ -7,8 +7,11 @@ module Banzai
class ColorFilter < HTML::Pipeline::Filter
COLOR_CHIP_CLASS = 'gfm-color_chip'
+ CSS = 'code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.css('code').each do |node|
+ doc.xpath(XPATH).each do |node|
color = ColorParser.parse(node.content)
node << color_chip(color) if color
end
diff --git a/lib/banzai/filter/custom_emoji_filter.rb b/lib/banzai/filter/custom_emoji_filter.rb
index 1ee8f4e31e8..e26c5d36f2b 100644
--- a/lib/banzai/filter/custom_emoji_filter.rb
+++ b/lib/banzai/filter/custom_emoji_filter.rb
@@ -9,7 +9,7 @@ module Banzai
return doc unless context[:project]
return doc unless Feature.enabled?(:custom_emoji, context[:project])
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
content = node.to_html
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index 8952a3ff6b4..9d24bf028b6 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -11,7 +11,7 @@ module Banzai
IGNORE_UNICODE_EMOJIS = %w(™ © ®).freeze
def call
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
content = node.to_html
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb
index 5474242e03c..0f856dc0eb9 100644
--- a/lib/banzai/filter/footnote_filter.rb
+++ b/lib/banzai/filter/footnote_filter.rb
@@ -23,17 +23,23 @@ module Banzai
FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}\d+\z/.freeze
FOOTNOTE_START_NUMBER = 1
+ CSS_SECTION = "ol > li[id=#{FOOTNOTE_ID_PREFIX}#{FOOTNOTE_START_NUMBER}]"
+ XPATH_SECTION = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION).freeze
+ CSS_FOOTNOTE = 'sup > a[id]'
+ XPATH_FOOTNOTE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_FOOTNOTE).freeze
+
def call
- return doc unless first_footnote = doc.at_css("ol > li[id=#{fn_id(FOOTNOTE_START_NUMBER)}]")
+ return doc unless first_footnote = doc.at_xpath(XPATH_SECTION)
# Sanitization stripped off the section wrapper - add it back in
first_footnote.parent.wrap('<section class="footnotes">')
rand_suffix = "-#{random_number}"
modified_footnotes = {}
- doc.css('sup > a[id]').each do |link_node|
+ doc.xpath(XPATH_FOOTNOTE).each do |link_node|
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
- footnote_node = doc.at_css("li[id=#{fn_id(ref_num)}]")
+ node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath("li[id=#{fn_id(ref_num)}]")
+ footnote_node = doc.at_xpath(node_xpath)
if INTEGER_PATTERN.match?(ref_num) && (footnote_node || modified_footnotes[ref_num])
link_node[:href] += rand_suffix
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 8a7d3c49ffb..56489c94fc8 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -60,7 +60,7 @@ module Banzai
IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
def call
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
next unless node.content =~ TAGS_PATTERN
diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb
index d8b9eb29cf5..916c135b777 100644
--- a/lib/banzai/filter/image_lazy_load_filter.rb
+++ b/lib/banzai/filter/image_lazy_load_filter.rb
@@ -6,8 +6,11 @@ module Banzai
# HTML filter that moves the value of image `src` attributes to `data-src`
# so they can be lazy loaded.
class ImageLazyLoadFilter < HTML::Pipeline::Filter
+ CSS = 'img'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.xpath('descendant-or-self::img').each do |img|
+ doc.xpath(XPATH).each do |img|
img.add_class('lazy')
img['data-src'] = img['src']
img['src'] = LazyImageTagHelper.placeholder_image
diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb
index 5a1c0bee32d..e47ff15e7b7 100644
--- a/lib/banzai/filter/inline_diff_filter.rb
+++ b/lib/banzai/filter/inline_diff_filter.rb
@@ -7,7 +7,7 @@ module Banzai
IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
def call
- doc.search(".//text()").each do |node|
+ doc.xpath('descendant-or-self::text()').each do |node|
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
content = node.to_html
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
index 2259115acfc..b256815ae84 100644
--- a/lib/banzai/filter/inline_metrics_redactor_filter.rb
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -8,6 +8,7 @@ module Banzai
include Gitlab::Utils::StrongMemoize
METRICS_CSS_CLASS = '.js-render-metrics'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(METRICS_CSS_CLASS).freeze
EMBED_LIMIT = 100
Route = Struct.new(:regex, :permission)
@@ -41,7 +42,7 @@ module Banzai
# @return [Nokogiri::XML::NodeSet]
def nodes
strong_memoize(:nodes) do
- nodes = doc.css(METRICS_CSS_CLASS)
+ nodes = doc.xpath(XPATH)
nodes.drop(EMBED_LIMIT).each(&:remove)
nodes
diff --git a/lib/banzai/filter/kroki_filter.rb b/lib/banzai/filter/kroki_filter.rb
index dbd4de32a47..3803302c324 100644
--- a/lib/banzai/filter/kroki_filter.rb
+++ b/lib/banzai/filter/kroki_filter.rb
@@ -15,10 +15,11 @@ module Banzai
.map { |diagram_type| %(pre[lang="#{diagram_type}"] > code) }
.join(', ')
- return doc unless doc.at(diagram_selectors)
+ xpath = Gitlab::Utils::Nokogiri.css_to_xpath(diagram_selectors)
+ return doc unless doc.at_xpath(xpath)
diagram_format = "svg"
- doc.css(diagram_selectors).each do |node|
+ doc.xpath(xpath).each do |node|
diagram_type = node.parent['lang']
img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img src="#{create_image_src(diagram_type, diagram_format, node.content)}"/>))
node.parent.replace(img_tag)
diff --git a/lib/banzai/filter/markdown_post_escape_filter.rb b/lib/banzai/filter/markdown_post_escape_filter.rb
index ad32e9afbf5..b69afdcfebe 100644
--- a/lib/banzai/filter/markdown_post_escape_filter.rb
+++ b/lib/banzai/filter/markdown_post_escape_filter.rb
@@ -8,6 +8,11 @@ module Banzai
NOT_LITERAL_REGEX = %r{#{LITERAL_KEYWORD}-((%5C|\\).+?)-#{LITERAL_KEYWORD}}.freeze
SPAN_REGEX = %r{<span>(.*?)</span>}.freeze
+ CSS_A = 'a'
+ XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze
+ CSS_CODE = 'code'
+ XPATH_CODE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_CODE).freeze
+
def call
return doc unless result[:escaped_literals]
@@ -24,12 +29,12 @@ module Banzai
# Banzai::Renderer::CommonMark::HTML. However, we eventually want to use
# the built-in compiled renderer, rather than the ruby version, for speed.
# So let's do this work here.
- doc.css('a').each do |node|
+ doc.xpath(XPATH_A).each do |node|
node.attributes['href'].value = node.attributes['href'].value.gsub(SPAN_REGEX, '\1') if node.attributes['href']
node.attributes['title'].value = node.attributes['title'].value.gsub(SPAN_REGEX, '\1') if node.attributes['title']
end
- doc.css('code').each do |node|
+ doc.xpath(XPATH_CODE).each do |node|
node.attributes['lang'].value = node.attributes['lang'].value.gsub(SPAN_REGEX, '\1') if node.attributes['lang']
end
diff --git a/lib/banzai/filter/math_filter.rb b/lib/banzai/filter/math_filter.rb
index c915f0ee35b..7173e16e547 100644
--- a/lib/banzai/filter/math_filter.rb
+++ b/lib/banzai/filter/math_filter.rb
@@ -10,6 +10,11 @@ module Banzai
# HTML filter that adds class="code math" and removes the dollar sign in $`2+2`$.
#
class MathFilter < HTML::Pipeline::Filter
+ CSS_MATH = 'pre.code.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.math').each do |el|
+ doc.xpath(XPATH_MATH).each do |el|
el[STYLE_ATTRIBUTE] = 'display'
el[:class] += " #{TAG_CLASS}"
end
diff --git a/lib/banzai/filter/mermaid_filter.rb b/lib/banzai/filter/mermaid_filter.rb
index f0adb83af8a..aaaf851ccf0 100644
--- a/lib/banzai/filter/mermaid_filter.rb
+++ b/lib/banzai/filter/mermaid_filter.rb
@@ -4,8 +4,11 @@
module Banzai
module Filter
class MermaidFilter < HTML::Pipeline::Filter
+ CSS = 'pre[lang="mermaid"] > code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.css('pre[lang="mermaid"] > code').add_class('js-render-mermaid')
+ doc.xpath(XPATH).add_class('js-render-mermaid')
doc
end
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
index 37d4126c1ba..93370178a61 100644
--- a/lib/banzai/filter/plantuml_filter.rb
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -8,12 +8,15 @@ module Banzai
# HTML that replaces all `code plantuml` tags with PlantUML img tags.
#
class PlantumlFilter < HTML::Pipeline::Filter
+ CSS = 'pre > code[lang="plantuml"]'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- return doc unless settings.plantuml_enabled? && doc.at('pre > code[lang="plantuml"]')
+ return doc unless settings.plantuml_enabled? && doc.at_xpath(XPATH)
plantuml_setup
- doc.css('pre > code[lang="plantuml"]').each do |node|
+ doc.xpath(XPATH).each do |node|
img_tag = Nokogiri::HTML::DocumentFragment.parse(
Asciidoctor::PlantUml::Processor.plantuml_content(node.content, {}))
node.parent.replace(img_tag)
diff --git a/lib/banzai/filter/suggestion_filter.rb b/lib/banzai/filter/suggestion_filter.rb
index ae093580001..bf893f177fd 100644
--- a/lib/banzai/filter/suggestion_filter.rb
+++ b/lib/banzai/filter/suggestion_filter.rb
@@ -7,10 +7,13 @@ module Banzai
# Class used for tagging elements that should be rendered
TAG_CLASS = 'js-render-suggestion'
+ CSS = 'pre.suggestion > code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
return doc unless suggestions_filter_enabled?
- doc.search('pre.suggestion > code').each do |node|
+ doc.xpath(XPATH).each do |node|
node.add_class(TAG_CLASS)
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 1d3bbe43344..90b1fa1040a 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -14,8 +14,11 @@ module Banzai
PARAMS_DELIMITER = ':'
LANG_PARAMS_ATTR = 'data-lang-params'
+ CSS = 'pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
- doc.search('pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code').each do |node|
+ doc.xpath(XPATH).each do |node|
highlight_node(node)
end
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index b362607aed2..13ca9cde567 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -19,6 +19,9 @@ module Banzai
class TableOfContentsFilter < HTML::Pipeline::Filter
include Gitlab::Utils::Markdown
+ CSS = 'h1, h2, h3, h4, h5, h6'
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
def call
return doc if context[:no_header_anchors]
@@ -27,7 +30,7 @@ module Banzai
headers = Hash.new(0)
header_root = current_header = HeaderNode.new
- doc.css('h1, h2, h3, h4, h5, h6').each do |node|
+ doc.xpath(XPATH).each do |node|
if header_content = node.children.first
id = string_to_anchor(node.text)
diff --git a/lib/banzai/filter/truncate_source_filter.rb b/lib/banzai/filter/truncate_source_filter.rb
index 44f88b253d9..a21d4a44295 100644
--- a/lib/banzai/filter/truncate_source_filter.rb
+++ b/lib/banzai/filter/truncate_source_filter.rb
@@ -3,12 +3,29 @@
module Banzai
module Filter
class TruncateSourceFilter < HTML::Pipeline::TextFilter
+ CHARACTER_COUNT_LIMIT = 1.megabyte
+ USER_MSG_LIMIT = 10_000
+
def call
- return text unless context.key?(:limit)
+ # don't truncate if it's a :blob and no limit is set
+ return text if context[:text_source] == :blob && !context.key?(:limit)
+
+ limit = context[:limit] || CHARACTER_COUNT_LIMIT
+
+ # no sense in allowing `truncate_bytes` to duplicate a large
+ # string unless it's too big
+ return text if text.bytesize <= limit
# Use three dots instead of the ellipsis Unicode character because
# some clients show the raw Unicode value in the merge commit.
- text.truncate_bytes(context[:limit], omission: '...')
+ trunc = text.truncate_bytes(limit, omission: '...')
+
+ # allows us to indicate to the user that what they see is a truncated copy
+ if limit > USER_MSG_LIMIT
+ trunc.prepend("_The text is longer than #{limit} characters and has been visually truncated._\n\n")
+ end
+
+ trunc
end
end
end
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 44f13612fde..2b95d87ff8e 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -10,14 +10,21 @@ module Banzai
class WikiLinkFilter < HTML::Pipeline::Filter
include Gitlab::Utils::SanitizeNodeLink
+ CSS_A = 'a:not(.gfm)'
+ XPATH_A = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_A).freeze
+ CSS_VA = 'video, audio'
+ XPATH_VA = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_VA).freeze
+ CSS_IMG = 'img'
+ XPATH_IMG = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_IMG).freeze
+
def call
return doc unless wiki?
- doc.search('a:not(.gfm)').each { |el| process_link(el.attribute('href'), el) }
+ doc.xpath(XPATH_A).each { |el| process_link(el.attribute('href'), el) }
- doc.search('video, audio').each { |el| process_link(el.attribute('src'), el) }
+ doc.xpath(XPATH_VA).each { |el| process_link(el.attribute('src'), el) }
- doc.search('img').each do |el|
+ doc.xpath(XPATH_IMG).each do |el|
attr = el.attribute('data-src') || el.attribute('src')
process_link(attr, el)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 1f5cce249d8..44561ee6d4a 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -84,7 +84,7 @@ module Gitlab
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
user = User.by_login(login)
- break if user && !user.can?(:log_in)
+ break if user && !can_user_login_with_non_expired_password?(user)
authenticators = []
@@ -182,7 +182,7 @@ module Gitlab
if valid_oauth_token?(token)
user = User.id_in(token.resource_owner_id).first
- return unless user&.can?(:log_in)
+ return unless user && can_user_login_with_non_expired_password?(user)
Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
end
@@ -200,7 +200,7 @@ module Gitlab
return if project && token.user.project_bot? && !project.bots.include?(token.user)
- if token.user.can?(:log_in) || token.user.project_bot?
+ if can_user_login_with_non_expired_password?(token.user) || token.user.project_bot?
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
end
end
@@ -285,7 +285,7 @@ module Gitlab
return unless build.project.builds_enabled?
if build.user
- return unless build.user.can?(:log_in) || (build.user.project_bot? && build.project.bots&.include?(build.user))
+ return unless can_user_login_with_non_expired_password?(build.user) || (build.user.project_bot? && build.project.bots&.include?(build.user))
# If user is assigned to build, use restricted credentials of user
Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
@@ -380,6 +380,10 @@ module Gitlab
user.increment_failed_attempts!
end
+
+ def can_user_login_with_non_expired_password?(user)
+ user.can?(:log_in) && !user.password_expired?
+ end
end
end
end
diff --git a/lib/gitlab/auth/user_access_denied_reason.rb b/lib/gitlab/auth/user_access_denied_reason.rb
index 36b54ba2e46..6639000dba8 100644
--- a/lib/gitlab/auth/user_access_denied_reason.rb
+++ b/lib/gitlab/auth/user_access_denied_reason.rb
@@ -23,6 +23,9 @@ module Gitlab
"Your primary email address is not confirmed. "\
"Please check your inbox for the confirmation instructions. "\
"In case the link is expired, you can request a new confirmation email at #{Rails.application.routes.url_helpers.new_user_confirmation_url}"
+ when :password_expired
+ "Your password expired. "\
+ "Please access GitLab from a web browser to update your password."
else
"Your account has been blocked."
end
@@ -41,6 +44,8 @@ module Gitlab
:deactivated
elsif !@user.confirmed?
:unconfirmed
+ elsif @user.password_expired?
+ :password_expired
else
:blocked
end
diff --git a/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb b/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
new file mode 100644
index 00000000000..f5ba9e63333
--- /dev/null
+++ b/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class UpdateUsersWhereTwoFactorAuthRequiredFromGroup # rubocop:disable Metrics/ClassLength
+ def perform(start_id, stop_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ UPDATE
+ users
+ SET
+ require_two_factor_authentication_from_group = TRUE
+ WHERE
+ users.id BETWEEN #{start_id}
+ AND #{stop_id}
+ AND users.require_two_factor_authentication_from_group = FALSE
+ AND users.id IN (
+ SELECT
+ DISTINCT users_groups_query.user_id
+ FROM
+ (
+ SELECT
+ users.id AS user_id,
+ members.source_id AS group_ids
+ FROM
+ users
+ LEFT JOIN members ON members.source_type = 'Namespace'
+ AND members.requested_at IS NULL
+ AND members.user_id = users.id
+ AND members.type = 'GroupMember'
+ WHERE
+ users.require_two_factor_authentication_from_group = FALSE
+ AND users.id BETWEEN #{start_id}
+ AND #{stop_id}) AS users_groups_query
+ INNER JOIN LATERAL (
+ WITH RECURSIVE "base_and_ancestors" AS (
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."id" = users_groups_query.group_ids
+ )
+ UNION
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces",
+ "base_and_ancestors"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."id" = "base_and_ancestors"."parent_id"
+ )
+ ),
+ "base_and_descendants" AS (
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."id" = users_groups_query.group_ids
+ )
+ UNION
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "namespaces",
+ "base_and_descendants"
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."parent_id" = "base_and_descendants"."id"
+ )
+ )
+ SELECT
+ "namespaces".*
+ FROM
+ (
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "base_and_ancestors" AS "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ )
+ UNION
+ (
+ SELECT
+ "namespaces"."type",
+ "namespaces"."id",
+ "namespaces"."parent_id",
+ "namespaces"."require_two_factor_authentication"
+ FROM
+ "base_and_descendants" AS "namespaces"
+ WHERE
+ "namespaces"."type" = 'Group'
+ )
+ ) namespaces
+ WHERE
+ "namespaces"."type" = 'Group'
+ AND "namespaces"."require_two_factor_authentication" = TRUE
+ ) AS hierarchy_tree ON TRUE
+ );
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 55f381fcb64..510c945dd86 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -7,6 +7,10 @@ module Gitlab
!signup_enabled?
end
+ def signup_limited?
+ domain_allowlist.present? || email_restrictions_enabled? || require_admin_approval_after_user_signup?
+ end
+
def current_application_settings
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
end
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
index 6e17ffaf6ff..6d3b93c3b92 100644
--- a/lib/gitlab/diff/suggestions_parser.rb
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -6,6 +6,9 @@ module Gitlab
# Matches for instance "-1", "+1" or "-1+2".
SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze
+ CSS = 'pre.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.suggestion')
+ suggestion_nodes = doc.xpath(XPATH)
return [] if suggestion_nodes.empty?
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index 29f02a5912a..ec15e3905e8 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -195,6 +195,24 @@ module Gitlab
rescue Addressable::URI::InvalidURIError, TypeError
end
+ def removes_sensitive_data_from_url(uri_string)
+ uri = parse_url(uri_string)
+
+ return unless uri
+ return uri_string unless uri.fragment
+
+ stripped_params = CGI.parse(uri.fragment)
+ if stripped_params['access_token']
+ stripped_params['access_token'] = 'filtered'
+ filtered_query = Addressable::URI.new
+ filtered_query.query_values = stripped_params
+
+ uri.fragment = filtered_query.query
+ end
+
+ uri.to_s
+ end
+
# Invert a hash, collecting all keys that map to a given value in an array.
#
# Unlike `Hash#invert`, where the last encountered pair wins, and which has the
diff --git a/lib/gitlab/utils/nokogiri.rb b/lib/gitlab/utils/nokogiri.rb
new file mode 100644
index 00000000000..4b37bb7e5ea
--- /dev/null
+++ b/lib/gitlab/utils/nokogiri.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Utils
+ class Nokogiri
+ class << self
+ # Use Nokogiri to convert a css selector into an xpath selector.
+ # Nokogiri can use css selectors with `doc.search()`. However
+ # for large node trees, it is _much_ slower than using xpath,
+ # by several orders of magnitude.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/329186
+ def css_to_xpath(css)
+ xpath = ::Nokogiri::CSS.xpath_for(css)
+
+ # Due to https://github.com/sparklemotion/nokogiri/issues/572,
+ # we remove the leading `//` and add `descendant-or-self::`
+ # in order to ensure we're searching from this node and all
+ # descendants.
+ xpath.map { |t| "descendant-or-self::#{t[2..-1]}" }.join('|')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/x509/signature.rb b/lib/gitlab/x509/signature.rb
index edff1540cb3..72bbf3d6e8b 100644
--- a/lib/gitlab/x509/signature.rb
+++ b/lib/gitlab/x509/signature.rb
@@ -23,7 +23,7 @@ module Gitlab
end
def user
- User.find_by_any_email(@email)
+ strong_memoize(:user) { User.find_by_any_email(@email) }
end
def verified_signature
@@ -31,9 +31,13 @@ module Gitlab
end
def verification_status
- return :unverified if x509_certificate.nil? || x509_certificate.revoked?
+ return :unverified if
+ x509_certificate.nil? ||
+ x509_certificate.revoked? ||
+ !verified_signature ||
+ user.nil?
- if verified_signature && certificate_email == @email
+ if user.verified_emails.include?(@email) && certificate_email == @email
:verified
else
:unverified
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ae9f014fa09..f15ad6b8a23 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -25020,6 +25020,9 @@ msgstr ""
msgid "Redirect to SAML provider to test configuration"
msgstr ""
+msgid "Redirecting"
+msgstr ""
+
msgid "Redis"
msgstr ""
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index 2df94a06b3e..549e7829c82 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -70,12 +70,29 @@ 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 '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
@@ -102,7 +119,8 @@ RSpec.describe Oauth::AuthorizationsController do
subject
expect(request.session['user_return_to']).to be_nil
- expect(response).to have_gitlab_http_status(:found)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('doorkeeper/authorizations/redirect')
end
end
end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 1d1120709b5..40057fd8c50 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -394,6 +394,25 @@ RSpec.describe 'Login' do
gitlab_sign_in(user)
end
+
+ context 'when the users password is expired' do
+ before do
+ user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC'))
+ end
+
+ it 'asks for a new password' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+
+ visit new_user_session_path
+
+ fill_in 'user_login', with: user.email
+ fill_in 'user_password', with: '12345678'
+ click_button 'Sign in'
+
+ expect(current_path).to eq(new_profile_password_path)
+ end
+ end
end
context 'with invalid username and password' do
diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js
index 4d6addaf47c..69e2e12b2c1 100644
--- a/spec/frontend/notebook/cells/markdown_spec.js
+++ b/spec/frontend/notebook/cells/markdown_spec.js
@@ -39,7 +39,7 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
- it('sanitizes output', () => {
+ it('sanitizes Markdown output', () => {
Object.assign(cell, {
source: [
'[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n',
@@ -51,6 +51,17 @@ describe('Markdown component', () => {
});
});
+ 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('katex', () => {
beforeEach(() => {
json = getJSONFixture('blob/notebook/math.json');
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 3d1690f6588..66da81cdb8b 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -396,6 +396,13 @@ RSpec.describe MarkupHelper do
describe '#markup' do
let(:content) { 'Noël' }
+ it 'sets the :text_source to :blob in the context' do
+ context = {}
+ helper.markup('foo.md', content, context)
+
+ expect(context).to include(text_source: :blob)
+ end
+
it 'preserves encoding' do
expect(content.encoding.name).to eq('UTF-8')
expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8')
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
index abb1673bb88..421f6373eff 100644
--- a/spec/initializers/lograge_spec.rb
+++ b/spec/initializers/lograge_spec.rb
@@ -173,6 +173,27 @@ RSpec.describe 'lograge', type: :request do
end
end
+ describe 'with access token in url' do
+ before do
+ event.payload[:location] = 'http://example.com/auth.html#access_token=secret_token&token_type=Bearer'
+ end
+
+ it 'strips location from sensitive information' do
+ subscriber.redirect_to(event)
+ subscriber.process_action(event)
+
+ expect(log_data['location']).not_to include('secret_token')
+ expect(log_data['location']).to include('filtered')
+ end
+
+ it 'leaves non-sensitive information from location' do
+ subscriber.redirect_to(event)
+ subscriber.process_action(event)
+
+ expect(log_data['location']).to include('&token_type=Bearer')
+ end
+ end
+
context 'with db payload' do
context 'when RequestStore is enabled', :request_store do
it 'includes db counters' do
diff --git a/spec/lib/banzai/filter/truncate_source_filter_spec.rb b/spec/lib/banzai/filter/truncate_source_filter_spec.rb
index d5eb8b738b1..8970aa1d382 100644
--- a/spec/lib/banzai/filter/truncate_source_filter_spec.rb
+++ b/spec/lib/banzai/filter/truncate_source_filter_spec.rb
@@ -8,24 +8,68 @@ RSpec.describe Banzai::Filter::TruncateSourceFilter do
let(:short_text) { 'foo' * 10 }
let(:long_text) { ([short_text] * 10).join(' ') }
- it 'does nothing when limit is unspecified' do
- output = filter(long_text)
-
- expect(output).to eq(long_text)
+ before do
+ stub_const("#{described_class}::CHARACTER_COUNT_LIMIT", 50)
+ stub_const("#{described_class}::USER_MSG_LIMIT", 20)
end
- it 'does nothing to a short-enough text' do
- output = filter(short_text, limit: short_text.bytesize)
+ context 'when markdown belongs to a blob' do
+ it 'does nothing when limit is unspecified' do
+ output = filter(long_text, text_source: :blob)
+
+ expect(output).to eq(long_text)
+ end
+
+ it 'truncates normally when limit specified' do
+ truncated = 'foofoof...'
+
+ output = filter(long_text, text_source: :blob, limit: 10)
- expect(output).to eq(short_text)
+ expect(output).to eq(truncated)
+ end
end
- it 'truncates UTF-8 text by bytes, on a character boundary' do
- utf8_text = '日本語の文字が大きい'
- truncated = '日...'
+ context 'when markdown belongs to a field (non-blob)' do
+ it 'does nothing when limit is greater' do
+ output = filter(long_text, limit: 1.megabyte)
+
+ expect(output).to eq(long_text)
+ end
+
+ it 'truncates to the default when limit is unspecified' do
+ stub_const("#{described_class}::USER_MSG_LIMIT", 200)
+ truncated = 'foofoofoofoofoofoofoofoofoofoo foofoofoofoofoof...'
+
+ output = filter(long_text)
+
+ expect(output).to eq(truncated)
+ end
+
+ it 'prepends the user message' do
+ truncated = <<~TEXT
+ _The text is longer than 50 characters and has been visually truncated._
+
+ foofoofoofoofoofoofoofoofoofoo foofoofoofoofoof...
+ TEXT
+
+ output = filter(long_text)
+
+ expect(output).to eq(truncated.strip)
+ end
+
+ it 'does nothing to a short-enough text' do
+ output = filter(short_text, limit: short_text.bytesize)
+
+ expect(output).to eq(short_text)
+ end
+
+ it 'truncates UTF-8 text by bytes, on a character boundary' do
+ utf8_text = '日本語の文字が大きい'
+ truncated = '日...'
- expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated)
- expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text)
- expect(filter(utf8_text, limit: utf8_text.mb_chars.size)).not_to eq(utf8_text)
+ expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated)
+ expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text)
+ expect(filter(utf8_text, limit: utf8_text.mb_chars.size)).not_to eq(utf8_text)
+ end
end
end
diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
index d3c6cde5590..102d6fba97f 100644
--- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
+++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
@@ -57,5 +57,13 @@ RSpec.describe Gitlab::Auth::UserAccessDeniedReason do
it { is_expected.to eq('Your account is pending approval from your administrator and hence blocked.') }
end
+
+ context 'when the user has expired password' do
+ before do
+ user.update!(password_expires_at: 2.days.ago)
+ end
+
+ it { is_expected.to eq('Your password expired. Please access GitLab from a web browser to update your password.') }
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb b/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb
new file mode 100644
index 00000000000..e14328b6150
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::UpdateUsersWhereTwoFactorAuthRequiredFromGroup, :migration, schema: 20210519154058 do
+ include MigrationHelpers::NamespacesHelpers
+
+ let(:group_with_2fa_parent) { create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true) }
+ let(:group_with_2fa_child) { create_namespace('child', Gitlab::VisibilityLevel::PRIVATE, parent_id: group_with_2fa_parent.id) }
+ let(:members_table) { table(:members) }
+ let(:users_table) { table(:users) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ context 'with group members' do
+ let(:user_1) { create_user('user@example.com') }
+ let!(:member) { create_group_member(user_1, group_with_2fa_parent) }
+ let!(:user_without_group) { create_user('user_without@example.com') }
+ let(:user_other) { create_user('user_other@example.com') }
+ let!(:member_other) { create_group_member(user_other, group_with_2fa_parent) }
+
+ it 'updates user when user should be required to establish two factor authentication' do
+ subject.perform(user_1.id, user_without_group.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'does not update user who is not in current batch' do
+ subject.perform(user_1.id, user_without_group.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false)
+ end
+
+ it 'updates all users in current batch' do
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'updates user when user is member of group in which parent group requires two factor authentication' do
+ member.destroy!
+
+ subgroup = create_namespace('subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false, parent_id: group_with_2fa_child.id)
+ create_group_member(user_1, subgroup)
+
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'updates user when user is member of a group and the subgroup requires two factor authentication' do
+ member.destroy!
+
+ parent = create_namespace('other_parent', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false)
+ create_namespace('other_subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true, parent_id: parent.id)
+ create_group_member(user_1, parent)
+
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true)
+ end
+
+ it 'does not update user when not a member of a group that requires two factor authentication' do
+ member_other.destroy!
+
+ other_group = create_namespace('other_group', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: false)
+ create_group_member(user_other, other_group)
+
+ subject.perform(user_1.id, user_other.id)
+
+ expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false)
+ end
+ end
+ end
+
+ def create_user(email, require_2fa: false)
+ users_table.create!(email: email, projects_limit: 10, require_two_factor_authentication_from_group: require_2fa)
+ end
+
+ def create_group_member(user, group)
+ members_table.create!(user_id: user.id, source_id: group.id, access_level: GroupMember::MAINTAINER, source_type: "Namespace", type: "GroupMember", notification_level: 3)
+ end
+end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index f5cb1987c5c..a5ab1047a40 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -24,6 +24,42 @@ RSpec.describe Gitlab::CurrentSettings do
end
end
+ describe '.signup_limited?' do
+ subject { described_class.signup_limited? }
+
+ context 'when there are allowed domains' do
+ before do
+ create(:application_setting, domain_allowlist: ['www.gitlab.com'])
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when there are email restrictions' do
+ before do
+ create(:application_setting, email_restrictions_enabled: true)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the admin has to approve signups' do
+ before do
+ create(:application_setting, require_admin_approval_after_user_signup: true)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when there are no restrictions' do
+ before do
+ create(:application_setting, domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '.signup_disabled?' do
subject { described_class.signup_disabled? }
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 9a1ecfe6459..ff07696992e 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -433,6 +433,13 @@ RSpec.describe Gitlab::GitAccess do
expect { pull_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
end
+ it 'disallows users with expired password to pull' do
+ project.add_maintainer(user)
+ user.update!(password_expires_at: 2.minutes.ago)
+
+ expect { pull_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
+ end
+
context 'when the project repository does not exist' do
before do
project.add_guest(user)
@@ -969,6 +976,13 @@ RSpec.describe Gitlab::GitAccess do
expect { push_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
end
+ it 'disallows users with expired password to push' do
+ project.add_maintainer(user)
+ user.update!(password_expires_at: 2.minutes.ago)
+
+ expect { push_access_check }.to raise_forbidden("Your password expired. Please access GitLab from a web browser to update your password.")
+ end
+
it 'cleans up the files' do
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
expect { push_access_check }.not_to raise_error
diff --git a/spec/lib/gitlab/utils/nokogiri_spec.rb b/spec/lib/gitlab/utils/nokogiri_spec.rb
new file mode 100644
index 00000000000..b731b4a6264
--- /dev/null
+++ b/spec/lib/gitlab/utils/nokogiri_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Utils::Nokogiri do
+ describe '#css_to_xpath' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:css, :xpath) do
+ 'img' | "descendant-or-self::img"
+ 'a.gfm' | "descendant-or-self::a[contains(concat(' ',normalize-space(@class),' '),' gfm ')]"
+ 'a:not(.gfm)' | "descendant-or-self::a[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]"
+ 'video, audio' | "descendant-or-self::video|descendant-or-self::audio"
+ '[data-math-style]' | "descendant-or-self::*[@data-math-style]"
+ '[data-mermaid-style]' | "descendant-or-self::*[@data-mermaid-style]"
+ '.js-render-metrics' | "descendant-or-self::*[contains(concat(' ',normalize-space(@class),' '),' js-render-metrics ')]"
+ 'h1, h2, h3, h4, h5, h6' | "descendant-or-self::h1|descendant-or-self::h2|descendant-or-self::h3|descendant-or-self::h4|descendant-or-self::h5|descendant-or-self::h6"
+ 'pre.code.math' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' code ') and contains(concat(' ',normalize-space(@class),' '),' math ')]"
+ 'pre > code[lang="plantuml"]' | "descendant-or-self::pre/code[@lang=\"plantuml\"]"
+ 'pre[lang="mermaid"] > code' | "descendant-or-self::pre[@lang=\"mermaid\"]/code"
+ 'pre.suggestion' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' suggestion ')]"
+ 'pre.suggestion > code' | "descendant-or-self::pre[contains(concat(' ',normalize-space(@class),' '),' suggestion ')]/code"
+ 'a.gfm[data-reference-type="user"]' | "descendant-or-self::a[contains(concat(' ',normalize-space(@class),' '),' gfm ') and @data-reference-type=\"user\"]"
+ 'a:not(.gfm), img:not(.gfm), video:not(.gfm), audio:not(.gfm)' | "descendant-or-self::a[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::img[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::video[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]|descendant-or-self::audio[not(contains(concat(' ',normalize-space(@class),' '),' gfm '))]"
+ 'pre:not([data-math-style]):not([data-mermaid-style]):not([data-kroki-style]) > code' | "descendant-or-self::pre[not(@data-math-style) and not(@data-mermaid-style) and not(@data-kroki-style)]/code"
+ end
+
+ with_them do
+ it 'generates the xpath' do
+ expect(described_class.css_to_xpath(css)).to eq xpath
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 665eebdfd9e..cc2a16da609 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -415,6 +415,29 @@ RSpec.describe Gitlab::Utils do
end
end
+ describe '.removes_sensitive_data_from_url' do
+ it 'returns string object' do
+ expect(described_class.removes_sensitive_data_from_url('http://gitlab.com')).to be_instance_of(String)
+ end
+
+ it 'returns nil when URI cannot be parsed' do
+ expect(described_class.removes_sensitive_data_from_url('://gitlab.com')).to be nil
+ end
+
+ it 'returns nil with invalid parameter' do
+ expect(described_class.removes_sensitive_data_from_url(1)).to be nil
+ end
+
+ it 'returns string with filtered access_token param' do
+ expect(described_class.removes_sensitive_data_from_url('http://gitlab.com/auth.html#access_token=secret_token')).to eq('http://gitlab.com/auth.html#access_token=filtered')
+ end
+
+ it 'returns string with filtered access_token param but other params preserved' do
+ expect(described_class.removes_sensitive_data_from_url('http://gitlab.com/auth.html#access_token=secret_token&token_type=Bearer&state=test'))
+ .to include('&token_type=Bearer', '&state=test')
+ end
+ end
+
describe 'multiple_key_invert' do
it 'invert keys with array values' do
hash = {
diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb
index 2ac9c1f3a3b..7ba15faf910 100644
--- a/spec/lib/gitlab/x509/signature_spec.rb
+++ b/spec/lib/gitlab/x509/signature_spec.rb
@@ -12,20 +12,30 @@ RSpec.describe Gitlab::X509::Signature do
end
shared_examples "a verified signature" do
- it 'returns a verified signature if email does match' do
- signature = described_class.new(
+ let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
+
+ subject(:signature) do
+ described_class.new(
X509Helpers::User1.signed_commit_signature,
X509Helpers::User1.signed_commit_base_data,
X509Helpers::User1.certificate_email,
X509Helpers::User1.signed_commit_time
)
+ end
+ it 'returns a verified signature if email does match' do
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified)
end
+ it "returns an unverified signature if the email matches but isn't confirmed" do
+ user.update!(confirmed_at: nil)
+
+ expect(signature.verification_status).to eq(:unverified)
+ end
+
it 'returns an unverified signature if email does not match' do
signature = described_class.new(
X509Helpers::User1.signed_commit_signature,
@@ -55,13 +65,6 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns an unverified signature if certificate is revoked' do
- signature = described_class.new(
- X509Helpers::User1.signed_commit_signature,
- X509Helpers::User1.signed_commit_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.verification_status).to eq(:verified)
signature.x509_certificate.revoked!
@@ -253,23 +256,25 @@ RSpec.describe Gitlab::X509::Signature do
end
describe '#user' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
+ subject do
+ described_class.new(
+ X509Helpers::User1.signed_tag_signature,
+ X509Helpers::User1.signed_tag_base_data,
+ X509Helpers::User1.certificate_email,
+ X509Helpers::User1.signed_commit_time
+ ).user
+ end
context 'if email is assigned to a user' do
let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
it 'returns user' do
- expect(signature.user).to eq(user)
+ is_expected.to eq(user)
end
end
it 'if email is not assigned to a user, return nil' do
- expect(signature.user).to be_nil
+ is_expected.to be_nil
end
end
@@ -292,6 +297,17 @@ RSpec.describe Gitlab::X509::Signature do
end
context 'verified signature' do
+ let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
+
+ subject(:signature) do
+ described_class.new(
+ X509Helpers::User1.signed_tag_signature,
+ X509Helpers::User1.signed_tag_base_data,
+ X509Helpers::User1.certificate_email,
+ X509Helpers::User1.signed_commit_time
+ )
+ end
+
context 'with trusted certificate store' do
before do
store = OpenSSL::X509::Store.new
@@ -301,19 +317,18 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns a verified signature if email does match' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified)
end
+ it "returns an unverified signature if the email matches but isn't confirmed" do
+ user.update!(confirmed_at: nil)
+
+ expect(signature.verification_status).to eq(:unverified)
+ end
+
it 'returns an unverified signature if email does not match' do
signature = described_class.new(
X509Helpers::User1.signed_tag_signature,
@@ -343,13 +358,6 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns an unverified signature if certificate is revoked' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.verification_status).to eq(:verified)
signature.x509_certificate.revoked!
@@ -368,13 +376,6 @@ RSpec.describe Gitlab::X509::Signature do
end
it 'returns an unverified signature' do
- signature = described_class.new(
- X509Helpers::User1.signed_tag_signature,
- X509Helpers::User1.signed_tag_base_data,
- X509Helpers::User1.certificate_email,
- X509Helpers::User1.signed_commit_time
- )
-
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_falsey
diff --git a/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb b/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb
new file mode 100644
index 00000000000..cec141cacc9
--- /dev/null
+++ b/spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20210519154058_schedule_update_users_where_two_factor_auth_required_from_group.rb')
+
+RSpec.describe ScheduleUpdateUsersWhereTwoFactorAuthRequiredFromGroup do
+ let(:users) { table(:users) }
+ let!(:user_1) { users.create!(require_two_factor_authentication_from_group: false, name: "user1", email: "user1@example.com", projects_limit: 1) }
+ let!(:user_2) { users.create!(require_two_factor_authentication_from_group: true, name: "user2", email: "user2@example.com", projects_limit: 1) }
+ let!(:user_3) { users.create!(require_two_factor_authentication_from_group: false, name: "user3", email: "user3@example.com", projects_limit: 1) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+ end
+
+ it 'schedules jobs for users that do not require two factor authentication' do
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 2.minutes, user_1.id, user_1.id)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(
+ 4.minutes, user_3.id, user_3.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index e677f5558fd..bbbc5d08c07 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -239,6 +239,14 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:access_api) }
end
+ context 'user with expired password' do
+ before do
+ current_user.update!(password_expires_at: 2.minutes.ago)
+ end
+
+ it { is_expected.not_to be_allowed(:access_api) }
+ end
+
context 'when terms are enforced' do
before do
enforce_terms
@@ -418,6 +426,14 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:access_git) }
end
+
+ context 'user with expired password' do
+ before do
+ current_user.update!(password_expires_at: 2.minutes.ago)
+ end
+
+ it { is_expected.not_to be_allowed(:access_git) }
+ end
end
describe 'read instance metadata' do
@@ -494,6 +510,14 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:use_slash_commands) }
end
+
+ context 'user with expired password' do
+ before do
+ current_user.update!(password_expires_at: 2.minutes.ago)
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
end
describe 'create_snippet' do
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index cf8cac773f5..07912602d1e 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -27,9 +27,10 @@ RSpec.describe API::Lint do
end
end
- context 'when signup settings are enabled' do
+ context 'when signup is enabled and not limited' do
before do
Gitlab::CurrentSettings.signup_enabled = true
+ stub_application_setting(domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false)
end
context 'when unauthenticated' do
@@ -50,6 +51,31 @@ RSpec.describe API::Lint do
end
end
+ context 'when limited signup is enabled' do
+ before do
+ stub_application_setting(domain_allowlist: ['www.gitlab.com'])
+ Gitlab::CurrentSettings.signup_enabled = true
+ end
+
+ context 'when unauthenticated' do
+ it 'returns unauthorized' do
+ post api('/ci/lint'), params: { content: 'content' }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when authenticated' do
+ let_it_be(:api_user) { create(:user) }
+
+ it 'returns authentication success' do
+ post api('/ci/lint', api_user), params: { content: 'content' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
context 'when authenticated' do
let_it_be(:api_user) { create(:user) }
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index a1e28c18769..279c65fc2f4 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -35,6 +35,26 @@ RSpec.describe 'Git HTTP requests' do
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end
end
+
+ context "when password is expired" do
+ it "responds to downloads with status 401 Unauthorized" do
+ user.update!(password_expires_at: 2.days.ago)
+
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context "when user is blocked" do
+ let(:user) { create(:user, :blocked) }
+
+ it "responds to downloads with status 401 Unauthorized" do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
context "when authentication succeeds" do
@@ -75,6 +95,15 @@ RSpec.describe 'Git HTTP requests' do
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end
end
+
+ context "when password is expired" do
+ it "responds to uploads with status 401 Unauthorized" do
+ user.update!(password_expires_at: 2.days.ago)
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
context "when authentication succeeds" do
@@ -576,6 +605,16 @@ RSpec.describe 'Git HTTP requests' do
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
+
+ context "when password is expired" do
+ it "responds to downloads with status 401 unauthorized" do
+ user.update!(password_expires_at: 2.days.ago)
+
+ download(path, **env) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
context 'when user has 2FA enabled' do
@@ -649,6 +688,18 @@ RSpec.describe 'Git HTTP requests' do
expect(response).to have_gitlab_http_status(:ok)
end
end
+
+ context "when password is expired" do
+ it "responds to uploads with status 401 unauthorized" do
+ user.update!(password_expires_at: 2.days.ago)
+
+ write_access_token = create(:personal_access_token, user: user, scopes: [:write_repository])
+
+ upload(path, user: user.username, password: write_access_token.token) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
end
@@ -860,6 +911,16 @@ RSpec.describe 'Git HTTP requests' do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when users password is expired' do
+ it 'rejects pulls with 401 unauthorized' do
+ user.update!(password_expires_at: 2.days.ago)
+
+ download(path, user: 'gitlab-ci-token', password: build.token) do |response|
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
end
end
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 4e18c9cb4ca..0e3a0252638 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -346,9 +346,7 @@ RSpec.describe 'Git LFS API and storage' do
let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)}
let(:role) { :reporter}
- # TODO: This should return a 404 response
- # https://gitlab.com/gitlab-org/gitlab/-/issues/292006
- it_behaves_like 'LFS http 200 response'
+ it_behaves_like 'LFS http 401 response'
end
context 'when user is blocked' do
diff --git a/spec/tasks/gitlab/x509/update_rake_spec.rb b/spec/tasks/gitlab/x509/update_rake_spec.rb
index 93e97ab38ad..b166e73935a 100644
--- a/spec/tasks/gitlab/x509/update_rake_spec.rb
+++ b/spec/tasks/gitlab/x509/update_rake_spec.rb
@@ -8,12 +8,13 @@ RSpec.describe 'gitlab:x509 namespace rake task' do
end
describe 'update_signatures' do
- subject { run_rake_task('gitlab:x509:update_signatures') }
-
- let(:project) { create :project, :repository, path: X509Helpers::User1.path }
+ let(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
+ let(:project) { create(:project, :repository, path: X509Helpers::User1.path, creator: user) }
let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') }
let(:x509_commit) { Gitlab::X509::Commit.new(x509_signed_commit).signature }
+ subject { run_rake_task('gitlab:x509:update_signatures') }
+
it 'changes from unverified to verified if the certificate store contains the root certificate' do
x509_commit
@@ -22,21 +23,14 @@ RSpec.describe 'gitlab:x509 namespace rake task' do
store.add_cert(certificate)
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
- expect(x509_commit.verification_status).to eq('unverified')
expect_any_instance_of(Gitlab::X509::Commit).to receive(:update_signature!).and_call_original
-
- subject
-
- x509_commit.reload
- expect(x509_commit.verification_status).to eq('verified')
+ expect { subject }.to change { x509_commit.reload.verification_status }.from('unverified').to('verified')
end
it 'returns if no signature is available' do
- expect_any_instance_of(Gitlab::X509::Commit) do |x509_commit|
- expect(x509_commit).not_to receive(:update_signature!)
+ expect_any_instance_of(Gitlab::X509::Commit).not_to receive(:update_signature!)
- subject
- end
+ subject
end
end
end