summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2021-12-06 22:53:58 +0000
committerGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2021-12-06 22:53:58 +0000
commit83ca7f60f94ee2d3f1a966fa9757849da5de4035 (patch)
tree5cca41bee4989af1291c17ab8d7aa3479e20aff0
parent9d9ee598bc514eaee681b40cdff4d12a3a8f412a (diff)
parent76ceea558aae5013c4e16b3b2f97363608765c21 (diff)
downloadgitlab-ce-83ca7f60f94ee2d3f1a966fa9757849da5de4035.tar.gz
Merge remote-tracking branch 'dev/14-5-stable' into 14-5-stable
-rw-r--r--CHANGELOG.md4
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js2
-rw-r--r--app/assets/javascripts/blob/openapi/index.js6
-rw-r--r--app/controllers/graphql_controller.rb16
-rw-r--r--app/graphql/gitlab_schema.rb3
-rw-r--r--app/graphql/types/user_interface.rb16
-rw-r--r--app/helpers/search_helper.rb24
-rw-r--r--app/models/concerns/bulk_member_access_load.rb25
-rw-r--r--app/models/concerns/diff_positionable_note.rb1
-rw-r--r--app/models/preloaders/user_max_access_level_in_groups_preloader.rb9
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/project_team.rb6
-rw-r--r--app/models/todo.rb2
-rw-r--r--app/policies/issuable_policy.rb4
-rw-r--r--app/policies/project_policy.rb13
-rw-r--r--app/services/protected_branches/base_service.rb18
-rw-r--r--app/services/protected_branches/create_service.rb2
-rw-r--r--app/services/protected_branches/update_service.rb2
-rw-r--r--app/validators/json_schema_validator.rb4
-rw-r--r--app/validators/json_schemas/position.json151
-rw-r--r--app/views/layouts/_search.html.haml5
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--config/application.rb1
-rw-r--r--doc/api/graphql/reference/index.md8
-rw-r--r--doc/user/packages/maven_repository/index.md2
-rw-r--r--lib/api/entities/project.rb4
-rw-r--r--lib/api/entities/user_safe.rb12
-rw-r--r--lib/api/lint.rb12
-rw-r--r--lib/api/merge_request_approvals.rb2
-rw-r--r--lib/api/merge_request_diffs.rb4
-rw-r--r--lib/api/merge_requests.rb10
-rw-r--r--lib/api/todos.rb4
-rw-r--r--lib/banzai/filter/front_matter_filter.rb2
-rw-r--r--lib/gitlab/current_settings.rb2
-rw-r--r--lib/gitlab/diff/lines_unfolder.rb1
-rw-r--r--lib/gitlab/front_matter.rb10
-rw-r--r--lib/gitlab/git_access_wiki.rb7
-rw-r--r--lib/gitlab/import_export/members_mapper.rb11
-rw-r--r--lib/gitlab/quick_actions/extractor.rb4
-rw-r--r--lib/gitlab/regex.rb2
-rw-r--r--lib/gitlab/slash_commands/deploy.rb12
-rw-r--r--lib/gitlab/wiki_pages/front_matter_parser.rb2
-rw-r--r--lib/sidebars/projects/menus/analytics_menu.rb2
-rw-r--r--locale/gitlab.pot3
-rw-r--r--package.json2
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb2
-rw-r--r--spec/controllers/graphql_controller_spec.rb38
-rw-r--r--spec/factories/diff_position.rb8
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb51
-rw-r--r--spec/features/projects/members/list_spec.rb2
-rw-r--r--spec/features/projects_spec.rb18
-rw-r--r--spec/features/protected_branches_spec.rb6
-rw-r--r--spec/frontend/diffs/store/utils_spec.js4
-rw-r--r--spec/graphql/gitlab_schema_spec.rb34
-rw-r--r--spec/graphql/types/user_type_spec.rb80
-rw-r--r--spec/helpers/search_helper_spec.rb16
-rw-r--r--spec/lib/api/entities/project_spec.rb22
-rw-r--r--spec/lib/api/entities/user_spec.rb45
-rw-r--r--spec/lib/banzai/filter/front_matter_filter_spec.rb16
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb24
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb25
-rw-r--r--spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb60
-rw-r--r--spec/lib/gitlab/import_export/project/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb17
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb8
-rw-r--r--spec/lib/gitlab/regex_spec.rb13
-rw-r--r--spec/lib/gitlab/slash_commands/deploy_spec.rb59
-rw-r--r--spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb2
-rw-r--r--spec/lib/sidebars/projects/menus/analytics_menu_spec.rb6
-rw-r--r--spec/models/packages/package_spec.rb2
-rw-r--r--spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb3
-rw-r--r--spec/policies/merge_request_policy_spec.rb35
-rw-r--r--spec/requests/api/graphql/user_query_spec.rb14
-rw-r--r--spec/requests/api/lint_spec.rb29
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/todos_spec.rb38
-rw-r--r--spec/services/protected_branches/create_service_spec.rb39
-rw-r--r--spec/services/protected_branches/update_service_spec.rb39
-rw-r--r--spec/support/helpers/features/members_helpers.rb4
-rw-r--r--spec/support/helpers/graphql_helpers.rb5
-rw-r--r--spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb33
-rw-r--r--spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/requests/api/merge_requests_shared_examples.rb6
-rw-r--r--spec/validators/json_schema_validator_spec.rb12
-rw-r--r--yarn.lock17
94 files changed, 1156 insertions, 171 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d204a9aeaeb..fce415ec963 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 14.5.2 (2021-12-03)
+
+No changes.
+
## 14.5.1 (2021-12-01)
### Fixed (4 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index afd36b76968..e9d8f88edb6 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-14.5.1 \ No newline at end of file
+14.5.2 \ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 87a0cff84c1..7fa615319c4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -97,7 +97,7 @@ gem 'grape-entity', '~> 0.10.0'
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
# GraphQL API
-gem 'graphql', '~> 1.11.8'
+gem 'graphql', '~> 1.11.10'
# NOTE: graphiql-rails v1.5+ doesn't work: https://gitlab.com/gitlab-org/gitlab/issues/31771
# TODO: remove app/views/graphiql/rails/editors/show.html.erb when https://github.com/rmosolgo/graphiql-rails/pull/71 is released:
# https://gitlab.com/gitlab-org/gitlab/issues/31747
diff --git a/Gemfile.lock b/Gemfile.lock
index 270ea4532f9..b54874e9d80 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -558,7 +558,7 @@ GEM
faraday (>= 1.0)
faraday_middleware
graphql-client
- graphql (1.11.8)
+ graphql (1.11.10)
graphql-client (0.16.0)
activesupport (>= 3.0)
graphql (~> 1.8)
@@ -1490,7 +1490,7 @@ DEPENDENCIES
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphlient (~> 0.4.0)
- graphql (~> 1.11.8)
+ graphql (~> 1.11.10)
graphql-docs (~> 1.6.0)
grpc (~> 1.30.2)
gssapi
diff --git a/VERSION b/VERSION
index afd36b76968..e9d8f88edb6 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-14.5.1 \ No newline at end of file
+14.5.2 \ No newline at end of file
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index 3f878949f9b..d78c456ed5b 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -75,7 +75,7 @@ export function initMermaid(mermaid) {
function importMermaidModule() {
return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
- .then((mermaid) => {
+ .then(({ default: mermaid }) => {
mermaidModule = initMermaid(mermaid);
})
.catch((err) => {
diff --git a/app/assets/javascripts/blob/openapi/index.js b/app/assets/javascripts/blob/openapi/index.js
index cb251274b18..b19cc19cb8c 100644
--- a/app/assets/javascripts/blob/openapi/index.js
+++ b/app/assets/javascripts/blob/openapi/index.js
@@ -1,5 +1,6 @@
import { SwaggerUIBundle } from 'swagger-ui-dist';
import createFlash from '~/flash';
+import { removeParams, updateHistory } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
export default () => {
@@ -7,9 +8,14 @@ export default () => {
Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')])
.then(() => {
+ // Temporary fix to prevent an XSS attack due to "useUnsafeMarkdown"
+ // Once we upgrade Swagger to "4.0.0", we can safely remove this as it will be deprecated
+ // Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/339696
+ updateHistory({ url: removeParams(['useUnsafeMarkdown']), replace: true });
SwaggerUIBundle({
url: el.dataset.endpoint,
dom_id: '#js-openapi-viewer',
+ useUnsafeMarkdown: false,
});
})
.catch((error) => {
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index fde0f133e53..899fa614949 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -9,6 +9,9 @@ class GraphqlController < ApplicationController
# Header can be passed by tests to disable SQL query limits.
DISABLE_SQL_QUERY_LIMIT_HEADER = 'HTTP_X_GITLAB_DISABLE_SQL_QUERY_LIMIT'
+ # Max size of the query text in characters
+ MAX_QUERY_SIZE = 10_000
+
# If a user is using their session to access GraphQL, we need to have session
# storage, since the admin-mode check is session wide.
# We can't enable this for anonymous users because that would cause users using
@@ -29,6 +32,7 @@ class GraphqlController < ApplicationController
before_action :set_user_last_activity
before_action :track_vs_code_usage
before_action :disable_query_limiting
+ before_action :limit_query_size
before_action :disallow_mutations_for_get
@@ -81,6 +85,16 @@ class GraphqlController < ApplicationController
raise ::Gitlab::Graphql::Errors::ArgumentError, "Mutations are forbidden in #{request.request_method} requests"
end
+ def limit_query_size
+ total_size = if multiplex?
+ params[:_json].sum { _1[:query].size }
+ else
+ query.size
+ end
+
+ raise ::Gitlab::Graphql::Errors::ArgumentError, "Query too large" if total_size > MAX_QUERY_SIZE
+ end
+
def any_mutating_query?
if multiplex?
multiplex_queries.any? { |q| mutation?(q[:query], q[:operation_name]) }
@@ -126,7 +140,7 @@ class GraphqlController < ApplicationController
end
def query
- params[:query]
+ params.fetch(:query, '')
end
def multiplex_queries
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index e15a185a743..9b23aa60eab 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -32,6 +32,9 @@ class GitlabSchema < GraphQL::Schema
default_max_page_size 100
+ validate_max_errors 5
+ validate_timeout 0.2.seconds
+
lazy_resolve ::Gitlab::Graphql::Lazy, :force
class << self
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index 8c67275eb73..7cc201b6df4 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -29,7 +29,10 @@ module Types
field :name,
type: GraphQL::Types::String,
null: false,
- description: 'Human-readable name of the user.'
+ resolver_method: :redacted_name,
+ description: 'Human-readable name of the user. ' \
+ 'Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens.'
+
field :state,
type: Types::UserStateEnum,
null: false,
@@ -121,5 +124,16 @@ module Types
::Types::UserType
end
end
+
+ def redacted_name
+ return object.name unless object.project_bot?
+
+ return object.name if context[:current_user]&.can?(:read_resource_access_tokens, object.projects.first)
+
+ # If the requester does not have permission to read the project bot name,
+ # the API returns an arbitrary string. UI changes will be addressed in a follow up issue:
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/346058
+ '****'
+ end
end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index cb28025c900..aedb7df9e4f 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -201,18 +201,30 @@ module SearchHelper
if @project && @project.repository.root_ref
ref = @ref || @project.repository.root_ref
- result = [
- { category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
- { category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) },
- { category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
- { category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) },
+ result = []
+
+ if can?(current_user, :download_code, @project)
+ result.concat([
+ { category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
+ { category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) }
+ ])
+ end
+
+ if can?(current_user, :read_repository_graphs, @project)
+ result.concat([
+ { category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
+ { category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) }
+ ])
+ end
+
+ result.concat([
{ category: "In this project", label: _("Issues"), url: project_issues_path(@project) },
{ category: "In this project", label: _("Merge requests"), url: project_merge_requests_path(@project) },
{ category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) },
{ category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) },
{ category: "In this project", label: _("Members"), url: project_project_members_path(@project) },
{ category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) }
- ]
+ ])
if can?(current_user, :read_feature_flag, @project)
result << { category: "In this project", label: _("Feature Flags"), url: project_feature_flags_path(@project) }
diff --git a/app/models/concerns/bulk_member_access_load.rb b/app/models/concerns/bulk_member_access_load.rb
index e252ca36629..927d6ccb28f 100644
--- a/app/models/concerns/bulk_member_access_load.rb
+++ b/app/models/concerns/bulk_member_access_load.rb
@@ -9,11 +9,15 @@ module BulkMemberAccessLoad
# Determine the maximum access level for a group of resources in bulk.
#
# Returns a Hash mapping resource ID -> maximum access level.
- def max_member_access_for_resource_ids(resource_klass, resource_ids, memoization_index = self.id, &block)
+ def max_member_access_for_resource_ids(resource_klass, resource_ids, &block)
raise 'Block is mandatory' unless block_given?
+ memoization_index = self.id
+ memoization_class = self.class
+
resource_ids = resource_ids.uniq
- access = load_access_hash(resource_klass, memoization_index)
+ memo_id = "#{memoization_class}:#{memoization_index}"
+ access = load_access_hash(resource_klass, memo_id)
# Look up only the IDs we need
resource_ids -= access.keys
@@ -33,8 +37,8 @@ module BulkMemberAccessLoad
access
end
- def merge_value_to_request_store(resource_klass, resource_id, memoization_index, value)
- max_member_access_for_resource_ids(resource_klass, [resource_id], memoization_index) do
+ def merge_value_to_request_store(resource_klass, resource_id, value)
+ max_member_access_for_resource_ids(resource_klass, [resource_id]) do
{ resource_id => value }
end
end
@@ -45,16 +49,13 @@ module BulkMemberAccessLoad
"max_member_access_for_#{klass.name.underscore.pluralize}:#{memoization_index}"
end
- def load_access_hash(resource_klass, memoization_index)
- key = max_member_access_for_resource_key(resource_klass, memoization_index)
+ def load_access_hash(resource_klass, memo_id)
+ return {} unless Gitlab::SafeRequestStore.active?
- access = {}
- if Gitlab::SafeRequestStore.active?
- Gitlab::SafeRequestStore[key] ||= {}
- access = Gitlab::SafeRequestStore[key]
- end
+ key = max_member_access_for_resource_key(resource_klass, memo_id)
+ Gitlab::SafeRequestStore[key] ||= {}
- access
+ Gitlab::SafeRequestStore[key]
end
end
end
diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb
index cea3c7d119c..b13ca4bf06e 100644
--- a/app/models/concerns/diff_positionable_note.rb
+++ b/app/models/concerns/diff_positionable_note.rb
@@ -12,6 +12,7 @@ module DiffPositionableNote
serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
validate :diff_refs_match_commit, if: :for_commit?
+ validates :position, json_schema: { filename: "position", hash_conversion: true }
end
%i(original_position position change_position).each do |meth|
diff --git a/app/models/preloaders/user_max_access_level_in_groups_preloader.rb b/app/models/preloaders/user_max_access_level_in_groups_preloader.rb
index bdd76d39ec1..2cd54b975f3 100644
--- a/app/models/preloaders/user_max_access_level_in_groups_preloader.rb
+++ b/app/models/preloaders/user_max_access_level_in_groups_preloader.rb
@@ -4,8 +4,6 @@ module Preloaders
# This class preloads the max access level (role) for the user within the given groups and
# stores the values in requests store.
class UserMaxAccessLevelInGroupsPreloader
- include BulkMemberAccessLoad
-
def initialize(groups, user)
@groups = groups
@user = user
@@ -27,8 +25,9 @@ module Preloaders
.group(:source_id)
.maximum(:access_level)
- group_memberships.each do |group_id, max_access_level|
- merge_value_to_request_store(User, @user.id, group_id, max_access_level)
+ @groups.each do |group|
+ access_level = group_memberships[group.id]
+ group.merge_value_to_request_store(User, @user.id, access_level) if access_level.present?
end
end
@@ -41,7 +40,7 @@ module Preloaders
@groups.each do |group|
max_access_level = max_access_levels[group.id] || Gitlab::Access::NO_ACCESS
- merge_value_to_request_store(User, @user.id, group.id, max_access_level)
+ group.merge_value_to_request_store(User, @user.id, max_access_level)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2288850553c..45999da7839 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -36,6 +36,7 @@ class Project < ApplicationRecord
include Repositories::CanHousekeepRepository
include EachBatch
include GitlabRoutingHelper
+ include BulkMemberAccessLoad
extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 94904e9792f..8061554006d 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
class ProjectTeam
- include BulkMemberAccessLoad
-
attr_accessor :project
def initialize(project)
@@ -171,7 +169,7 @@ class ProjectTeam
#
# Returns a Hash mapping user ID -> maximum access level.
def max_member_access_for_user_ids(user_ids)
- max_member_access_for_resource_ids(User, user_ids, project.id) do |user_ids|
+ project.max_member_access_for_resource_ids(User, user_ids) do |user_ids|
project.project_authorizations
.where(user: user_ids)
.group(:user_id)
@@ -180,7 +178,7 @@ class ProjectTeam
end
def write_member_access_for_user_id(user_id, project_access_level)
- merge_value_to_request_store(User, user_id, project.id, project_access_level)
+ project.merge_value_to_request_store(User, user_id, project_access_level)
end
def max_member_access(user_id)
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 742b8fd2a9d..cfcb2201b80 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -69,7 +69,7 @@ class Todo < ApplicationRecord
scope :for_type, -> (type) { where(target_type: type) }
scope :for_target, -> (id) { where(target_id: id) }
scope :for_commit, -> (id) { where(commit_id: id) }
- scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) }
+ scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: [:route, :owner] }]) }
scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
enum resolved_by_action: { system_done: 0, api_all_done: 1, api_done: 2, mark_all_done: 3, mark_done: 4 }, _prefix: :resolved_by
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 39ce26526e6..ed5a0f24ed0 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -17,7 +17,9 @@ class IssuablePolicy < BasePolicy
enable :read_issue
enable :update_issue
enable :reopen_issue
- enable :read_merge_request
+ end
+
+ rule { can?(:read_merge_request) & assignee_or_author }.policy do
enable :update_merge_request
enable :reopen_merge_request
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index d81db357162..b3aa49a00ae 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -93,6 +93,11 @@ class ProjectPolicy < BasePolicy
user.is_a?(DeployToken) && user.has_access_to?(project) && user.write_package_registry
end
+ desc "Deploy token with read access"
+ condition(:download_code_deploy_token) do
+ user.is_a?(DeployToken) && user.has_access_to?(project)
+ end
+
desc "If user is authenticated via CI job token then the target project should be in scope"
condition(:project_allowed_for_job_token) do
!@user&.from_ci_job_token? || @user.ci_job_token_scope.includes?(project)
@@ -506,6 +511,10 @@ class ProjectPolicy < BasePolicy
prevent(:download_wiki_code)
end
+ rule { download_code_deploy_token }.policy do
+ enable :download_wiki_code
+ end
+
rule { builds_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin_destroy(:build))
prevent(*create_read_update_admin_destroy(:pipeline_schedule))
@@ -687,12 +696,14 @@ class ProjectPolicy < BasePolicy
rule { project_bot }.enable :project_bot_access
+ rule { can?(:read_all_resources) }.enable :read_resource_access_tokens
+
rule { can?(:admin_project) & resource_access_token_feature_available }.policy do
enable :read_resource_access_tokens
enable :destroy_resource_access_tokens
end
- rule { can?(:read_resource_access_tokens) & resource_access_token_creation_allowed }.policy do
+ rule { can?(:admin_project) & resource_access_token_feature_available & resource_access_token_creation_allowed }.policy do
enable :create_resource_access_tokens
end
diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb
index f48e02ab4b5..df801311aaf 100644
--- a/app/services/protected_branches/base_service.rb
+++ b/app/services/protected_branches/base_service.rb
@@ -13,5 +13,23 @@ module ProtectedBranches
def after_execute(*)
# overridden in EE::ProtectedBranches module
end
+
+ def filtered_params
+ return unless params
+
+ params[:name] = sanitize_branch_name(params[:name]) if params[:name].present?
+ params
+ end
+
+ private
+
+ def sanitize_branch_name(name)
+ name = CGI.unescapeHTML(name)
+ name = Sanitize.fragment(name)
+
+ # Sanitize.fragment escapes HTML chars, so unescape again to allow names
+ # like `feature->master`
+ CGI.unescapeHTML(name)
+ end
end
end
diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb
index dada449989a..ea494dd4426 100644
--- a/app/services/protected_branches/create_service.rb
+++ b/app/services/protected_branches/create_service.rb
@@ -21,7 +21,7 @@ module ProtectedBranches
end
def protected_branch
- @protected_branch ||= project.protected_branches.new(params)
+ @protected_branch ||= project.protected_branches.new(filtered_params)
end
end
end
diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb
index 1e70f2d9793..40e9a286af9 100644
--- a/app/services/protected_branches/update_service.rb
+++ b/app/services/protected_branches/update_service.rb
@@ -8,7 +8,7 @@ module ProtectedBranches
old_merge_access_levels = protected_branch.merge_access_levels.map(&:clone)
old_push_access_levels = protected_branch.push_access_levels.map(&:clone)
- if protected_branch.update(params)
+ if protected_branch.update(filtered_params)
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
end
diff --git a/app/validators/json_schema_validator.rb b/app/validators/json_schema_validator.rb
index 68f03e8a6a3..4896c2ea2ef 100644
--- a/app/validators/json_schema_validator.rb
+++ b/app/validators/json_schema_validator.rb
@@ -24,8 +24,10 @@ class JsonSchemaValidator < ActiveModel::EachValidator
end
def validate_each(record, attribute, value)
+ value = value.to_h.stringify_keys if options[:hash_conversion] == true
+
unless valid_schema?(value)
- record.errors.add(attribute, "must be a valid json schema")
+ record.errors.add(attribute, _("must be a valid json schema"))
end
end
diff --git a/app/validators/json_schemas/position.json b/app/validators/json_schemas/position.json
new file mode 100644
index 00000000000..d2c83be7639
--- /dev/null
+++ b/app/validators/json_schemas/position.json
@@ -0,0 +1,151 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "description": "Gitlab::Diff::Position",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "base_sha": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 40 }
+ ]
+ },
+ "start_sha": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 40 }
+ ]
+ },
+ "head_sha": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 40 }
+ ]
+ },
+ "file_identifier_hash": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 40 }
+ ]
+ },
+ "old_path": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 1000 }
+ ]
+ },
+ "new_path": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 1000 }
+ ]
+ },
+ "position_type": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 10 }
+ ]
+ },
+ "old_line": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" }
+ ]
+ },
+ "new_line": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" }
+ ]
+ },
+ "line_range": {
+ "oneOf": [
+ { "type": "null" },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "start": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "line_code": { "type": "string", "maxLength": 100 },
+ "type": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 100 }
+ ]
+ },
+ "old_line": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" }
+ ]
+ },
+ "new_line": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" }
+ ]
+ }
+ }
+ },
+ "end": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "line_code": { "type": "string", "maxLength": 100 },
+ "type": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "string", "maxLength": 100 }
+ ]
+ },
+ "old_line": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" }
+ ]
+ },
+ "new_line": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" }
+ ]
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "width": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" },
+ { "type": "string", "maxLength": 10 }
+ ]
+ },
+ "height": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" },
+ { "type": "string", "maxLength": 10 }
+ ]
+ },
+ "x": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" },
+ { "type": "string", "maxLength": 10 }
+ ]
+ },
+ "y": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "integer" },
+ { "type": "string", "maxLength": 10 }
+ ]
+ }
+ }
+}
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 2d186dfbd91..0350dc82e46 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -29,8 +29,9 @@
= hidden_field_tag :scope, search_context.scope
= hidden_field_tag :search_code, search_context.code_search?
+ - ref = search_context.ref if can?(current_user, :download_code, search_context.project)
= hidden_field_tag :snippets, search_context.for_snippets?
- = hidden_field_tag :repository_ref, search_context.ref
+ = hidden_field_tag :repository_ref, ref
= hidden_field_tag :nav_source, 'navbar'
-# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb
@@ -38,4 +39,4 @@
%noscript= button_tag 'Search'
.search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path,
:'data-autocomplete-project-id' => search_context.project.try(:id),
- :'data-autocomplete-project-ref' => search_context.ref }
+ :'data-autocomplete-project-ref' => ref }
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index e515f1e7320..1cbb061784e 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,5 +1,4 @@
- current_route_path = request.fullpath.match(%r{-/tree/[^/]+/(.+$)}).to_a[1]
-- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
- @content_class = "limit-container-width" unless fluid_layout
- @skip_current_level_breadcrumb = true
- add_page_specific_style 'page_bundles/project'
@@ -14,6 +13,7 @@
= render "home_panel"
- if can?(current_user, :download_code, @project) && @project.repository_languages.present?
+ - add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
= repository_languages_bar(@project.repository_languages)
= render "archived_notice", project: @project
diff --git a/config/application.rb b/config/application.rb
index dde1eae30e7..f64e5c998eb 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -411,6 +411,7 @@ module Gitlab
config.cache_store = :redis_cache_store, Gitlab::Redis::Cache.active_support_config
config.active_job.queue_adapter = :sidekiq
+ config.active_job.logger = nil
config.action_mailer.deliver_later_queue_name = :mailers
# This is needed for gitlab-shell
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 34af9736056..905b6d418a9 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -11651,7 +11651,7 @@ A user assigned to a merge request.
| <a id="mergerequestassigneeid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestassigneelocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestassigneemergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
-| <a id="mergerequestassigneename"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
+| <a id="mergerequestassigneename"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
| <a id="mergerequestassigneenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestassigneeprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestassigneepublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
@@ -11903,7 +11903,7 @@ A user assigned to a merge request as a reviewer.
| <a id="mergerequestreviewerid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestreviewerlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestreviewermergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
-| <a id="mergerequestreviewername"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
+| <a id="mergerequestreviewername"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
| <a id="mergerequestreviewernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestreviewerprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestreviewerpublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
@@ -14970,7 +14970,7 @@ Core represention of a GitLab user.
| <a id="usercoregroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="usercoreid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="usercorelocation"></a>`location` | [`String`](#string) | Location of the user. |
-| <a id="usercorename"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
+| <a id="usercorename"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
| <a id="usercorenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="usercoreprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
| <a id="usercorepublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
@@ -18058,7 +18058,7 @@ Implementations:
| <a id="usergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="userid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="userlocation"></a>`location` | [`String`](#string) | Location of the user. |
-| <a id="username"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
+| <a id="username"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
| <a id="usernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="userprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
| <a id="userpublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md
index 1ca4bb2759d..6646f18e6fe 100644
--- a/doc/user/packages/maven_repository/index.md
+++ b/doc/user/packages/maven_repository/index.md
@@ -806,7 +806,7 @@ When the pipeline is successful, the package is created.
The version string is validated by using the following regex.
```ruby
-\A(\.?[\w\+-]+\.?)+\z
+\A(?!.*\.\.)[\w+.-]+\z
```
You can play around with the regex and try your version strings on [this regular expression editor](https://rubular.com/r/rrLQqUXjfKEoL6).
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index e3f1e90b80f..662ca59852e 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -55,7 +55,9 @@ module API
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
expose(:container_registry_enabled) { |project, options| project.feature_available?(:container_registry, options[:current_user]) }
expose :service_desk_enabled
- expose :service_desk_address
+ expose :service_desk_address, if: -> (project, options) do
+ Ability.allowed?(options[:current_user], :admin_issue, project)
+ end
expose(:can_create_merge_request_in) do |project, options|
Ability.allowed?(options[:current_user], :create_merge_request_in, project)
diff --git a/lib/api/entities/user_safe.rb b/lib/api/entities/user_safe.rb
index feb01767fd6..6006a076020 100644
--- a/lib/api/entities/user_safe.rb
+++ b/lib/api/entities/user_safe.rb
@@ -3,7 +3,17 @@
module API
module Entities
class UserSafe < Grape::Entity
- expose :id, :name, :username
+ expose :id, :username
+ expose :name do |user|
+ next user.name unless user.project_bot?
+
+ next user.name if options[:current_user]&.can?(:read_resource_access_tokens, user.projects.first)
+
+ # If the requester does not have permission to read the project bot name,
+ # the API returns an arbitrary string. UI changes will be addressed in a follow up issue:
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/346058
+ '****'
+ end
end
end
end
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index f1e19e9c3c5..3655cb56564 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -4,6 +4,16 @@ module API
class Lint < ::API::Base
feature_category :pipeline_authoring
+ helpers do
+ def can_lint_ci?
+ signup_unrestricted = Gitlab::CurrentSettings.signup_enabled? && !Gitlab::CurrentSettings.signup_limited?
+ internal_user = current_user.present? && !current_user.external?
+ is_developer = current_user.present? && current_user.projects.any? { |p| p.team.member?(current_user, Gitlab::Access::DEVELOPER) }
+
+ signup_unrestricted || internal_user || is_developer
+ end
+ end
+
namespace :ci do
desc 'Validation of .gitlab-ci.yml content'
params do
@@ -12,7 +22,7 @@ module API
optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response'
end
post '/lint' do
- unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil?
+ unauthorized! unless can_lint_ci?
result = Gitlab::Ci::Lint.new(project: nil, current_user: current_user)
.validate(params[:content], dry_run: false)
diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb
index dd49624c74f..71ca8331ed6 100644
--- a/lib/api/merge_request_approvals.rb
+++ b/lib/api/merge_request_approvals.rb
@@ -26,8 +26,6 @@ module API
# GET /projects/:id/merge_requests/:merge_request_iid/approvals
desc 'List approvals for merge request'
get 'approvals', urgency: :low do
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present_approval(merge_request)
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 470f78a7dc2..8fa7138af42 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -23,8 +23,6 @@ module API
use :pagination
end
get ":id/merge_requests/:merge_request_iid/versions" do
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.merge_request_diffs.order_id_desc), with: Entities::MergeRequestDiff
@@ -41,8 +39,6 @@ module API
end
get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present_cached merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull, cache_context: nil
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 21c1b7969aa..96d1a69c03a 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -264,8 +264,6 @@ module API
success Entities::MergeRequest
end
get ':id/merge_requests/:merge_request_iid', feature_category: :code_review do
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request,
@@ -282,8 +280,6 @@ module API
success Entities::UserBasic
end
get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review do
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
merge_request = find_merge_request_with_access(params[:merge_request_iid])
participants = ::Kaminari.paginate_array(merge_request.participants)
@@ -295,8 +291,6 @@ module API
success Entities::Commit
end
get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review do
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
merge_request = find_merge_request_with_access(params[:merge_request_iid])
commits =
@@ -378,8 +372,6 @@ module API
success Entities::MergeRequestChanges
end
get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review do
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request,
@@ -395,8 +387,6 @@ module API
get ':id/merge_requests/:merge_request_iid/pipelines', feature_category: :continuous_integration do
pipelines = merge_request_pipelines_with_access
- not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
-
present paginate(pipelines), with: Entities::Ci::PipelineBasic
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 57a6ee0bebb..1bc3e25a46c 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -29,10 +29,6 @@ module API
post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder)
- unless can?(current_user, :read_merge_request, issuable.project)
- not_found!(type.split("_").map(&:capitalize).join(" "))
- end
-
todo = TodoService.new.mark_todo(issuable, current_user).first
if todo
diff --git a/lib/banzai/filter/front_matter_filter.rb b/lib/banzai/filter/front_matter_filter.rb
index d47900b816a..705400a5497 100644
--- a/lib/banzai/filter/front_matter_filter.rb
+++ b/lib/banzai/filter/front_matter_filter.rb
@@ -9,7 +9,7 @@ module Banzai
html.sub(Gitlab::FrontMatter::PATTERN) do |_match|
lang = $~[:lang].presence || lang_mapping[$~[:delim]]
- ["```#{lang}:frontmatter", $~[:front_matter], "```", "\n"].join("\n")
+ ["```#{lang}:frontmatter", $~[:front_matter].strip!, "```", "\n"].join("\n")
end
end
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index b9034cff447..2d2d8c41236 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def signup_limited?
- domain_allowlist.present? || email_restrictions_enabled? || require_admin_approval_after_user_signup?
+ domain_allowlist.present? || email_restrictions_enabled? || require_admin_approval_after_user_signup? || user_default_external?
end
def current_application_settings
diff --git a/lib/gitlab/diff/lines_unfolder.rb b/lib/gitlab/diff/lines_unfolder.rb
index 6def3a074a3..04ed5857233 100644
--- a/lib/gitlab/diff/lines_unfolder.rb
+++ b/lib/gitlab/diff/lines_unfolder.rb
@@ -57,6 +57,7 @@ module Gitlab
next false unless @position.unfoldable?
next false if @diff_file.new_file? || @diff_file.deleted_file?
next false unless @position.old_line
+ next false unless @position.old_line.is_a?(Integer)
# Invalid position (MR import scenario)
next false if @position.old_line > @blob.lines.size
next false if @diff_file.diff_lines.empty?
diff --git a/lib/gitlab/front_matter.rb b/lib/gitlab/front_matter.rb
index 7612bd36aca..5c5c74ca1a0 100644
--- a/lib/gitlab/front_matter.rb
+++ b/lib/gitlab/front_matter.rb
@@ -11,13 +11,11 @@ module Gitlab
DELIM = Regexp.union(DELIM_LANG.keys)
PATTERN = %r{
- \A(?:[^\r\n]*coding:[^\r\n]*)? # optional encoding line
+ \A(?:[^\r\n]*coding:[^\r\n]*\R)? # optional encoding line
\s*
- ^(?<delim>#{DELIM})[ \t]*(?<lang>\S*) # opening front matter marker (optional language specifier)
- \s*
- ^(?<front_matter>.*?) # front matter block content (not greedy)
- \s*
- ^(\k<delim> | \.{3}) # closing front matter marker
+ ^(?<delim>#{DELIM})[ \t]*(?<lang>\S*)\R # opening front matter marker (optional language specifier)
+ (?<front_matter>.*?) # front matter block content (not greedy)
+ ^(\k<delim> | \.{3}) # closing front matter marker
\s*
}mx.freeze
end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 0963eb6b72a..f8f61511265 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -27,6 +27,13 @@ module Gitlab
:create_wiki
end
+ override :check_download_access!
+ def check_download_access!
+ super
+
+ raise ForbiddenError, download_forbidden_message if deploy_token && !deploy_token.can?(:download_wiki_code, container)
+ end
+
override :check_change_access!
def check_change_access!
raise ForbiddenError, write_to_wiki_message unless user_can_push?
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index ce886cb8738..dd7ec361dd8 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -52,11 +52,20 @@ module Gitlab
@importable.members.destroy_all # rubocop: disable Cop/DestroyAll
- relation_class.create!(user: @user, access_level: highest_access_level, source_id: @importable.id, importing: true)
+ relation_class.create!(user: @user, access_level: importer_access_level, source_id: @importable.id, importing: true)
rescue StandardError => e
raise e, "Error adding importer user to #{@importable.class} members. #{e.message}"
end
+ def importer_access_level
+ if @importable.parent.is_a?(::Group) && !@user.admin?
+ lvl = @importable.parent.max_member_access_for_user(@user, only_concrete_membership: true)
+ [lvl, highest_access_level].min
+ else
+ highest_access_level
+ end
+ end
+
def user_already_member?
member = @importable.members&.first
diff --git a/lib/gitlab/quick_actions/extractor.rb b/lib/gitlab/quick_actions/extractor.rb
index 1294e475145..2e4817e6b17 100644
--- a/lib/gitlab/quick_actions/extractor.rb
+++ b/lib/gitlab/quick_actions/extractor.rb
@@ -29,9 +29,7 @@ module Gitlab
# Anything, including `/cmd arg` which are ignored by this filter
# `
- `\n*
- .+?
- \n*`
+ `.+?`
)
}mix.freeze
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 8b2f786a91a..904fc744c6b 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -57,7 +57,7 @@ module Gitlab
end
def maven_version_regex
- @maven_version_regex ||= /\A(\.?[\w\+-]+\.?)+\z/.freeze
+ @maven_version_regex ||= /\A(?!.*\.\.)[\w+.-]+\z/.freeze
end
def maven_app_group_regex
diff --git a/lib/gitlab/slash_commands/deploy.rb b/lib/gitlab/slash_commands/deploy.rb
index 157d924f99f..9fcefd99f81 100644
--- a/lib/gitlab/slash_commands/deploy.rb
+++ b/lib/gitlab/slash_commands/deploy.rb
@@ -3,8 +3,18 @@
module Gitlab
module SlashCommands
class Deploy < BaseCommand
+ DEPLOY_REGEX = /\Adeploy\s/.freeze
+
def self.match(text)
- /\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
+ return unless text&.match?(DEPLOY_REGEX)
+
+ from, _, to = text.sub(DEPLOY_REGEX, '').rpartition(/\sto+\s/)
+ return if from.blank? || to.blank?
+
+ {
+ from: from.strip,
+ to: to.strip
+ }
end
def self.help_message
diff --git a/lib/gitlab/wiki_pages/front_matter_parser.rb b/lib/gitlab/wiki_pages/front_matter_parser.rb
index 45dc6cf7fd1..0ceec39782c 100644
--- a/lib/gitlab/wiki_pages/front_matter_parser.rb
+++ b/lib/gitlab/wiki_pages/front_matter_parser.rb
@@ -54,7 +54,7 @@ module Gitlab
def initialize(delim = nil, lang = '', text = nil)
@lang = lang.downcase.presence || Gitlab::FrontMatter::DELIM_LANG[delim]
- @text = text
+ @text = text&.strip!
end
def data
diff --git a/lib/sidebars/projects/menus/analytics_menu.rb b/lib/sidebars/projects/menus/analytics_menu.rb
index b13b25d1cfe..2a89dc66219 100644
--- a/lib/sidebars/projects/menus/analytics_menu.rb
+++ b/lib/sidebars/projects/menus/analytics_menu.rb
@@ -60,7 +60,7 @@ module Sidebars
end
def repository_analytics_menu_item
- if context.project.empty_repo?
+ if context.project.empty_repo? || !can?(context.current_user, :read_repository_graphs, context.project)
return ::Sidebars::NilMenuItem.new(item_id: :repository_analytics)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f87833e829d..1e290fa6c62 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -41577,6 +41577,9 @@ msgstr ""
msgid "must be a valid IPv4 or IPv6 address"
msgstr ""
+msgid "must be a valid json schema"
+msgstr ""
+
msgid "must be after start"
msgstr ""
diff --git a/package.json b/package.json
index 943a176ce75..5016d75f74c 100644
--- a/package.json
+++ b/package.json
@@ -148,7 +148,7 @@
"lowlight": "^1.20.0",
"marked": "^0.3.12",
"mathjax": "3",
- "mermaid": "^8.13.2",
+ "mermaid": "^8.13.4",
"minimatch": "^3.0.4",
"monaco-editor": "^0.25.2",
"monaco-editor-webpack-plugin": "^4.0.0",
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index cf528b414c0..abada97fb10 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -65,7 +65,7 @@ RSpec.describe Dashboard::TodosController do
project_2 = create(:project, namespace: user.namespace)
project_2.add_developer(user)
merge_request_2 = create(:merge_request, source_project: project_2)
- create(:todo, project: project, author: author, user: user, target: merge_request_2)
+ create(:todo, project: project_2, author: author, user: user, target: merge_request_2)
expect { get :index }.not_to exceed_query_limit(control)
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index 6e7bcfdaa08..f9b15c9a48e 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -52,6 +52,44 @@ RSpec.describe GraphqlController do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'executes a simple query with no errors' do
+ post :execute, params: { query: '{ __typename }' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq({ 'data' => { '__typename' => 'Query' } })
+ end
+
+ it 'executes a simple multiplexed query with no errors' do
+ multiplex = [{ query: '{ __typename }' }] * 2
+
+ post :execute, params: { _json: multiplex }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq([
+ { 'data' => { '__typename' => 'Query' } },
+ { 'data' => { '__typename' => 'Query' } }
+ ])
+ end
+
+ it 'sets a limit on the total query size' do
+ graphql_query = "{#{(['__typename'] * 1000).join(' ')}}"
+
+ post :execute, params: { query: graphql_query }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response).to eq({ 'errors' => [{ 'message' => 'Query too large' }] })
+ end
+
+ it 'sets a limit on the total query size for multiplex queries' do
+ graphql_query = "{#{(['__typename'] * 200).join(' ')}}"
+ multiplex = [{ query: graphql_query }] * 5
+
+ post :execute, params: { _json: multiplex }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response).to eq({ 'errors' => [{ 'message' => 'Query too large' }] })
+ end
+
it 'returns forbidden when user cannot access API' do
# User cannot access API in a couple of cases
# * When user is internal(like ghost users)
diff --git a/spec/factories/diff_position.rb b/spec/factories/diff_position.rb
index 41f9a7b574e..bd248452de8 100644
--- a/spec/factories/diff_position.rb
+++ b/spec/factories/diff_position.rb
@@ -43,8 +43,12 @@ FactoryBot.define do
trait :multi_line do
line_range do
{
- start_line_code: Gitlab::Git.diff_line_code(file, 10, 10),
- end_line_code: Gitlab::Git.diff_line_code(file, 12, 13)
+ start: {
+ line_code: Gitlab::Git.diff_line_code(file, 10, 10)
+ },
+ end: {
+ line_code: Gitlab::Git.diff_line_code(file, 12, 13)
+ }
}
end
end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 8281e82959b..9d05c985af1 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -7,8 +7,8 @@ RSpec.describe 'File blob', :js do
let(:project) { create(:project, :public, :repository) }
- def visit_blob(path, anchor: nil, ref: 'master')
- visit project_blob_path(project, File.join(ref, path), anchor: anchor)
+ def visit_blob(path, anchor: nil, ref: 'master', **additional_args)
+ visit project_blob_path(project, File.join(ref, path), anchor: anchor, **additional_args)
wait_for_requests
end
@@ -1501,6 +1501,53 @@ RSpec.describe 'File blob', :js do
end
end
end
+
+ context 'openapi.yml' do
+ before do
+ file_name = 'openapi.yml'
+
+ create_file(file_name, '
+ swagger: \'2.0\'
+ info:
+ title: Classic API Resource Documentation
+ description: |
+ <div class="foo-bar" style="background-color: red;" data-foo-bar="baz">
+ <h1>Swagger API documentation</h1>
+ </div>
+ version: production
+ basePath: /JSSResource/
+ produces:
+ - application/xml
+ - application/json
+ consumes:
+ - application/xml
+ - application/json
+ security:
+ - basicAuth: []
+ paths:
+ /accounts:
+ get:
+ responses:
+ \'200\':
+ description: No response was specified
+ tags:
+ - accounts
+ operationId: findAccounts
+ summary: Finds all accounts
+ ')
+ visit_blob(file_name, useUnsafeMarkdown: '1')
+ click_button('Display rendered file')
+
+ wait_for_requests
+ end
+
+ it 'removes `style`, `class`, and `data-*`` attributes from HTML' do
+ expect(page).to have_css('h1', text: 'Swagger API documentation')
+ expect(page).not_to have_css('.foo-bar')
+ expect(page).not_to have_css('[style="background-color: red;"]')
+ expect(page).not_to have_css('[data-foo-bar="baz"]')
+ end
+ end
end
end
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index 25598146604..308098c72a1 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -147,7 +147,7 @@ RSpec.describe 'Project members list', :js do
it 'does not show form used to change roles and "Expiration date" or the remove user button', :aggregate_failures do
visit_members_page
- page.within find_member_row(project_bot) do
+ page.within find_username_row(project_bot) do
expect(page).not_to have_button('Maintainer')
expect(page).to have_field('Expiration date', disabled: true)
expect(page).not_to have_button('Remove member')
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index c4619b5498e..26deca9c8f1 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -383,6 +383,24 @@ RSpec.describe 'Project' do
{ form: '.rspec-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
end
+ describe 'view for a user without an access to a repo' do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ it 'does not contain default branch information in its content' do
+ default_branch = 'merge-commit-analyze-side-branch'
+
+ project.add_guest(user)
+ project.change_head(default_branch)
+
+ sign_in(user)
+ visit project_path(project)
+
+ lines_with_default_branch = page.html.lines.select { |line| line.include?(default_branch) }
+ expect(lines_with_default_branch).to eq([])
+ end
+ end
+
def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confirm')
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 6fbed21acdb..15ec11c256f 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -118,12 +118,12 @@ RSpec.describe 'Protected Branches', :js do
it "allows creating explicit protected branches" do
visit project_protected_branches_path(project)
set_defaults
- set_protected_branch_name('some-branch')
+ set_protected_branch_name('some->branch')
click_on "Protect"
- within(".protected-branches-list") { expect(page).to have_content('some-branch') }
+ within(".protected-branches-list") { expect(page).to have_content('some->branch') }
expect(ProtectedBranch.count).to eq(1)
- expect(ProtectedBranch.last.name).to eq('some-branch')
+ expect(ProtectedBranch.last.name).to eq('some->branch')
end
it "displays the last commit on the matching branch if it exists" do
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 73de0a6d381..55c0141552d 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -138,7 +138,7 @@ describe('DiffsStoreUtils', () => {
old_line: 1,
},
linePosition: LINE_POSITION_LEFT,
- lineRange: { start_line_code: 'abc_1_1', end_line_code: 'abc_2_2' },
+ lineRange: { start: { line_code: 'abc_1_1' }, end: { line_code: 'abc_2_2' } },
};
const position = JSON.stringify({
@@ -608,7 +608,7 @@ describe('DiffsStoreUtils', () => {
// When multi line comments are fully implemented `line_code` will be
// included in all requests. Until then we need to ensure the logic does
// not change when it is included only in the "comparison" argument.
- const lineRange = { start_line_code: 'abc_1_1', end_line_code: 'abc_1_2' };
+ const lineRange = { start: { line_code: 'abc_1_1' }, end: { line_code: 'abc_1_2' } };
it('returns true when the discussion is up to date', () => {
expect(
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 3fa0dc95126..02c686af688 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -35,6 +35,10 @@ RSpec.describe GitlabSchema do
expect(connection).to eq(Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection)
end
+ it 'sets an appropriate validation timeout' do
+ expect(described_class.validate_timeout).to be <= 0.2.seconds
+ end
+
describe '.execute' do
describe 'setting query `max_complexity` and `max_depth`' do
subject(:result) { described_class.execute('query', **kwargs).query }
@@ -195,6 +199,36 @@ RSpec.describe GitlabSchema do
end
end
+ describe 'validate_max_errors' do
+ it 'reports at most 5 errors' do
+ query = <<~GQL
+ query {
+ currentUser {
+ x: id
+ x: bot
+ x: username
+ x: state
+ x: name
+
+ x: id
+ x: bot
+ x: username
+ x: state
+ x: name
+
+ badField
+ veryBadField
+ alsoNotAGoodField
+ }
+ }
+ GQL
+
+ result = described_class.execute(query)
+
+ expect(result.to_h['errors'].count).to eq 5
+ end
+ end
+
describe '.parse_gid' do
let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' }
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 0bad8c95ba2..4e3f442dc71 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -44,6 +44,86 @@ RSpec.describe GitlabSchema.types['User'] do
expect(described_class).to have_graphql_fields(*expected_fields)
end
+ describe 'name field' do
+ let_it_be(:admin) { create(:user, :admin)}
+ let_it_be(:user) { create(:user) }
+ let_it_be(:requested_user) { create(:user, name: 'John Smith') }
+ let_it_be(:requested_project_bot) { create(:user, :project_bot, name: 'Project bot') }
+ let_it_be(:project) { create(:project, :public) }
+
+ before do
+ project.add_maintainer(requested_project_bot)
+ end
+
+ let(:username) { requested_user.username }
+
+ let(:query) do
+ %(
+ query {
+ user(username: "#{username}") {
+ name
+ }
+ }
+ )
+ end
+
+ subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json.dig('data', 'user', 'name') }
+
+ context 'user requests' do
+ let(:current_user) { user }
+
+ context 'a user' do
+ it 'returns name' do
+ expect(subject).to eq('John Smith')
+ end
+ end
+
+ context 'a project bot' do
+ let(:username) { requested_project_bot.username }
+
+ context 'when requester is nil' do
+ let(:current_user) { nil }
+
+ it 'returns `****`' do
+ expect(subject).to eq('****')
+ end
+ end
+
+ it 'returns `****` for a regular user' do
+ expect(subject).to eq('****')
+ end
+
+ context 'when requester is a project maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns name' do
+ expect(subject).to eq('Project bot')
+ end
+ end
+ end
+ end
+
+ context 'admin requests', :enable_admin_mode do
+ let(:current_user) { admin }
+
+ context 'a user' do
+ it 'returns name' do
+ expect(subject).to eq('John Smith')
+ end
+ end
+
+ context 'a project bot' do
+ let(:username) { requested_project_bot.username }
+
+ it 'returns name' do
+ expect(subject).to eq('Project bot')
+ end
+ end
+ end
+ end
+
describe 'snippets field' do
subject { described_class.fields['snippets'] }
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 9e870658870..17dcbab09bb 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -174,12 +174,26 @@ RSpec.describe SearchHelper do
context "with a current project" do
before do
@project = create(:project, :repository)
+
+ allow(self).to receive(:can?).and_return(true)
allow(self).to receive(:can?).with(user, :read_feature_flag, @project).and_return(false)
end
- it "includes project-specific sections", :aggregate_failures do
+ it 'returns repository related labels based on users abilities', :aggregate_failures do
expect(search_autocomplete_opts("Files").size).to eq(1)
expect(search_autocomplete_opts("Commits").size).to eq(1)
+ expect(search_autocomplete_opts("Network").size).to eq(1)
+ expect(search_autocomplete_opts("Graph").size).to eq(1)
+
+ allow(self).to receive(:can?).with(user, :download_code, @project).and_return(false)
+
+ expect(search_autocomplete_opts("Files").size).to eq(0)
+ expect(search_autocomplete_opts("Commits").size).to eq(0)
+
+ allow(self).to receive(:can?).with(user, :read_repository_graphs, @project).and_return(false)
+
+ expect(search_autocomplete_opts("Network").size).to eq(0)
+ expect(search_autocomplete_opts("Graph").size).to eq(0)
end
context 'when user does not have access to project' do
diff --git a/spec/lib/api/entities/project_spec.rb b/spec/lib/api/entities/project_spec.rb
index 8d1c3aa878d..6b542278fa6 100644
--- a/spec/lib/api/entities/project_spec.rb
+++ b/spec/lib/api/entities/project_spec.rb
@@ -13,6 +13,28 @@ RSpec.describe ::API::Entities::Project do
subject(:json) { entity.as_json }
+ describe '.service_desk_address' do
+ before do
+ allow(project).to receive(:service_desk_enabled?).and_return(true)
+ end
+
+ context 'when a user can admin issues' do
+ before do
+ project.add_reporter(current_user)
+ end
+
+ it 'is present' do
+ expect(json[:service_desk_address]).to be_present
+ end
+ end
+
+ context 'when a user can not admin project' do
+ it 'is empty' do
+ expect(json[:service_desk_address]).to be_nil
+ end
+ end
+ end
+
describe '.shared_with_groups' do
let(:group) { create(:group, :private) }
diff --git a/spec/lib/api/entities/user_spec.rb b/spec/lib/api/entities/user_spec.rb
index 9c9a157d68a..14dc60e1a5f 100644
--- a/spec/lib/api/entities/user_spec.rb
+++ b/spec/lib/api/entities/user_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe API::Entities::User do
subject { entity.as_json }
it 'exposes correct attributes' do
- expect(subject).to include(:bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information, :pronouns)
+ expect(subject).to include(:name, :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information, :pronouns)
end
it 'exposes created_at if the current user can read the user profile' do
@@ -31,12 +31,51 @@ RSpec.describe API::Entities::User do
expect(subject[:bot]).to be_falsey
end
- context 'with bot user' do
- let(:user) { create(:user, :security_bot) }
+ context 'with project bot user' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user, :project_bot, name: 'secret') }
+
+ before do
+ project.add_maintainer(user)
+ end
it 'exposes user as a bot' do
expect(subject[:bot]).to eq(true)
end
+
+ context 'when the requester is not an admin' do
+ it 'does not expose project bot user name' do
+ expect(subject[:name]).to eq('****')
+ end
+ end
+
+ context 'when the requester is nil' do
+ let(:current_user) { nil }
+
+ it 'does not expose project bot user name' do
+ expect(subject[:name]).to eq('****')
+ end
+ end
+
+ context 'when the requester is a project maintainer' do
+ let(:current_user) { create(:user) }
+
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ it 'exposes project bot user name' do
+ expect(subject[:name]).to eq('secret')
+ end
+ end
+
+ context 'when the requester is an admin' do
+ let(:current_user) { create(:user, :admin) }
+
+ it 'exposes project bot user name', :enable_admin_mode do
+ expect(subject[:name]).to eq('secret')
+ end
+ end
end
it 'exposes local_time' do
diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb
index cef6a2ddcce..1562c388296 100644
--- a/spec/lib/banzai/filter/front_matter_filter_spec.rb
+++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb
@@ -139,4 +139,20 @@ RSpec.describe Banzai::Filter::FrontMatterFilter do
end
end
end
+
+ it 'fails fast for strings with many spaces' do
+ content = "coding:" + " " * 50_000 + ";"
+
+ expect do
+ Timeout.timeout(3.seconds) { filter(content) }
+ end.not_to raise_error
+ end
+
+ it 'fails fast for strings with many newlines' do
+ content = "coding:\n" + ";;;" + "\n" * 10_000 + "x"
+
+ expect do
+ Timeout.timeout(3.seconds) { filter(content) }
+ end.not_to raise_error
+ end
end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index a5ab1047a40..46c33d7b7b2 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -51,9 +51,17 @@ RSpec.describe Gitlab::CurrentSettings do
it { is_expected.to be_truthy }
end
+ context 'when new users are set to external' do
+ before do
+ create(:application_setting, user_default_external: 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)
+ create(:application_setting, domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false, user_default_external: false)
end
it { is_expected.to be_falsey }
diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
index 41877a16ebf..b6bdc5ff493 100644
--- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
@@ -47,14 +47,14 @@ RSpec.describe Gitlab::Diff::Formatters::TextFormatter do
describe "#==" do
it "is false when the line_range changes" do
- formatter_1 = described_class.new(base.merge(line_range: { start_line_code: "foo", end_line_code: "bar" }))
- formatter_2 = described_class.new(base.merge(line_range: { start_line_code: "foo", end_line_code: "baz" }))
+ formatter_1 = described_class.new(base.merge(line_range: { "start": { "line_code" => "foo" }, "end": { "line_code" => "bar" } }))
+ formatter_2 = described_class.new(base.merge(line_range: { "start": { "line_code" => "foo" }, "end": { "line_code" => "baz" } }))
expect(formatter_1).not_to eq(formatter_2)
end
it "is true when the line_range doesn't change" do
- attrs = base.merge({ line_range: { start_line_code: "foo", end_line_code: "baz" } })
+ attrs = base.merge({ line_range: { start: { line_code: "foo" }, end: { line_code: "baz" } } })
formatter_1 = described_class.new(attrs)
formatter_2 = described_class.new(attrs)
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index 8385cba3532..f0e710be2e4 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -215,6 +215,16 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
build(:text_diff_position, old_line: 43, new_line: 40)
end
+ context 'old_line is an invalid number' do
+ let(:position) do
+ build(:text_diff_position, old_line: "foo", new_line: 40)
+ end
+
+ it 'fails gracefully' do
+ expect(subject.unfolded_diff_lines).to be_nil
+ end
+ end
+
context 'blob lines' do
let(:expected_blob_lines) do
[[40, 40, " \"config-opts\": [ \"--disable-introspection\" ],"],
diff --git a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
index b646cf38178..c46f476899e 100644
--- a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
@@ -295,8 +295,12 @@ RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy, :clean_gitlab_redis_c
new_path: file_name,
new_line: 2,
line_range: {
- "start_line_code" => 1,
- "end_line_code" => 2
+ "start" => {
+ "line_code" => 1
+ },
+ "end" => {
+ "line_code" => 2
+ }
}
)
end
@@ -575,8 +579,12 @@ RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy, :clean_gitlab_redis_c
new_path: file_name,
new_line: 2,
line_range: {
- "start_line_code" => 1,
- "end_line_code" => 2
+ "start" => {
+ "line_code" => 1
+ },
+ "end" => {
+ "line_code" => 2
+ }
}
)
end
@@ -588,8 +596,12 @@ RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy, :clean_gitlab_redis_c
old_line: nil,
new_line: 2,
line_range: {
- "start_line_code" => 1,
- "end_line_code" => 2
+ "start" => {
+ "line_code" => 1
+ },
+ "end" => {
+ "line_code" => 2
+ }
}
)
end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 5ada8a6ef40..27175dc8c44 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -79,5 +79,30 @@ RSpec.describe Gitlab::GitAccessWiki do
let(:message) { include('wiki') }
end
end
+
+ context 'when the actor is a deploy token' do
+ let_it_be(:actor) { create(:deploy_token, projects: [project]) }
+ let_it_be(:user) { actor }
+
+ before do
+ project.project_feature.update_attribute(:wiki_access_level, wiki_access_level)
+ end
+
+ subject { access.check('git-upload-pack', changes) }
+
+ context 'when the wiki is enabled' do
+ let(:wiki_access_level) { ProjectFeature::ENABLED }
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'when the wiki is disabled' do
+ let(:wiki_access_level) { ProjectFeature::DISABLED }
+
+ it_behaves_like 'forbidden git access' do
+ let(:message) { 'You are not allowed to download files from this wiki.' }
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb
index 473dbf5ecc5..ce6607f6a26 100644
--- a/spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/relation_tree_restorer_spec.rb
@@ -10,8 +10,8 @@
require 'spec_helper'
RSpec.describe Gitlab::ImportExport::Group::RelationTreeRestorer do
- let_it_be(:group) { create(:group) }
- let_it_be(:importable) { create(:group, parent: group) }
+ let(:group) { create(:group).tap { |g| g.add_owner(user) } }
+ let(:importable) { create(:group, parent: group) }
include_context 'relation tree restorer shared context' do
let(:importable_name) { nil }
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 847d6b5d1ed..8b9ca90a280 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -267,6 +267,66 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
end
end
+ context 'when importer is not an admin' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: [], user: user, importable: importable)
+ end
+
+ shared_examples_for 'it fetches the access level from parent group' do
+ before do
+ group.add_users([user], group_access_level)
+ end
+
+ it "and resolves it correctly" do
+ members_mapper.map
+ expect(member_class.find_by_user_id(user.id).access_level).to eq(resolved_access_level)
+ end
+ end
+
+ context 'and the imported project is part of a group' do
+ let(:importable) { create(:project, namespace: group) }
+ let(:member_class) { ProjectMember }
+
+ it_behaves_like 'it fetches the access level from parent group' do
+ let(:group_access_level) { GroupMember::DEVELOPER }
+ let(:resolved_access_level) { ProjectMember::DEVELOPER }
+ end
+
+ it_behaves_like 'it fetches the access level from parent group' do
+ let(:group_access_level) { GroupMember::MAINTAINER }
+ let(:resolved_access_level) { ProjectMember::MAINTAINER }
+ end
+
+ it_behaves_like 'it fetches the access level from parent group' do
+ let(:group_access_level) { GroupMember::OWNER }
+ let(:resolved_access_level) { ProjectMember::MAINTAINER }
+ end
+ end
+
+ context 'and the imported group is part of another group' do
+ let(:importable) { create(:group, parent: group) }
+ let(:member_class) { GroupMember }
+
+ it_behaves_like 'it fetches the access level from parent group' do
+ let(:group_access_level) { GroupMember::DEVELOPER }
+ let(:resolved_access_level) { GroupMember::DEVELOPER }
+ end
+
+ it_behaves_like 'it fetches the access level from parent group' do
+ let(:group_access_level) { GroupMember::MAINTAINER }
+ let(:resolved_access_level) { GroupMember::MAINTAINER }
+ end
+
+ it_behaves_like 'it fetches the access level from parent group' do
+ let(:group_access_level) { GroupMember::OWNER }
+ let(:resolved_access_level) { GroupMember::OWNER }
+ end
+ end
+ end
+
context 'when importable is Group' do
include_examples 'imports exported members' do
let(:source_type) { 'Namespace' }
diff --git a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
index 49df2313924..80ba50976af 100644
--- a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_memory_store_caching do
- let(:group) { create(:group) }
+ let(:group) { create(:group).tap { |g| g.add_maintainer(importer_user) } }
let(:project) { create(:project, :repository, group: group) }
let(:members_mapper) { double('members_mapper').as_null_object }
let(:admin) { create(:admin) }
diff --git a/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb
index 5ebace263ba..577f1e46db6 100644
--- a/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb
@@ -101,7 +101,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationTreeRestorer do
it_behaves_like 'import project successfully'
context 'with logging of relations creation' do
- let_it_be(:group) { create(:group) }
+ let_it_be(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
let_it_be(:importable) do
create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project', group: group)
end
@@ -118,7 +118,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationTreeRestorer do
context 'when inside a group' do
let_it_be(:group) do
- create(:group, :disabled_and_unoverridable)
+ create(:group, :disabled_and_unoverridable).tap { |g| g.add_maintainer(user) }
end
before do
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index cd3d29f1a51..6bb6be07749 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -674,6 +674,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
# Project needs to be in a group for visibility level comparison
# to happen
group = create(:group)
+ group.add_maintainer(user)
project.group = group
project.create_import_data(data: { override_params: { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } })
@@ -715,13 +716,19 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
context 'with a project that has a group' do
+ let(:group) do
+ create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE).tap do |g|
+ g.add_maintainer(user)
+ end
+ end
+
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
- group: create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE))
+ group: group)
end
before do
@@ -750,13 +757,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
context 'with existing group models' do
+ let(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
- group: create(:group))
+ group: group)
end
before do
@@ -785,13 +793,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
context 'with clashing milestones on IID' do
+ let(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
- group: create(:group))
+ group: group)
end
before do
@@ -870,7 +879,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
context 'with group visibility' do
before do
group = create(:group, visibility_level: group_visibility)
-
+ group.add_users([user], GroupMember::MAINTAINER)
project.update(group: group)
end
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index 61fffe3fb6b..c040a70e403 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -352,6 +352,14 @@ RSpec.describe Gitlab::QuickActions::Extractor do
expect(commands).to eq(expected_commands)
expect(msg).to eq expected_msg
end
+
+ it 'fails fast for strings with many newlines' do
+ msg = '`' + "\n" * 100_000
+
+ expect do
+ Timeout.timeout(3.seconds) { extractor.extract_commands(msg) }
+ end.not_to raise_error
+ end
end
describe '#redact_commands' do
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 9514654204b..05f1c88a6ab 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -344,6 +344,18 @@ RSpec.describe Gitlab::Regex do
describe '.maven_version_regex' do
subject { described_class.maven_version_regex }
+ it 'has no ReDoS issues with long strings' do
+ Timeout.timeout(5) do
+ expect(subject).to match("aaaaaaaa.aaaaaaaaa+aa-111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111")
+ end
+ end
+
+ it 'has no ReDos issues with long strings ending with an exclamation mark' do
+ Timeout.timeout(5) do
+ expect(subject).not_to match('a' * 50000 + '!')
+ end
+ end
+
it { is_expected.to match('0')}
it { is_expected.to match('1') }
it { is_expected.to match('03') }
@@ -364,6 +376,7 @@ RSpec.describe Gitlab::Regex do
it { is_expected.to match('703220b4e2cea9592caeb9f3013f6b1e5335c293') }
it { is_expected.to match('RELEASE') }
it { is_expected.not_to match('..1.2.3') }
+ it { is_expected.not_to match('1.2.3..beta') }
it { is_expected.not_to match(' 1.2.3') }
it { is_expected.not_to match("1.2.3 \r\t") }
it { is_expected.not_to match("\r\t 1.2.3") }
diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
index 36f47c711bc..71fca1e1fc8 100644
--- a/spec/lib/gitlab/slash_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -109,6 +109,21 @@ RSpec.describe Gitlab::SlashCommands::Deploy do
end
end
end
+
+ context 'with extra spaces in the deploy command' do
+ let(:regex_match) { described_class.match('deploy staging to production ') }
+
+ before do
+ create(:ci_build, :manual, pipeline: pipeline, name: 'production', environment: 'production')
+ create(:ci_build, :manual, pipeline: pipeline, name: 'not prod', environment: 'not prod')
+ end
+
+ it 'deploys to production' do
+ expect(subject[:text])
+ .to start_with('Deployment started from staging to production')
+ expect(subject[:response_type]).to be(:in_channel)
+ end
+ end
end
end
@@ -119,5 +134,49 @@ RSpec.describe Gitlab::SlashCommands::Deploy do
expect(match[:from]).to eq('staging')
expect(match[:to]).to eq('production')
end
+
+ it 'matches the environment with spaces in it' do
+ match = described_class.match('deploy staging env to production env')
+
+ expect(match[:from]).to eq('staging env')
+ expect(match[:to]).to eq('production env')
+ end
+
+ it 'matches the environment name with surrounding spaces' do
+ match = described_class.match('deploy staging to production ')
+
+ # The extra spaces are stripped later in the code
+ expect(match[:from]).to eq('staging')
+ expect(match[:to]).to eq('production')
+ end
+
+ it 'returns nil for text that is not a deploy command' do
+ match = described_class.match('foo bar')
+
+ expect(match).to be_nil
+ end
+
+ it 'returns nil for a partial command' do
+ match = described_class.match('deploy staging to ')
+
+ expect(match).to be_nil
+ end
+
+ context 'with ReDoS attempts' do
+ def duration_for(&block)
+ start = Time.zone.now
+ yield if block_given?
+ Time.zone.now - start
+ end
+
+ it 'has smaller than linear execution time growth with a malformed "to"' do
+ Timeout.timeout(3.seconds) do
+ sample1 = duration_for { described_class.match("deploy abc t" + "o" * 1000 + "X") }
+ sample2 = duration_for { described_class.match("deploy abc t" + "o" * 4000 + "X") }
+
+ expect((sample2 / sample1) < 4).to be_truthy
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
index c78103f33f4..3152dc2ad2f 100644
--- a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
+++ b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
@@ -118,7 +118,7 @@ RSpec.describe Gitlab::WikiPages::FrontMatterParser do
MD
end
- it { is_expected.to have_attributes(reason: :not_mapping) }
+ it { is_expected.to have_attributes(reason: :no_match) }
end
context 'there is a string in the YAML block' do
diff --git a/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb b/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb
index 9d5f029fff5..6f2ca719bc9 100644
--- a/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/analytics_menu_spec.rb
@@ -102,6 +102,12 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
specify { is_expected.to be_nil }
end
+ describe 'when a user does not have access to repository graphs' do
+ let(:current_user) { guest }
+
+ specify { is_expected.to be_nil }
+ end
+
describe 'when the user does not have access' do
let(:current_user) { nil }
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 6ee5219819c..44ba6e0e2fd 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -289,7 +289,6 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to allow_value('1.1-beta-2').for(:version) }
it { is_expected.to allow_value('1.2-SNAPSHOT').for(:version) }
it { is_expected.to allow_value('12.1.2-2-1').for(:version) }
- it { is_expected.to allow_value('1.2.3..beta').for(:version) }
it { is_expected.to allow_value('1.2.3-beta').for(:version) }
it { is_expected.to allow_value('10.2.3-beta').for(:version) }
it { is_expected.to allow_value('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq').for(:version) }
@@ -297,6 +296,7 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to allow_value('703220b4e2cea9592caeb9f3013f6b1e5335c293').for(:version) }
it { is_expected.to allow_value('RELEASE').for(:version) }
it { is_expected.not_to allow_value('..1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('1.2.3..beta').for(:version) }
it { is_expected.not_to allow_value(' 1.2.3').for(:version) }
it { is_expected.not_to allow_value("1.2.3 \r\t").for(:version) }
it { is_expected.not_to allow_value("\r\t 1.2.3").for(:version) }
diff --git a/spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb b/spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb
index 5fc7bfb1f62..2060e6cd44a 100644
--- a/spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb
+++ b/spec/models/preloaders/user_max_access_level_in_groups_preloader_spec.rb
@@ -13,7 +13,8 @@ RSpec.describe Preloaders::UserMaxAccessLevelInGroupsPreloader do
shared_examples 'executes N max member permission queries to the DB' do
it 'executes the specified max membership queries' do
- expect { groups.each { |group| user.can?(:read_group, group) } }.to make_queries_matching(max_query_regex, expected_query_count)
+ expect { groups.each { |group| user.can?(:read_group, group) } }
+ .to make_queries_matching(max_query_regex, expected_query_count)
end
it 'caches the correct access_level for each group' do
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index b94df4d4374..e05de25f182 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -5,10 +5,11 @@ require 'spec_helper'
RSpec.describe MergeRequestPolicy do
include ExternalAuthorizationServiceHelpers
- let(:guest) { create(:user) }
- let(:author) { create(:user) }
- let(:developer) { create(:user) }
- let(:non_team_member) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:author) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:non_team_member) { create(:user) }
+
let(:project) { create(:project, :public) }
def permissions(user, merge_request)
@@ -50,15 +51,31 @@ RSpec.describe MergeRequestPolicy do
end
context 'when merge request is public' do
- context 'and user is anonymous' do
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+ context 'and user is anonymous' do
subject { permissions(nil, merge_request) }
it do
is_expected.to be_disallowed(:create_todo, :update_subscription)
end
end
+
+ describe 'the author, who became a guest' do
+ subject { permissions(author, merge_request) }
+
+ it do
+ is_expected.to be_allowed(:update_merge_request)
+ end
+
+ it do
+ is_expected.to be_allowed(:reopen_merge_request)
+ end
+
+ it do
+ is_expected.to be_allowed(:approve_merge_request)
+ end
+ end
end
context 'when merge requests have been disabled' do
@@ -107,6 +124,12 @@ RSpec.describe MergeRequestPolicy do
it_behaves_like 'a denied user'
end
+ describe 'the author' do
+ subject { author }
+
+ it_behaves_like 'a denied user'
+ end
+
describe 'a developer' do
subject { developer }
diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb
index 59b805bb25b..1cba3674d25 100644
--- a/spec/requests/api/graphql/user_query_spec.rb
+++ b/spec/requests/api/graphql/user_query_spec.rb
@@ -488,5 +488,19 @@ RSpec.describe 'getting user information' do
end
end
end
+
+ context 'the user is project bot' do
+ let(:user) { create(:user, :project_bot) }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ context 'we only request basic fields' do
+ let(:user_fields) { %i[id name username state web_url avatar_url] }
+
+ it_behaves_like 'a working graphql query'
+ end
+ end
end
end
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index ac30da99afe..0e83b964121 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -26,6 +26,35 @@ RSpec.describe API::Lint do
expect(response).to have_gitlab_http_status(:ok)
end
end
+
+ context 'when authenticated as external user' do
+ let(:project) { create(:project) }
+ let(:api_user) { create(:user, :external) }
+
+ context 'when reporter in a project' do
+ before do
+ project.add_reporter(api_user)
+ end
+
+ it 'returns authorization failure' do
+ post api('/ci/lint', api_user), params: { content: 'content' }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when developer in a project' do
+ before do
+ project.add_developer(api_user)
+ end
+
+ it 'returns authorization success' do
+ post api('/ci/lint', api_user), params: { content: 'content' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
end
context 'when signup is enabled and not limited' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 4f84e6f2562..cc546cbcda1 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -225,7 +225,7 @@ RSpec.describe API::Projects do
create(:project, :public, group: create(:group))
end
- it_behaves_like 'projects response without N + 1 queries', 0 do
+ it_behaves_like 'projects response without N + 1 queries', 1 do
let(:current_user) { user }
let(:additional_project) { create(:project, :public, group: create(:group)) }
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index c9deb84ff98..c6b4f50afae 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -378,30 +378,36 @@ RSpec.describe API::Todos do
expect(response).to have_gitlab_http_status(:not_found)
end
end
-
- it 'returns an error if the issuable author does not have access' do
- project_1.add_guest(issuable.author)
-
- post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", issuable.author)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
end
describe 'POST :id/issuable_type/:issueable_id/todo' do
context 'for an issue' do
- it_behaves_like 'an issuable', 'issues' do
- let_it_be(:issuable) do
- create(:issue, :confidential, author: author_1, project: project_1)
- end
+ let_it_be(:issuable) do
+ create(:issue, :confidential, project: project_1)
+ end
+
+ it_behaves_like 'an issuable', 'issues'
+
+ it 'returns an error if the issue author does not have access' do
+ post api("/projects/#{project_1.id}/issues/#{issuable.iid}/todo", issuable.author)
+
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'for a merge request' do
- it_behaves_like 'an issuable', 'merge_requests' do
- let_it_be(:issuable) do
- create(:merge_request, :simple, source_project: project_1)
- end
+ let_it_be(:issuable) do
+ create(:merge_request, :simple, source_project: project_1)
+ end
+
+ it_behaves_like 'an issuable', 'merge_requests'
+
+ it 'returns an error if the merge request author does not have access' do
+ project_1.add_guest(issuable.author)
+
+ post api("/projects/#{project_1.id}/merge_requests/#{issuable.iid}/todo", issuable.author)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 45462831a31..756c775be9b 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -7,13 +7,15 @@ RSpec.describe ProtectedBranches::CreateService do
let(:user) { project.owner }
let(:params) do
{
- name: 'master',
+ name: name,
merge_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }],
push_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }]
}
end
describe '#execute' do
+ let(:name) { 'master' }
+
subject(:service) { described_class.new(project, user, params) }
it 'creates a new protected branch' do
@@ -22,6 +24,41 @@ RSpec.describe ProtectedBranches::CreateService do
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
end
+ context 'when name has escaped HTML' do
+ let(:name) { 'feature-&gt;test' }
+
+ it 'creates the new protected branch matching the unescaped version' do
+ expect { service.execute }.to change(ProtectedBranch, :count).by(1)
+ expect(project.protected_branches.last.name).to eq('feature->test')
+ end
+
+ context 'and name contains HTML tags' do
+ let(:name) { '&lt;b&gt;master&lt;/b&gt;' }
+
+ it 'creates the new protected branch with sanitized name' do
+ expect { service.execute }.to change(ProtectedBranch, :count).by(1)
+ expect(project.protected_branches.last.name).to eq('master')
+ end
+
+ context 'and contains unsafe HTML' do
+ let(:name) { '&lt;script&gt;alert(&#39;foo&#39;);&lt;/script&gt;' }
+
+ it 'does not create the new protected branch' do
+ expect { service.execute }.not_to change(ProtectedBranch, :count)
+ end
+ end
+ end
+
+ context 'when name contains unescaped HTML tags' do
+ let(:name) { '<b>master</b>' }
+
+ it 'creates the new protected branch with sanitized name' do
+ expect { service.execute }.to change(ProtectedBranch, :count).by(1)
+ expect(project.protected_branches.last.name).to eq('master')
+ end
+ end
+ end
+
context 'when user does not have permission' do
let(:user) { create(:user) }
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 88e58ad5907..b5cf1a54aff 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -6,17 +6,50 @@ RSpec.describe ProtectedBranches::UpdateService do
let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project }
let(:user) { project.owner }
- let(:params) { { name: 'new protected branch name' } }
+ let(:params) { { name: new_name } }
describe '#execute' do
+ let(:new_name) { 'new protected branch name' }
+ let(:result) { service.execute(protected_branch) }
+
subject(:service) { described_class.new(project, user, params) }
it 'updates a protected branch' do
- result = service.execute(protected_branch)
-
expect(result.reload.name).to eq(params[:name])
end
+ context 'when name has escaped HTML' do
+ let(:new_name) { 'feature-&gt;test' }
+
+ it 'updates protected branch name with unescaped HTML' do
+ expect(result.reload.name).to eq('feature->test')
+ end
+
+ context 'and name contains HTML tags' do
+ let(:new_name) { '&lt;b&gt;master&lt;/b&gt;' }
+
+ it 'updates protected branch name with sanitized name' do
+ expect(result.reload.name).to eq('master')
+ end
+
+ context 'and contains unsafe HTML' do
+ let(:new_name) { '&lt;script&gt;alert(&#39;foo&#39;);&lt;/script&gt;' }
+
+ it 'does not update the protected branch' do
+ expect(result.reload.name).to eq(protected_branch.name)
+ end
+ end
+ end
+ end
+
+ context 'when name contains unescaped HTML tags' do
+ let(:new_name) { '<b>master</b>' }
+
+ it 'updates protected branch name with sanitized name' do
+ expect(result.reload.name).to eq('master')
+ end
+ end
+
context 'without admin_project permissions' do
let(:user) { create(:user) }
diff --git a/spec/support/helpers/features/members_helpers.rb b/spec/support/helpers/features/members_helpers.rb
index 2e86e014a1b..bdadcb8af43 100644
--- a/spec/support/helpers/features/members_helpers.rb
+++ b/spec/support/helpers/features/members_helpers.rb
@@ -37,6 +37,10 @@ module Spec
find_row(user.name)
end
+ def find_username_row(user)
+ find_row(user.username)
+ end
+
def find_invited_member_row(email)
find_row(email)
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index ee4621deb2d..1f0c9b658dc 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -374,6 +374,7 @@ module GraphqlHelpers
allow_unlimited_graphql_depth if max_depth > 1
allow_high_graphql_recursion
allow_high_graphql_transaction_threshold
+ allow_high_graphql_query_size
type = class_name.respond_to?(:kind) ? class_name : GitlabSchema.types[class_name.to_s]
raise "#{class_name} is not a known type in the GitlabSchema" unless type
@@ -624,6 +625,10 @@ module GraphqlHelpers
stub_const("Gitlab::QueryLimiting::Transaction::THRESHOLD", 1000)
end
+ def allow_high_graphql_query_size
+ stub_const('GraphqlController::MAX_QUERY_SIZE', 10_000_000)
+ end
+
def node_array(data, extract_attribute = nil)
data.map do |item|
extract_attribute ? item['node'][extract_attribute] : item['node']
diff --git a/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
index 759b22f794e..eafa589a1d3 100644
--- a/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
+++ b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
@@ -71,5 +71,38 @@ RSpec.shared_examples 'a valid diff positionable note' do |factory_on_commit|
end
end
end
+
+ describe 'schema validation' do
+ where(:position_attrs) do
+ [
+ { old_path: SecureRandom.alphanumeric(1001) },
+ { new_path: SecureRandom.alphanumeric(1001) },
+ { old_line: "foo" }, # this should be an integer
+ { new_line: "foo" }, # this should be an integer
+ { line_range: { "foo": "bar" } },
+ { line_range: { "line_code": SecureRandom.alphanumeric(101) } },
+ { line_range: { "type": SecureRandom.alphanumeric(101) } },
+ { line_range: { "old_line": "foo" } },
+ { line_range: { "new_line": "foo" } }
+ ]
+ end
+
+ with_them do
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ {
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ line_range: nil,
+ diff_refs: diff_refs
+ }.merge(position_attrs)
+ )
+ end
+
+ it { is_expected.to be_invalid }
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb
index 518c5b8dc28..7f2c445e93d 100644
--- a/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb
@@ -29,10 +29,14 @@ RSpec.shared_examples 'diff discussions API' do |parent_type, noteable_type, id_
describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "creates a new diff note" do
line_range = {
- "start_line_code" => Gitlab::Git.diff_line_code(diff_note.position.file_path, 1, 1),
- "end_line_code" => Gitlab::Git.diff_line_code(diff_note.position.file_path, 2, 2),
- "start_line_type" => diff_note.position.type,
- "end_line_type" => diff_note.position.type
+ "start" => {
+ "line_code" => Gitlab::Git.diff_line_code(diff_note.position.file_path, 1, 1),
+ "type" => diff_note.position.type
+ },
+ "end" => {
+ "line_code" => Gitlab::Git.diff_line_code(diff_note.position.file_path, 2, 2),
+ "type" => diff_note.position.type
+ }
}
position = diff_note.position.to_h.merge({ line_range: line_range })
diff --git a/spec/support/shared_examples/requests/api/merge_requests_shared_examples.rb b/spec/support/shared_examples/requests/api/merge_requests_shared_examples.rb
index e6f9e5a434c..28813a23fed 100644
--- a/spec/support/shared_examples/requests/api/merge_requests_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/merge_requests_shared_examples.rb
@@ -14,10 +14,10 @@ RSpec.shared_examples 'rejects user from accessing merge request info' do
project.add_guest(user)
end
- it 'returns a 404 error' do
+ it 'returns a 403 error' do
get api(url, user)
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('404 Merge Request Not Found')
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
diff --git a/spec/validators/json_schema_validator_spec.rb b/spec/validators/json_schema_validator_spec.rb
index 83eb0e2f3dd..01caf4ab0bd 100644
--- a/spec/validators/json_schema_validator_spec.rb
+++ b/spec/validators/json_schema_validator_spec.rb
@@ -46,5 +46,17 @@ RSpec.describe JsonSchemaValidator do
expect { subject }.to raise_error(described_class::FilenameError)
end
end
+
+ describe 'hash_conversion option' do
+ context 'when hash_conversion is enabled' do
+ let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data", hash_conversion: true) }
+
+ it 'returns no errors' do
+ subject
+
+ expect(build_report_result.errors).to be_empty
+ end
+ end
+ end
end
end
diff --git a/yarn.lock b/yarn.lock
index b7f16a7282f..4e493812ead 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4918,12 +4918,7 @@ domhandler@^4.0.0, domhandler@^4.2.0:
dependencies:
domelementtype "^2.2.0"
-dompurify@2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.1.tgz#a47059ca21fd1212d3c8f71fdea6943b8bfbdf6a"
- integrity sha512-xGWt+NHAQS+4tpgbOAI08yxW0Pr256Gu/FNE2frZVTbgrBUn8M7tz7/ktS/LZ2MHeGqz6topj0/xY+y8R5FBFw==
-
-dompurify@^2.3.3:
+dompurify@2.3.3, dompurify@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c"
integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg==
@@ -8445,16 +8440,16 @@ merge2@^1.3.0:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
-mermaid@^8.13.2:
- version "8.13.2"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.13.2.tgz#9f8abc66ba1c53b132fdaa0d4a80f4717b7b7655"
- integrity sha512-qTFI7MfC2d+x0Hft5gx063EH9tZg36lERG8o7Zq0Ag+MnO8CgVaMZEU6oA8gzTtTn9upMdy4UlYSLVmavu27cQ==
+mermaid@^8.13.4:
+ version "8.13.4"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.13.4.tgz#924cb85f39380285e0a99f245c66cfa61014a2e1"
+ integrity sha512-zdWtsXabVy1PEAE25Jkm4zbTDlQe8rqNlTMq2B3j+D+NxDskJEY5OsgalarvNLsw+b5xFa1a8D1xcm/PijrDow==
dependencies:
"@braintree/sanitize-url" "^3.1.0"
d3 "^7.0.0"
dagre "^0.8.5"
dagre-d3 "^0.6.4"
- dompurify "2.3.1"
+ dompurify "2.3.3"
graphlib "^2.1.8"
khroma "^1.4.1"
moment-mini "^2.24.0"