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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorick@yorickpeterse.com>2019-10-30 17:22:45 +0300
committerYorick Peterse <yorick@yorickpeterse.com>2019-10-30 17:22:45 +0300
commitad8eea383406037a207c80421e6e4bfa357f8044 (patch)
tree396b89ad72b9d7e35fab26c6ee22c978a12defbb
parent228d752ff09362002cc904d28edee7d63cc3cef2 (diff)
parentb0f939a79fe16ff760d6e589c8f9cd71c0fa1da7 (diff)
Merge dev.gitlab.org@master into GitLab.com@master
-rw-r--r--CHANGELOG.md18
-rw-r--r--app/assets/javascripts/project_find_file.js3
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/concerns/internal_redirect.rb2
-rw-r--r--app/controllers/concerns/lfs_request.rb1
-rw-r--r--app/finders/labels_finder.rb8
-rw-r--r--app/graphql/gitlab_schema.rb10
-rw-r--r--app/helpers/markup_helper.rb10
-rw-r--r--app/models/concerns/mentionable/reference_regexes.rb4
-rw-r--r--app/models/discussion.rb1
-rw-r--r--app/models/member.rb1
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/project.rb12
-rw-r--r--app/models/system_note_metadata.rb1
-rw-r--r--app/models/wiki_page.rb6
-rw-r--r--app/policies/commit_policy.rb1
-rw-r--r--app/policies/group_policy.rb2
-rw-r--r--app/policies/namespace_policy.rb2
-rw-r--r--app/policies/note_policy.rb2
-rw-r--r--app/services/auto_merge/base_service.rb7
-rw-r--r--app/services/concerns/merge_requests/assigns_merge_params.rb24
-rw-r--r--app/services/error_tracking/list_projects_service.rb9
-rw-r--r--app/services/merge_requests/base_service.rb14
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/merge_requests/create_service.rb1
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/services/projects/operations/update_service.rb6
-rw-r--r--app/services/projects/participants_service.rb57
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml2
-rw-r--r--changelogs/unreleased/29986-remove-leaky-401-responses.yml5
-rw-r--r--changelogs/unreleased/security-2914-labels-visible-despite-no-access-to-issues-repositories.yml5
-rw-r--r--changelogs/unreleased/security-2920-fix-notes-with-label-cross-reference.yml5
-rw-r--r--changelogs/unreleased/security-64519-nested-graphql-query-can-cause-denial-of-service.yml5
-rw-r--r--changelogs/unreleased/security-65756-ex-admin-attacker-can-comment-in-internal.yml5
-rw-r--r--changelogs/unreleased/security-bvl-validate-force-remove-branch-on-mrs.yml6
-rw-r--r--changelogs/unreleased/security-developer-transfer-project.yml5
-rw-r--r--changelogs/unreleased/security-hide-private-members-in-project-member-autocomplete.yml3
-rw-r--r--changelogs/unreleased/security-id-fix-disclosure-of-private-repo-names.yml5
-rw-r--r--changelogs/unreleased/security-mask-sentry-token-ce.yml4
-rw-r--r--changelogs/unreleased/security-open-redirect-internalredirect.yml5
-rw-r--r--changelogs/unreleased/security-stored-xss-using-find-file.yml5
-rw-r--r--changelogs/unreleased/security-wiki-rdoc-content.yml5
-rw-r--r--doc/user/project/members/share_project_with_groups.md4
-rw-r--r--lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb62
-rw-r--r--lib/gitlab/other_markup.rb2
-rw-r--r--lib/gitlab/search_results.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb2
-rw-r--r--spec/controllers/concerns/internal_redirect_spec.rb3
-rw-r--r--spec/controllers/concerns/lfs_request_spec.rb43
-rw-r--r--spec/controllers/projects/autocomplete_sources_controller_spec.rb35
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb4
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb4
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb5
-rw-r--r--spec/features/projects/tags/user_views_tags_spec.rb2
-rw-r--r--spec/finders/labels_finder_spec.rb83
-rw-r--r--spec/fixtures/api/graphql/recursive-introspection.graphql57
-rw-r--r--spec/fixtures/api/graphql/recursive-query.graphql47
-rw-r--r--spec/fixtures/api/graphql/small-recursive-introspection.graphql15
-rw-r--r--spec/frontend/project_find_file_spec.js37
-rw-r--r--spec/helpers/markup_helper_spec.rb78
-rw-r--r--spec/models/milestone_spec.rb8
-rw-r--r--spec/models/note_spec.rb77
-rw-r--r--spec/models/project_spec.rb12
-rw-r--r--spec/models/wiki_page_spec.rb17
-rw-r--r--spec/policies/commit_policy_spec.rb48
-rw-r--r--spec/policies/group_policy_spec.rb82
-rw-r--r--spec/policies/namespace_policy_spec.rb3
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb48
-rw-r--r--spec/requests/api/merge_requests_spec.rb32
-rw-r--r--spec/requests/api/projects_spec.rb16
-rw-r--r--spec/services/auto_merge/base_service_spec.rb3
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb7
-rw-r--r--spec/services/concerns/merge_requests/assigns_merge_params_spec.rb70
-rw-r--r--spec/services/error_tracking/list_projects_service_spec.rb13
-rw-r--r--spec/services/merge_requests/build_service_spec.rb3
-rw-r--r--spec/services/merge_requests/update_service_spec.rb24
-rw-r--r--spec/services/projects/operations/update_service_spec.rb21
-rw-r--r--spec/services/projects/participants_service_spec.rb104
-rw-r--r--spec/services/projects/transfer_service_spec.rb18
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb22
-rw-r--r--spec/support/helpers/graphql_helpers.rb22
-rw-r--r--spec/support/shared_examples/controllers/todos_shared_examples.rb2
89 files changed, 1285 insertions, 157 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9411180abff..16a36724b4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 12.4.1
+
+### Security (12 changes)
+
+- Standardize error response when route is missing.
+- Do not display project labels that are not visible for user accessing group labels.
+- Show cross-referenced label and milestones in issues' activities only to authorized users.
+- Analyze incoming GraphQL queries and check for recursion.
+- Disallow unprivileged users from commenting on private repository commits.
+- Don't allow maintainers of a target project to delete the source branch of a merge request from a fork.
+- Require Maintainer permission on group where project is transferred to.
+- Don't leak private members in project member autocomplete suggestions.
+- Return 404 on LFS request if project doesn't exist.
+- Mask sentry auth token in Error Tracking dashboard.
+- Fixes a Open Redirect issue in `InternalRedirect`.
+- Sanitize all wiki markup formats with GitLab sanitization pipelines.
+
+
## 12.4.0
### Security (14 changes)
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 2c375b39c1f..58f088444d0 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -5,6 +5,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
+import sanitize from 'sanitize-html';
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
const highlighter = function(element, text, matches) {
@@ -74,7 +75,7 @@ export default class ProjectFindFile {
findFile() {
var result, searchText;
- searchText = this.inputElement.val();
+ searchText = sanitize(this.inputElement.val());
result =
searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
return this.renderList(result, searchText);
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c600b45c8b7..af2869ddba7 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -17,7 +17,7 @@ class ApplicationController < ActionController::Base
include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern
- before_action :authenticate_user!
+ before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
before_action :check_password_expiration
@@ -98,7 +98,9 @@ class ApplicationController < ActionController::Base
if current_user
not_found
else
- authenticate_user!
+ store_location_for(:user, request.fullpath) unless request.xhr?
+
+ redirect_to new_user_session_path, alert: I18n.t('devise.failure.unauthenticated')
end
end
diff --git a/app/controllers/concerns/internal_redirect.rb b/app/controllers/concerns/internal_redirect.rb
index 99bbfd56516..a35bc19aa37 100644
--- a/app/controllers/concerns/internal_redirect.rb
+++ b/app/controllers/concerns/internal_redirect.rb
@@ -6,7 +6,7 @@ module InternalRedirect
def safe_redirect_path(path)
return unless path
# Verify that the string starts with a `/` and a known route character.
- return unless path =~ %r{^/[-\w].*$}
+ return unless path =~ %r{\A/[-\w].*\z}
uri = URI(path)
# Ignore anything path of the redirect except for the path, querystring and,
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 733265f4099..417bb169f39 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -34,6 +34,7 @@ module LfsRequest
end
def lfs_check_access!
+ return render_lfs_not_found unless project
return if download_request? && lfs_download_access?
return if upload_request? && lfs_upload_access?
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index e523942ea4c..027cdc4fc78 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -51,7 +51,7 @@ class LabelsFinder < UnionFinder
end
label_ids << Label.where(group_id: projects.group_ids)
- label_ids << Label.where(project_id: projects.select(:id)) unless only_group_labels?
+ label_ids << Label.where(project_id: ids_user_can_read_labels(projects)) unless only_group_labels?
end
label_ids
@@ -188,4 +188,10 @@ class LabelsFinder < UnionFinder
groups.select { |group| authorized_to_read_labels?(group) }
end
end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def ids_user_can_read_labels(projects)
+ Project.where(id: projects.select(:id)).ids_with_issuables_available_for(current_user)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 4c8612c8f2e..1899278ff3c 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -18,15 +18,15 @@ class GitlabSchema < GraphQL::Schema
use Gitlab::Graphql::GenericTracing
query_analyzer Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer.new
-
- query(Types::QueryType)
-
- default_max_page_size 100
+ query_analyzer Gitlab::Graphql::QueryAnalyzers::RecursionAnalyzer.new
max_complexity DEFAULT_MAX_COMPLEXITY
max_depth DEFAULT_MAX_DEPTH
- mutation(Types::MutationType)
+ query Types::QueryType
+ mutation Types::MutationType
+
+ default_max_page_size 100
class << self
def multiplex(queries, **kwargs)
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index d76a0f3a3b8..e2524938e10 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -133,15 +133,7 @@ module MarkupHelper
issuable_state_filter_enabled: true
)
- html =
- case wiki_page.format
- when :markdown
- markdown_unsafe(text, context)
- when :asciidoc
- asciidoc_unsafe(text)
- else
- wiki_page.formatted_content.html_safe
- end
+ html = markup_unsafe(wiki_page.path, text, context)
prepare_for_rendering(html, context)
end
diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb
index fec31cd262b..f44a674b3c9 100644
--- a/app/models/concerns/mentionable/reference_regexes.rb
+++ b/app/models/concerns/mentionable/reference_regexes.rb
@@ -13,7 +13,9 @@ module Mentionable
def self.other_patterns
[
Commit.reference_pattern,
- MergeRequest.reference_pattern
+ MergeRequest.reference_pattern,
+ Label.reference_pattern,
+ Milestone.reference_pattern
]
end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 0d066d0d99f..b8525f7b135 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -16,6 +16,7 @@ class Discussion
:commit_id,
:for_commit?,
:for_merge_request?,
+ :noteable_ability_name,
:to_ability_name,
:editable?,
:visible_for?,
diff --git a/app/models/member.rb b/app/models/member.rb
index e2d26773d45..2654453cf3f 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -8,6 +8,7 @@ class Member < ApplicationRecord
include Gitlab::Access
include Presentable
include Gitlab::Utils::StrongMemoize
+ include FromUnion
attr_accessor :raw_invite_token
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 250bac95b82..9dc4bd940d9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -69,6 +69,14 @@ class MergeRequest < ApplicationRecord
has_many :merge_request_assignees
has_many :assignees, class_name: "User", through: :merge_request_assignees
+ KNOWN_MERGE_PARAMS = [
+ :auto_merge_strategy,
+ :should_remove_source_branch,
+ :force_remove_source_branch,
+ :commit_message,
+ :squash_commit_message,
+ :sha
+ ].freeze
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
after_create :ensure_merge_request_diff
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 2fa0cfc9b93..a9f4cdec901 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -261,6 +261,10 @@ class Milestone < ApplicationRecord
group || project
end
+ def to_ability_name
+ model_name.singular
+ end
+
def group_milestone?
group_id.present?
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 43f349c6fa2..ce60413b8a0 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -361,6 +361,10 @@ class Note < ApplicationRecord
end
def to_ability_name
+ model_name.singular
+ end
+
+ def noteable_ability_name
for_snippet? ? noteable.class.name.underscore : noteable_type.demodulize.underscore
end
diff --git a/app/models/project.rb b/app/models/project.rb
index b5046bbb6ee..16317b5827a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -614,11 +614,11 @@ class Project < ApplicationRecord
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
- # Returns ids of projects with milestones available for given user
+ # Returns ids of projects with issuables available for given user
#
- # Used on queries to find milestones which user can see
- # For example: Milestone.where(project_id: ids_with_milestone_available_for(user))
- def ids_with_milestone_available_for(user)
+ # Used on queries to find milestones or labels which user can see
+ # For example: Milestone.where(project_id: ids_with_issuables_available_for(user))
+ def ids_with_issuables_available_for(user)
with_issues_enabled = with_issues_available_for_user(user).select(:id)
with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id)
@@ -1265,6 +1265,10 @@ class Project < ApplicationRecord
end
end
+ def to_ability_name
+ model_name.singular
+ end
+
# rubocop: disable CodeReuse/ServiceClass
def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 11cbeb60bba..5a44ee7211b 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -10,6 +10,7 @@ class SystemNoteMetadata < ApplicationRecord
commit cross_reference
close duplicate
moved merge
+ label milestone
].freeze
ICON_TYPES = %w[
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 6b3cb0b39d8..3006753e9e7 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -121,6 +121,12 @@ class WikiPage
@version ||= @page.version
end
+ def path
+ return unless persisted?
+
+ @path ||= @page.path
+ end
+
def versions(options = {})
return [] unless persisted?
diff --git a/app/policies/commit_policy.rb b/app/policies/commit_policy.rb
index 4d4f0ba9267..4b358c45ec2 100644
--- a/app/policies/commit_policy.rb
+++ b/app/policies/commit_policy.rb
@@ -4,4 +4,5 @@ class CommitPolicy < BasePolicy
delegate { @subject.project }
rule { can?(:download_code) }.enable :read_commit
+ rule { ~can?(:read_commit) }.prevent :create_note
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 9e8ee3acf00..13e5b4ae41a 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -131,6 +131,8 @@ class GroupPolicy < BasePolicy
rule { owner | admin }.enable :read_statistics
+ rule { maintainer & can?(:create_projects) }.enable :transfer_projects
+
def access_level
return GroupMember::NO_ACCESS if @user.nil?
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
index fd3bdddded6..350dd208499 100644
--- a/app/policies/namespace_policy.rb
+++ b/app/policies/namespace_policy.rb
@@ -15,6 +15,8 @@ class NamespacePolicy < BasePolicy
end
rule { personal_project & ~can_create_personal_project }.prevent :create_projects
+
+ rule { (owner | admin) & can?(:create_projects) }.enable :transfer_projects
end
NamespacePolicy.prepend_if_ee('EE::NamespacePolicy')
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index b2af6c874c7..dcde8cefa0d 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -9,7 +9,7 @@ class NotePolicy < BasePolicy
condition(:editable, scope: :subject) { @subject.editable? }
- condition(:can_read_noteable) { can?(:"read_#{@subject.to_ability_name}") }
+ condition(:can_read_noteable) { can?(:"read_#{@subject.noteable_ability_name}") }
condition(:is_visible) { @subject.visible_for?(@user) }
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
index e06659a39cd..e08b4ac2260 100644
--- a/app/services/auto_merge/base_service.rb
+++ b/app/services/auto_merge/base_service.rb
@@ -3,12 +3,13 @@
module AutoMerge
class BaseService < ::BaseService
include Gitlab::Utils::StrongMemoize
+ include MergeRequests::AssignsMergeParams
def execute(merge_request)
- merge_request.merge_params.merge!(params)
+ assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy))
+
merge_request.auto_merge_enabled = true
merge_request.merge_user = current_user
- merge_request.auto_merge_strategy = strategy
return :failed unless merge_request.save
@@ -21,7 +22,7 @@ module AutoMerge
end
def update(merge_request)
- merge_request.merge_params.merge!(params)
+ assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy))
return :failed unless merge_request.save
diff --git a/app/services/concerns/merge_requests/assigns_merge_params.rb b/app/services/concerns/merge_requests/assigns_merge_params.rb
new file mode 100644
index 00000000000..bd870d9a1e7
--- /dev/null
+++ b/app/services/concerns/merge_requests/assigns_merge_params.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ module AssignsMergeParams
+ def self.included(klass)
+ raise "#{self} can not be included in #{klass} without implementing #current_user" unless klass.method_defined?(:current_user)
+ end
+
+ def assign_allowed_merge_params(merge_request, merge_params)
+ known_merge_params = merge_params.to_h.with_indifferent_access.slice(*MergeRequest::KNOWN_MERGE_PARAMS)
+
+ # Not checking `MergeRequest#can_remove_source_branch` as that includes
+ # other checks that aren't needed here.
+ known_merge_params.delete(:force_remove_source_branch) unless current_user.can?(:push_code, merge_request.source_project)
+
+ merge_request.merge_params.merge!(known_merge_params)
+
+ # Delete the known params now that they're assigned, so we don't try to
+ # assign them through an `#assign_attributes` later.
+ # They could be coming in as strings or symbols
+ merge_params.to_h.with_indifferent_access.except!(*MergeRequest::KNOWN_MERGE_PARAMS)
+ end
+ end
+end
diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb
index 8d08f0cda94..92d4ef85ecf 100644
--- a/app/services/error_tracking/list_projects_service.rb
+++ b/app/services/error_tracking/list_projects_service.rb
@@ -32,7 +32,7 @@ module ErrorTracking
project_slug: 'proj'
)
- setting.token = params[:token]
+ setting.token = token(setting)
setting.enabled = true
end
end
@@ -40,5 +40,12 @@ module ErrorTracking
def can_read?
can?(current_user, :read_sentry_issue, project)
end
+
+ def token(setting)
+ # Use param token if not masked, otherwise use database token
+ return params[:token] unless /\A\*+\z/.match?(params[:token])
+
+ setting.token
+ end
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 7d4227e4a41..aacc3d6831e 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -2,6 +2,8 @@
module MergeRequests
class BaseService < ::IssuableBaseService
+ include MergeRequests::AssignsMergeParams
+
def create_note(merge_request, state = merge_request.state)
SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, state, nil)
end
@@ -29,6 +31,18 @@ module MergeRequests
private
+ def create(merge_request)
+ self.params = assign_allowed_merge_params(merge_request, params)
+
+ super
+ end
+
+ def update(merge_request)
+ self.params = assign_allowed_merge_params(merge_request, params)
+
+ super
+ end
+
def handle_wip_event(merge_request)
if wip_event = params.delete(:wip_event)
# We update the title that is provided in the params or we use the mr title
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 06ee25eac2a..456cc589477 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -24,6 +24,8 @@ module MergeRequests
merge_request.source_project.remove_source_branch_after_merge?
end
+ self.params = assign_allowed_merge_params(merge_request, params)
+
filter_params(merge_request)
# merge_request.assign_attributes(...) below is a Rails
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 1c730232abb..9a37a0330fc 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -9,7 +9,6 @@ module MergeRequests
merge_request.target_project = @project
merge_request.source_project = @source_project
merge_request.source_branch = params[:source_branch]
- merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
create(merge_request)
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index ae678d4c036..7c9abb12b6e 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -16,10 +16,6 @@ module MergeRequests
params.delete(:force_remove_source_branch)
end
- if params.has_key?(:force_remove_source_branch)
- merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
- end
-
handle_wip_event(merge_request)
update_task_event(merge_request) || update(merge_request)
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index b56b2cf14e3..1709474a6c7 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -281,7 +281,7 @@ class NotificationService
end
def send_new_note_notifications(note)
- notify_method = "note_#{note.to_ability_name}_email".to_sym
+ notify_method = "note_#{note.noteable_ability_name}_email".to_sym
recipients = NotificationRecipientService.build_new_note_recipients(note)
recipients.each do |recipient|
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
index 64519501ff4..0ca89664304 100644
--- a/app/services/projects/operations/update_service.rb
+++ b/app/services/projects/operations/update_service.rb
@@ -36,15 +36,17 @@ module Projects
organization_slug: settings.dig(:project, :organization_slug)
)
- {
+ params = {
error_tracking_setting_attributes: {
api_url: api_url,
- token: settings[:token],
enabled: settings[:enabled],
project_name: settings.dig(:project, :name),
organization_name: settings.dig(:project, :organization_name)
}
}
+ params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value
+
+ params
end
def grafana_integration_params
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 7080f388e53..1cd81fe37c7 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -7,16 +7,69 @@ module Projects
def execute(noteable)
@noteable = noteable
- participants = noteable_owner + participants_in_noteable + all_members + groups + project_members
+ participants =
+ noteable_owner +
+ participants_in_noteable +
+ all_members +
+ groups +
+ project_members
+
participants.uniq
end
def project_members
- @project_members ||= sorted(project.team.members)
+ @project_members ||= sorted(get_project_members)
+ end
+
+ def get_project_members
+ members = Member.from_union([project_members_through_ancestral_groups,
+ project_members_through_invited_groups,
+ individual_project_members])
+
+ User.id_in(members.select(:user_id))
end
def all_members
[{ username: "all", name: "All Project and Group Members", count: project_members.count }]
end
+
+ private
+
+ def project_members_through_invited_groups
+ groups_with_ancestors_ids = Gitlab::ObjectHierarchy
+ .new(visible_groups)
+ .base_and_ancestors
+ .pluck_primary_key
+
+ GroupMember
+ .active_without_invites_and_requests
+ .with_source_id(groups_with_ancestors_ids)
+ end
+
+ def visible_groups
+ visible_groups = project.invited_groups
+
+ unless project_owner?
+ visible_groups = visible_groups.public_or_visible_to_user(current_user)
+ end
+
+ visible_groups
+ end
+
+ def project_members_through_ancestral_groups
+ project.group.present? ? project.group.members_with_parents : Member.none
+ end
+
+ def individual_project_members
+ project.project_members
+ end
+
+ def project_owner?
+ if project.group.present?
+ project.group.owners.include?(current_user)
+ else
+ project.namespace.owner == current_user
+ end
+ end
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 4b3aca58dd7..525fc18b312 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -98,7 +98,7 @@ module Projects
@new_namespace &&
can?(current_user, :change_namespace, project) &&
@new_namespace.id != project.namespace_id &&
- current_user.can?(:create_projects, @new_namespace)
+ current_user.can?(:transfer_projects, @new_namespace)
end
def update_namespace_and_visibility(to_namespace)
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
index 583fc08f375..589d3037eba 100644
--- a/app/views/projects/settings/operations/_error_tracking.html.haml
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -17,4 +17,4 @@
project: error_tracking_setting_project_json,
api_host: setting.api_host,
enabled: setting.enabled.to_json,
- token: setting.token } }
+ token: setting.token.present? ? '*' * 12 : nil } }
diff --git a/changelogs/unreleased/29986-remove-leaky-401-responses.yml b/changelogs/unreleased/29986-remove-leaky-401-responses.yml
new file mode 100644
index 00000000000..3d60011b63f
--- /dev/null
+++ b/changelogs/unreleased/29986-remove-leaky-401-responses.yml
@@ -0,0 +1,5 @@
+---
+title: Standardize error response when route is missing
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-2914-labels-visible-despite-no-access-to-issues-repositories.yml b/changelogs/unreleased/security-2914-labels-visible-despite-no-access-to-issues-repositories.yml
new file mode 100644
index 00000000000..59af202a3bd
--- /dev/null
+++ b/changelogs/unreleased/security-2914-labels-visible-despite-no-access-to-issues-repositories.yml
@@ -0,0 +1,5 @@
+---
+title: Do not display project labels that are not visible for user accessing group labels
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-2920-fix-notes-with-label-cross-reference.yml b/changelogs/unreleased/security-2920-fix-notes-with-label-cross-reference.yml
new file mode 100644
index 00000000000..b2901411729
--- /dev/null
+++ b/changelogs/unreleased/security-2920-fix-notes-with-label-cross-reference.yml
@@ -0,0 +1,5 @@
+---
+title: Show cross-referenced label and milestones in issues' activities only to authorized users
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-64519-nested-graphql-query-can-cause-denial-of-service.yml b/changelogs/unreleased/security-64519-nested-graphql-query-can-cause-denial-of-service.yml
new file mode 100644
index 00000000000..5ce37b0d032
--- /dev/null
+++ b/changelogs/unreleased/security-64519-nested-graphql-query-can-cause-denial-of-service.yml
@@ -0,0 +1,5 @@
+---
+title: Analyze incoming GraphQL queries and check for recursion
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-65756-ex-admin-attacker-can-comment-in-internal.yml b/changelogs/unreleased/security-65756-ex-admin-attacker-can-comment-in-internal.yml
new file mode 100644
index 00000000000..3d9f480ba11
--- /dev/null
+++ b/changelogs/unreleased/security-65756-ex-admin-attacker-can-comment-in-internal.yml
@@ -0,0 +1,5 @@
+---
+title: Disallow unprivileged users from commenting on private repository commits
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-bvl-validate-force-remove-branch-on-mrs.yml b/changelogs/unreleased/security-bvl-validate-force-remove-branch-on-mrs.yml
new file mode 100644
index 00000000000..50dc9c32c5d
--- /dev/null
+++ b/changelogs/unreleased/security-bvl-validate-force-remove-branch-on-mrs.yml
@@ -0,0 +1,6 @@
+---
+title: Don't allow maintainers of a target project to delete the source branch of
+ a merge request from a fork
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-developer-transfer-project.yml b/changelogs/unreleased/security-developer-transfer-project.yml
new file mode 100644
index 00000000000..fe533fc099a
--- /dev/null
+++ b/changelogs/unreleased/security-developer-transfer-project.yml
@@ -0,0 +1,5 @@
+---
+title: Require Maintainer permission on group where project is transferred to
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-hide-private-members-in-project-member-autocomplete.yml b/changelogs/unreleased/security-hide-private-members-in-project-member-autocomplete.yml
new file mode 100644
index 00000000000..5992e93bda2
--- /dev/null
+++ b/changelogs/unreleased/security-hide-private-members-in-project-member-autocomplete.yml
@@ -0,0 +1,3 @@
+---
+title: "Don't leak private members in project member autocomplete suggestions"
+type: security
diff --git a/changelogs/unreleased/security-id-fix-disclosure-of-private-repo-names.yml b/changelogs/unreleased/security-id-fix-disclosure-of-private-repo-names.yml
new file mode 100644
index 00000000000..dfd7a2d11f9
--- /dev/null
+++ b/changelogs/unreleased/security-id-fix-disclosure-of-private-repo-names.yml
@@ -0,0 +1,5 @@
+---
+title: Return 404 on LFS request if project doesn't exist
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-mask-sentry-token-ce.yml b/changelogs/unreleased/security-mask-sentry-token-ce.yml
new file mode 100644
index 00000000000..e9fe780a488
--- /dev/null
+++ b/changelogs/unreleased/security-mask-sentry-token-ce.yml
@@ -0,0 +1,4 @@
+---
+title: Mask sentry auth token in Error Tracking dashboard
+author:
+type: security
diff --git a/changelogs/unreleased/security-open-redirect-internalredirect.yml b/changelogs/unreleased/security-open-redirect-internalredirect.yml
new file mode 100644
index 00000000000..5ac65a4b355
--- /dev/null
+++ b/changelogs/unreleased/security-open-redirect-internalredirect.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes a Open Redirect issue in `InternalRedirect`.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-stored-xss-using-find-file.yml b/changelogs/unreleased/security-stored-xss-using-find-file.yml
new file mode 100644
index 00000000000..41cd2f9494f
--- /dev/null
+++ b/changelogs/unreleased/security-stored-xss-using-find-file.yml
@@ -0,0 +1,5 @@
+---
+title: Sanitize search text to prevent XSS
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-wiki-rdoc-content.yml b/changelogs/unreleased/security-wiki-rdoc-content.yml
new file mode 100644
index 00000000000..f40f1abcd94
--- /dev/null
+++ b/changelogs/unreleased/security-wiki-rdoc-content.yml
@@ -0,0 +1,5 @@
+---
+title: Sanitize all wiki markup formats with GitLab sanitization pipelines
+merge_request:
+author:
+type: security
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
index 9340d239677..79fb2ea50a0 100644
--- a/doc/user/project/members/share_project_with_groups.md
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -44,6 +44,10 @@ Admins are able to share projects with any group in the system.
In the example above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Maintainer' or 'Owner') will only have 'Developer' access to 'Project Acme'.
+## Sharing public project with private group
+
+When sharing a public project with a private group, owners and maintainers of the project will see the name of the group in the `members` page. Owners will also have the possibility to see members of the private group they don't have access to when mentioning them in the issue or merge request.
+
## Share project with group lock
It is possible to prevent projects in a group from [sharing
diff --git a/lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb b/lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb
new file mode 100644
index 00000000000..ccf9e597307
--- /dev/null
+++ b/lib/gitlab/graphql/query_analyzers/recursion_analyzer.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+# Recursive queries, with relatively low effort, can quickly spiral out of control exponentially
+# and may not be picked up by depth and complexity alone.
+module Gitlab
+ module Graphql
+ module QueryAnalyzers
+ class RecursionAnalyzer
+ IGNORED_FIELDS = %w(node edges ofType).freeze
+ RECURSION_THRESHOLD = 2
+
+ def initial_value(query)
+ {
+ recurring_fields: {}
+ }
+ end
+
+ def call(memo, visit_type, irep_node)
+ return memo if skip_node?(irep_node)
+
+ node_name = irep_node.ast_node.name
+ times_encountered = memo[node_name] || 0
+
+ if visit_type == :enter
+ times_encountered += 1
+ memo[:recurring_fields][node_name] = times_encountered if recursion_too_deep?(node_name, times_encountered)
+ else
+ times_encountered -= 1
+ end
+
+ memo[node_name] = times_encountered
+ memo
+ end
+
+ def final_value(memo)
+ recurring_fields = memo[:recurring_fields]
+ recurring_fields = recurring_fields.select { |k, v| recursion_too_deep?(k, v) }
+ if recurring_fields.any?
+ GraphQL::AnalysisError.new("Recursive query - too many of fields '#{recurring_fields}' detected in single branch of the query")
+ end
+ end
+
+ private
+
+ def recursion_too_deep?(node_name, times_encountered)
+ return if IGNORED_FIELDS.include?(node_name)
+
+ times_encountered > recursion_threshold
+ end
+
+ def skip_node?(irep_node)
+ ast_node = irep_node.ast_node
+ !ast_node.is_a?(GraphQL::Language::Nodes::Field) || ast_node.selections.empty?
+ end
+
+ def recursion_threshold
+ RECURSION_THRESHOLD
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index bc467486eee..0dd6b8a809c 100644
--- a/lib/gitlab/other_markup.rb
+++ b/lib/gitlab/other_markup.rb
@@ -10,7 +10,7 @@ module Gitlab
def self.render(file_name, input, context)
html = GitHub::Markup.render(file_name, input)
.force_encoding(input.encoding)
- context[:pipeline] = :markup
+ context[:pipeline] ||= :markup
html = Banzai.render(html, context)
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 782ac534a7b..d74e64116ca 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -163,7 +163,7 @@ module Gitlab
return Milestone.none if project_ids.nil?
authorized_project_ids_relation =
- Project.where(id: project_ids).ids_with_milestone_available_for(current_user)
+ Project.where(id: project_ids).ids_with_issuables_available_for(current_user)
milestones.where(project_id: authorized_project_ids_relation)
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 94afe741057..bb7f5ec2b28 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -186,7 +186,7 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(404)
end
- it 'redirects to login page via authenticate_user! if not authenticated' do
+ it 'redirects to login page if not authenticated' do
get :index
expect(response).to redirect_to new_user_session_path
diff --git a/spec/controllers/concerns/internal_redirect_spec.rb b/spec/controllers/concerns/internal_redirect_spec.rb
index da68c8c8697..e5e50cfd55e 100644
--- a/spec/controllers/concerns/internal_redirect_spec.rb
+++ b/spec/controllers/concerns/internal_redirect_spec.rb
@@ -19,7 +19,8 @@ describe InternalRedirect do
[
'Hello world',
'//example.com/hello/world',
- 'https://example.com/hello/world'
+ 'https://example.com/hello/world',
+ "not-starting-with-a-slash\n/starting/with/slash"
]
end
diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb
index cb8c0b8f71c..823b9a50434 100644
--- a/spec/controllers/concerns/lfs_request_spec.rb
+++ b/spec/controllers/concerns/lfs_request_spec.rb
@@ -16,13 +16,17 @@ describe LfsRequest do
end
def project
- @project ||= Project.find(params[:id])
+ @project ||= Project.find_by(id: params[:id])
end
def download_request?
true
end
+ def upload_request?
+ false
+ end
+
def ci?
false
end
@@ -49,4 +53,41 @@ describe LfsRequest do
expect(assigns(:storage_project)).to eq(project)
end
end
+
+ context 'user is authenticated without access to lfs' do
+ before do
+ allow(controller).to receive(:authenticate_user)
+ allow(controller).to receive(:authentication_result) do
+ Gitlab::Auth::Result.new
+ end
+ end
+
+ context 'with access to the project' do
+ it 'returns 403' do
+ get :show, params: { id: project.id }
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'without access to the project' do
+ context 'project does not exist' do
+ it 'returns 404' do
+ get :show, params: { id: 'does not exist' }
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404' do
+ get :show, params: { id: project.id }
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/autocomplete_sources_controller_spec.rb b/spec/controllers/projects/autocomplete_sources_controller_spec.rb
index 34765ae3951..fc8fe1ac4f6 100644
--- a/spec/controllers/projects/autocomplete_sources_controller_spec.rb
+++ b/spec/controllers/projects/autocomplete_sources_controller_spec.rb
@@ -8,6 +8,10 @@ describe Projects::AutocompleteSourcesController do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:user) { create(:user) }
+ def members_by_username(username)
+ json_response.find { |member| member['username'] == username }
+ end
+
describe 'GET members' do
before do
group.add_owner(user)
@@ -17,22 +21,21 @@ describe Projects::AutocompleteSourcesController do
it 'returns an array of member object' do
get :members, format: :json, params: { namespace_id: group.path, project_id: project.path, type: issue.class.name, type_id: issue.id }
- all = json_response.find {|member| member["username"] == 'all'}
- the_group = json_response.find {|member| member["username"] == group.full_path}
- the_user = json_response.find {|member| member["username"] == user.username}
-
- expect(all.symbolize_keys).to include(username: 'all',
- name: 'All Project and Group Members',
- count: 1)
-
- expect(the_group.symbolize_keys).to include(type: group.class.name,
- name: group.full_name,
- avatar_url: group.avatar_url,
- count: 1)
-
- expect(the_user.symbolize_keys).to include(type: user.class.name,
- name: user.name,
- avatar_url: user.avatar_url)
+ expect(members_by_username('all').symbolize_keys).to include(
+ username: 'all',
+ name: 'All Project and Group Members',
+ count: 1)
+
+ expect(members_by_username(group.full_path).symbolize_keys).to include(
+ type: group.class.name,
+ name: group.full_name,
+ avatar_url: group.avatar_url,
+ count: 1)
+
+ expect(members_by_username(user.username).symbolize_keys).to include(
+ type: user.class.name,
+ name: user.name,
+ avatar_url: user.avatar_url)
end
end
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 9c4d6fdcb2a..1977e92e42b 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -142,7 +142,7 @@ describe Projects::CommitsController do
context 'token authentication' do
context 'public project' do
- it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, { public: true, ignore_incrementing: true } do
before do
public_project = create(:project, :repository, :public)
@@ -152,7 +152,7 @@ describe Projects::CommitsController do
end
context 'private project' do
- it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, { public: false, ignore_incrementing: true } do
before do
private_project = create(:project, :repository, :private)
private_project.add_maintainer(user)
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index 4c224e960a6..31868f5f717 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do
it 'redirects to sign-in page' do
post :list_projects, params: list_projects_params
- expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(response).to have_gitlab_http_status(:redirect)
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 4c2b58551bf..8770a5ee303 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1441,7 +1441,7 @@ describe Projects::IssuesController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
- it_behaves_like 'authenticates sessionless user', :index, :atom do
+ it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
@@ -1449,7 +1449,7 @@ describe Projects::IssuesController do
end
end
- it_behaves_like 'authenticates sessionless user', :calendar, :ics do
+ it_behaves_like 'authenticates sessionless user', :calendar, :ics, ignore_incrementing: true do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index b99b5d611fc..f077b4c99fc 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -41,7 +41,7 @@ describe Projects::TagsController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :repository, :private) }
- it_behaves_like 'authenticates sessionless user', :index, :atom do
+ it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 321f5ecdbc9..22538565698 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1149,7 +1149,7 @@ describe ProjectsController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
- it_behaves_like 'authenticates sessionless user', :show, :atom do
+ it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do
before do
default_params.merge!(id: private_project, namespace_id: private_project.namespace)
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index a9a127da56f..f6eeb8d7065 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -819,7 +819,10 @@ describe 'Pipelines', :js do
context 'when project is private' do
let(:project) { create(:project, :private, :repository) }
- it { expect(page).to have_content 'You need to sign in' }
+ it 'redirects the user to sign_in and displays the flash alert' do
+ expect(page).to have_content 'You need to sign in'
+ expect(page.current_path).to eq("/users/sign_in")
+ end
end
end
diff --git a/spec/features/projects/tags/user_views_tags_spec.rb b/spec/features/projects/tags/user_views_tags_spec.rb
index f344b682715..bc570f502bf 100644
--- a/spec/features/projects/tags/user_views_tags_spec.rb
+++ b/spec/features/projects/tags/user_views_tags_spec.rb
@@ -15,7 +15,7 @@ describe 'User views tags', :feature do
it do
visit project_tags_path(project, format: :atom)
- expect(page).to have_gitlab_http_status(401)
+ expect(page.current_path).to eq("/users/sign_in")
end
end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 2681f098fec..7590c399cf9 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -128,6 +128,89 @@ describe LabelsFinder do
expect(finder.execute).to eq [private_subgroup_label_1]
end
end
+
+ context 'when including labels from group projects with limited visibility' do
+ let(:finder) { described_class.new(user, group_id: group_4.id) }
+ let(:group_4) { create(:group) }
+ let(:limited_visibility_project) { create(:project, :public, group: group_4) }
+ let(:visible_project) { create(:project, :public, group: group_4) }
+ let!(:group_label_1) { create(:group_label, group: group_4) }
+ let!(:limited_visibility_label) { create(:label, project: limited_visibility_project) }
+ let!(:visible_label) { create(:label, project: visible_project) }
+
+ shared_examples 'with full visibility' do
+ it 'returns all projects labels' do
+ expect(finder.execute).to eq [group_label_1, limited_visibility_label, visible_label]
+ end
+ end
+
+ shared_examples 'with limited visibility' do
+ it 'returns only authorized projects labels' do
+ expect(finder.execute).to eq [group_label_1, visible_label]
+ end
+ end
+
+ context 'when merge requests and issues are not visible for non members' do
+ before do
+ limited_visibility_project.project_feature.update!(
+ merge_requests_access_level: ProjectFeature::PRIVATE,
+ issues_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ context 'when user is not a group member' do
+ it_behaves_like 'with limited visibility'
+ end
+
+ context 'when user is a group member' do
+ before do
+ group_4.add_developer(user)
+ end
+
+ it_behaves_like 'with full visibility'
+ end
+ end
+
+ context 'when merge requests are not visible for non members' do
+ before do
+ limited_visibility_project.project_feature.update!(
+ merge_requests_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ context 'when user is not a group member' do
+ it_behaves_like 'with full visibility'
+ end
+
+ context 'when user is a group member' do
+ before do
+ group_4.add_developer(user)
+ end
+
+ it_behaves_like 'with full visibility'
+ end
+ end
+
+ context 'when issues are not visible for non members' do
+ before do
+ limited_visibility_project.project_feature.update!(
+ issues_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ context 'when user is not a group member' do
+ it_behaves_like 'with full visibility'
+ end
+
+ context 'when user is a group member' do
+ before do
+ group_4.add_developer(user)
+ end
+
+ it_behaves_like 'with full visibility'
+ end
+ end
+ end
end
context 'filtering by project_id' do
diff --git a/spec/fixtures/api/graphql/recursive-introspection.graphql b/spec/fixtures/api/graphql/recursive-introspection.graphql
new file mode 100644
index 00000000000..db970bb14b6
--- /dev/null
+++ b/spec/fixtures/api/graphql/recursive-introspection.graphql
@@ -0,0 +1,57 @@
+query allSchemaTypes {
+ __schema {
+ types {
+ fields {
+ type{
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ type {
+ fields {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/spec/fixtures/api/graphql/recursive-query.graphql b/spec/fixtures/api/graphql/recursive-query.graphql
new file mode 100644
index 00000000000..d1616c4de6e
--- /dev/null
+++ b/spec/fixtures/api/graphql/recursive-query.graphql
@@ -0,0 +1,47 @@
+{
+ project(fullPath: "gitlab-org/gitlab-ce") {
+ group {
+ projects {
+ edges {
+ node {
+ group {
+ projects {
+ edges {
+ node {
+ group {
+ projects {
+ edges {
+ node {
+ group {
+ projects {
+ edges {
+ node {
+ group {
+ projects {
+ edges {
+ node {
+ group {
+ description
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/graphql/small-recursive-introspection.graphql b/spec/fixtures/api/graphql/small-recursive-introspection.graphql
new file mode 100644
index 00000000000..1025043b474
--- /dev/null
+++ b/spec/fixtures/api/graphql/small-recursive-introspection.graphql
@@ -0,0 +1,15 @@
+query allSchemaTypes {
+ __schema {
+ types {
+ fields {
+ type {
+ fields {
+ type {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/frontend/project_find_file_spec.js b/spec/frontend/project_find_file_spec.js
index 8102033139f..e60f9f62747 100644
--- a/spec/frontend/project_find_file_spec.js
+++ b/spec/frontend/project_find_file_spec.js
@@ -3,6 +3,9 @@ import $ from 'jquery';
import ProjectFindFile from '~/project_find_file';
import axios from '~/lib/utils/axios_utils';
import { TEST_HOST } from 'helpers/test_constants';
+import sanitize from 'sanitize-html';
+
+jest.mock('sanitize-html', () => jest.fn(val => val));
const BLOB_URL_TEMPLATE = `${TEST_HOST}/namespace/project/blob/master`;
const FILE_FIND_URL = `${TEST_HOST}/namespace/project/files/master?format=json`;
@@ -38,31 +41,31 @@ describe('ProjectFindFile', () => {
href: el.querySelector('a').href,
}));
+ const files = [
+ 'fileA.txt',
+ 'fileB.txt',
+ 'fi#leC.txt',
+ 'folderA/fileD.txt',
+ 'folder#B/fileE.txt',
+ 'folde?rC/fil#F.txt',
+ ];
+
beforeEach(() => {
// Create a mock adapter for stubbing axios API requests
mock = new MockAdapter(axios);
element = $(TEMPLATE);
+ mock.onGet(FILE_FIND_URL).replyOnce(200, files);
+ getProjectFindFileInstance(); // This triggers a load / axios call + subsequent render in the constructor
});
afterEach(() => {
// Reset the mock adapter
mock.restore();
+ sanitize.mockClear();
});
it('loads and renders elements from remote server', done => {
- const files = [
- 'fileA.txt',
- 'fileB.txt',
- 'fi#leC.txt',
- 'folderA/fileD.txt',
- 'folder#B/fileE.txt',
- 'folde?rC/fil#F.txt',
- ];
- mock.onGet(FILE_FIND_URL).replyOnce(200, files);
-
- getProjectFindFileInstance(); // This triggers a load / axios call + subsequent render in the constructor
-
setImmediate(() => {
expect(findFiles()).toEqual(
files.map(text => ({
@@ -74,4 +77,14 @@ describe('ProjectFindFile', () => {
done();
});
});
+
+ it('sanitizes search text', done => {
+ const searchText = element.find('.file-finder-input').val();
+
+ setImmediate(() => {
+ expect(sanitize).toHaveBeenCalledTimes(1);
+ expect(sanitize).toHaveBeenCalledWith(searchText);
+ done();
+ });
+ });
});
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 364f215420a..32851249b2e 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -3,18 +3,18 @@
require 'spec_helper'
describe MarkupHelper do
- let!(:project) { create(:project, :repository) }
-
- let(:user) { create(:user, username: 'gfm') }
- let(:commit) { project.commit }
- let(:issue) { create(:issue, project: project) }
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:snippet) { create(:project_snippet, project: project) }
-
- before do
- # Ensure the generated reference links aren't redacted
+ set(:project) { create(:project, :repository) }
+ set(:user) do
+ user = create(:user, username: 'gfm')
project.add_maintainer(user)
+ user
+ end
+ set(:issue) { create(:issue, project: project) }
+ set(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ set(:snippet) { create(:project_snippet, project: project) }
+ let(:commit) { project.commit }
+ before do
# Helper expects a @project instance variable
helper.instance_variable_set(:@project, project)
@@ -44,8 +44,8 @@ describe MarkupHelper do
describe "override default project" do
let(:actual) { issue.to_reference }
- let(:second_project) { create(:project, :public) }
- let(:second_issue) { create(:issue, project: second_project) }
+ set(:second_project) { create(:project, :public) }
+ set(:second_issue) { create(:issue, project: second_project) }
it 'links to the issue' do
expected = urls.project_issue_path(second_project, second_issue)
@@ -55,7 +55,7 @@ describe MarkupHelper do
describe 'uploads' do
let(:text) { "![ImageTest](/uploads/test.png)" }
- let(:group) { create(:group) }
+ set(:group) { create(:group) }
subject { helper.markdown(text) }
@@ -77,7 +77,7 @@ describe MarkupHelper do
end
describe "with a group in the context" do
- let(:project_in_group) { create(:project, group: group) }
+ set(:project_in_group) { create(:project, group: group) }
before do
helper.instance_variable_set(:@group, group)
@@ -235,38 +235,48 @@ describe MarkupHelper do
end
describe '#render_wiki_content' do
+ let(:wiki) { double('WikiPage', path: "file.#{extension}") }
+ let(:context) do
+ {
+ pipeline: :wiki, project: project, project_wiki: wiki,
+ page_slug: 'nested/page', issuable_state_filter_enabled: true
+ }
+ end
+
before do
- @wiki = double('WikiPage')
- allow(@wiki).to receive(:content).and_return('wiki content')
- allow(@wiki).to receive(:slug).and_return('nested/page')
- helper.instance_variable_set(:@project_wiki, @wiki)
+ expect(wiki).to receive(:content).and_return('wiki content')
+ expect(wiki).to receive(:slug).and_return('nested/page')
+ helper.instance_variable_set(:@project_wiki, wiki)
end
- it "uses Wiki pipeline for markdown files" do
- allow(@wiki).to receive(:format).and_return(:markdown)
+ context 'when file is Markdown' do
+ let(:extension) { 'md' }
- expect(helper).to receive(:markdown_unsafe).with('wiki content',
- pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page",
- issuable_state_filter_enabled: true)
+ it 'renders using #markdown_unsafe helper method' do
+ expect(helper).to receive(:markdown_unsafe).with('wiki content', context)
- helper.render_wiki_content(@wiki)
+ helper.render_wiki_content(wiki)
+ end
end
- it "uses Asciidoctor for asciidoc files" do
- allow(@wiki).to receive(:format).and_return(:asciidoc)
+ context 'when file is Asciidoc' do
+ let(:extension) { 'adoc' }
- expect(helper).to receive(:asciidoc_unsafe).with('wiki content')
+ it 'renders using Gitlab::Asciidoc' do
+ expect(Gitlab::Asciidoc).to receive(:render)
- helper.render_wiki_content(@wiki)
+ helper.render_wiki_content(wiki)
+ end
end
- it "uses the Gollum renderer for all other file types" do
- allow(@wiki).to receive(:format).and_return(:rdoc)
- formatted_content_stub = double('formatted_content')
- expect(formatted_content_stub).to receive(:html_safe)
- allow(@wiki).to receive(:formatted_content).and_return(formatted_content_stub)
+ context 'any other format' do
+ let(:extension) { 'foo' }
- helper.render_wiki_content(@wiki)
+ it 'renders all other formats using Gitlab::OtherMarkup' do
+ expect(Gitlab::OtherMarkup).to receive(:render)
+
+ helper.render_wiki_content(wiki)
+ end
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 2ecbe548520..120ba67f328 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -227,6 +227,14 @@ describe Milestone do
end
end
+ describe '#to_ability_name' do
+ it 'returns milestone' do
+ milestone = build(:milestone)
+
+ expect(milestone.to_ability_name).to eq('milestone')
+ end
+ end
+
describe '.search' do
let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 4c320b4b145..3ab88b52568 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -379,6 +379,63 @@ describe Note do
expect(label_note.cross_reference?).to be_falsy
end
end
+
+ context 'when system note metadata is not present' do
+ let(:note) { build(:note, :system) }
+
+ before do
+ allow(note).to receive(:system_note_metadata).and_return(nil)
+ end
+
+ it 'delegates to the system note service' do
+ expect(SystemNotes::IssuablesService).to receive(:cross_reference?).with(note.note)
+
+ note.cross_reference?
+ end
+ end
+
+ context 'with a system note' do
+ let(:issue) { create(:issue, project: create(:project, :repository)) }
+ let(:note) { create(:system_note, note: "test", noteable: issue, project: issue.project) }
+
+ shared_examples 'system_note_metadata includes note action' do
+ it 'delegates to the cross-reference regex' do
+ expect(note).to receive(:matches_cross_reference_regex?)
+
+ note.cross_reference?
+ end
+ end
+
+ context 'with :label action' do
+ let!(:metadata) {create(:system_note_metadata, note: note, action: :label)}
+
+ it_behaves_like 'system_note_metadata includes note action'
+
+ it { expect(note.cross_reference?).to be_falsy }
+
+ context 'with cross reference label note' do
+ let(:label) { create(:label, project: issue.project)}
+ let(:note) { create(:system_note, note: "added #{label.to_reference} label", noteable: issue, project: issue.project) }
+
+ it { expect(note.cross_reference?).to be_truthy }
+ end
+ end
+
+ context 'with :milestone action' do
+ let!(:metadata) {create(:system_note_metadata, note: note, action: :milestone)}
+
+ it_behaves_like 'system_note_metadata includes note action'
+
+ it { expect(note.cross_reference?).to be_falsy }
+
+ context 'with cross reference milestone note' do
+ let(:milestone) { create(:milestone, project: issue.project)}
+ let(:note) { create(:system_note, note: "added #{milestone.to_reference} milestone", noteable: issue, project: issue.project) }
+
+ it { expect(note.cross_reference?).to be_truthy }
+ end
+ end
+ end
end
describe 'clear_blank_line_code!' do
@@ -578,24 +635,30 @@ describe Note do
end
describe '#to_ability_name' do
- it 'returns snippet for a project snippet note' do
- expect(build(:note_on_project_snippet).to_ability_name).to eq('project_snippet')
+ it 'returns note' do
+ expect(build(:note).to_ability_name).to eq('note')
+ end
+ end
+
+ describe '#noteable_ability_name' do
+ it 'returns project_snippet for a project snippet note' do
+ expect(build(:note_on_project_snippet).noteable_ability_name).to eq('project_snippet')
end
it 'returns personal_snippet for a personal snippet note' do
- expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet')
+ expect(build(:note_on_personal_snippet).noteable_ability_name).to eq('personal_snippet')
end
it 'returns merge_request for an MR note' do
- expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request')
+ expect(build(:note_on_merge_request).noteable_ability_name).to eq('merge_request')
end
it 'returns issue for an issue note' do
- expect(build(:note_on_issue).to_ability_name).to eq('issue')
+ expect(build(:note_on_issue).noteable_ability_name).to eq('issue')
end
- it 'returns issue for a commit note' do
- expect(build(:note_on_commit).to_ability_name).to eq('commit')
+ it 'returns commit for a commit note' do
+ expect(build(:note_on_commit).noteable_ability_name).to eq('commit')
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 5922a6f36f5..71b999048b4 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -3416,7 +3416,7 @@ describe Project do
end
end
- describe '.ids_with_milestone_available_for' do
+ describe '.ids_with_issuables_available_for' do
let!(:user) { create(:user) }
it 'returns project ids with milestones available for user' do
@@ -3426,7 +3426,7 @@ describe Project do
project_4 = create(:project, :public)
project_4.project_feature.update(issues_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE )
- project_ids = described_class.ids_with_milestone_available_for(user).pluck(:id)
+ project_ids = described_class.ids_with_issuables_available_for(user).pluck(:id)
expect(project_ids).to include(project_2.id, project_3.id)
expect(project_ids).not_to include(project_1.id, project_4.id)
@@ -4444,6 +4444,14 @@ describe Project do
end
end
+ describe '#to_ability_name' do
+ it 'returns project' do
+ project = build(:project_empty_repo)
+
+ expect(project.to_ability_name).to eq('project')
+ end
+ end
+
describe '#execute_hooks' do
let(:data) { { ref: 'refs/heads/master', data: 'data' } }
it 'executes active projects hooks with the specified scope' do
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 8f3730f97ac..38415613b9e 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -358,6 +358,23 @@ describe WikiPage do
end
end
+ describe '#path' do
+ let(:path) { 'mypath.md' }
+ let(:wiki_page) { instance_double('Gitlab::Git::WikiPage', path: path).as_null_object }
+
+ it 'returns the path when persisted' do
+ page = described_class.new(wiki, wiki_page, true)
+
+ expect(page.path).to eq(path)
+ end
+
+ it 'returns nil when not persisted' do
+ page = described_class.new(wiki, wiki_page, false)
+
+ expect(page.path).to be_nil
+ end
+ end
+
describe '#directory' do
context 'when the page is at the root directory' do
it 'returns an empty string' do
diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb
index 41f6fb08426..40183f51e9e 100644
--- a/spec/policies/commit_policy_spec.rb
+++ b/spec/policies/commit_policy_spec.rb
@@ -8,28 +8,42 @@ describe CommitPolicy do
let(:commit) { project.repository.head_commit }
let(:policy) { described_class.new(user, commit) }
+ shared_examples 'can read commit and create a note' do
+ it 'can read commit' do
+ expect(policy).to be_allowed(:read_commit)
+ end
+
+ it 'can create a note' do
+ expect(policy).to be_allowed(:create_note)
+ end
+ end
+
+ shared_examples 'cannot read commit nor create a note' do
+ it 'can not read commit' do
+ expect(policy).to be_disallowed(:read_commit)
+ end
+
+ it 'can not create a note' do
+ expect(policy).to be_disallowed(:create_note)
+ end
+ end
+
context 'when project is public' do
let(:project) { create(:project, :public, :repository) }
- it 'can read commit and create a note' do
- expect(policy).to be_allowed(:read_commit)
- end
+ it_behaves_like 'can read commit and create a note'
context 'when repository access level is private' do
let(:project) { create(:project, :public, :repository, :repository_private) }
- it 'can not read commit and create a note' do
- expect(policy).to be_disallowed(:read_commit)
- end
+ it_behaves_like 'cannot read commit nor create a note'
context 'when the user is a project member' do
before do
project.add_developer(user)
end
- it 'can read commit and create a note' do
- expect(policy).to be_allowed(:read_commit)
- end
+ it_behaves_like 'can read commit and create a note'
end
end
end
@@ -37,9 +51,7 @@ describe CommitPolicy do
context 'when project is private' do
let(:project) { create(:project, :private, :repository) }
- it 'can not read commit and create a note' do
- expect(policy).to be_disallowed(:read_commit)
- end
+ it_behaves_like 'cannot read commit nor create a note'
context 'when the user is a project member' do
before do
@@ -50,6 +62,18 @@ describe CommitPolicy do
expect(policy).to be_allowed(:read_commit)
end
end
+
+ context 'when the user is a guest' do
+ before do
+ project.add_guest(user)
+ end
+
+ it_behaves_like 'cannot read commit nor create a note'
+
+ it 'cannot download code' do
+ expect(policy).to be_disallowed(:download_code)
+ end
+ end
end
end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 9634a591592..ae9d125f970 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -356,6 +356,88 @@ describe GroupPolicy do
end
end
+ context 'transfer_projects' do
+ shared_examples_for 'allowed to transfer projects' do
+ before do
+ group.update(project_creation_level: project_creation_level)
+ end
+
+ it { is_expected.to be_allowed(:transfer_projects) }
+ end
+
+ shared_examples_for 'not allowed to transfer projects' do
+ before do
+ group.update(project_creation_level: project_creation_level)
+ end
+
+ it { is_expected.to be_disallowed(:transfer_projects) }
+ end
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS }
+ end
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS }
+ end
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS }
+ end
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it_behaves_like 'not allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS }
+ end
+
+ it_behaves_like 'allowed to transfer projects' do
+ let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS }
+ end
+ end
+ end
+
context "create_projects" do
context 'when group has no project creation level set' do
before_all do
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index ece0519112a..c0a5119c550 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -8,7 +8,7 @@ describe NamespacePolicy do
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, owner: owner) }
- let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace, :read_statistics] }
+ let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace, :read_statistics, :transfer_projects] }
subject { described_class.new(current_user, namespace) }
@@ -33,6 +33,7 @@ describe NamespacePolicy do
let(:owner) { create(:user, projects_limit: 0) }
it { is_expected.to be_disallowed(:create_projects) }
+ it { is_expected.to be_disallowed(:transfer_projects) }
end
end
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index d02eccedf32..2aeb75a10b4 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -15,7 +15,7 @@ describe 'GitlabSchema configurations' do
subject
- expect(graphql_errors.flatten.first['message']).to include('which exceeds max complexity of 1')
+ expect_graphql_errors_to_include /which exceeds max complexity of 1/
end
end
end
@@ -23,12 +23,11 @@ describe 'GitlabSchema configurations' do
describe '#max_depth' do
context 'when query depth is too high' do
it 'shows error' do
- errors = { "message" => "Query has depth of 2, which exceeds max depth of 1" }
allow(GitlabSchema).to receive(:max_query_depth).and_return 1
subject
- expect(graphql_errors.flatten).to include(errors)
+ expect_graphql_errors_to_include /exceeds max depth/
end
end
@@ -38,7 +37,42 @@ describe 'GitlabSchema configurations' do
subject
- expect(Array.wrap(graphql_errors).compact).to be_empty
+ expect_graphql_errors_to_be_empty
+ end
+ end
+ end
+ end
+
+ context 'depth, complexity and recursion checking' do
+ context 'unauthenticated recursive queries' do
+ context 'a not-quite-recursive-enough introspective query' do
+ it 'succeeds' do
+ query = File.read(Rails.root.join('spec/fixtures/api/graphql/small-recursive-introspection.graphql'))
+
+ post_graphql(query, current_user: nil)
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ context 'a deep but simple recursive introspective query' do
+ it 'fails due to recursion' do
+ query = File.read(Rails.root.join('spec/fixtures/api/graphql/recursive-introspection.graphql'))
+
+ post_graphql(query, current_user: nil)
+
+ expect_graphql_errors_to_include [/Recursive query/]
+ end
+ end
+
+ context 'a deep recursive non-introspective query' do
+ it 'fails due to recursion, complexity and depth' do
+ allow(GitlabSchema).to receive(:max_query_complexity).and_return 1
+ query = File.read(Rails.root.join('spec/fixtures/api/graphql/recursive-query.graphql'))
+
+ post_graphql(query, current_user: nil)
+
+ expect_graphql_errors_to_include [/Recursive query/, /exceeds max complexity/, /exceeds max depth/]
end
end
end
@@ -88,7 +122,7 @@ describe 'GitlabSchema configurations' do
# Expect errors for each query
expect(graphql_errors.size).to eq(3)
graphql_errors.each do |single_query_errors|
- expect(single_query_errors.first['message']).to include('which exceeds max complexity of 4')
+ expect_graphql_errors_to_include(/which exceeds max complexity of 4/)
end
end
end
@@ -105,12 +139,12 @@ describe 'GitlabSchema configurations' do
end
context 'when IntrospectionQuery' do
- it 'is not too complex' do
+ it 'is not too complex nor recursive' do
query = File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql'))
post_graphql(query, current_user: nil)
- expect(graphql_errors).to be_nil
+ expect_graphql_errors_to_be_empty
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 8063004549e..c622add7f8f 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1741,6 +1741,38 @@ describe API::MergeRequests do
expect(json_response['state']).to eq('closed')
expect(json_response['force_remove_source_branch']).to be_truthy
end
+
+ context 'with a merge request across forks' do
+ let(:fork_owner) { create(:user) }
+ let(:source_project) { fork_project(project, fork_owner) }
+ let(:target_project) { project }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: source_project,
+ target_project: target_project,
+ source_branch: 'fixes',
+ merge_params: { 'force_remove_source_branch' => false })
+ end
+
+ it 'is true for an authorized user' do
+ put api("/projects/#{target_project.id}/merge_requests/#{merge_request.iid}", fork_owner), params: { state_event: 'close', remove_source_branch: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['state']).to eq('closed')
+ expect(json_response['force_remove_source_branch']).to be true
+ end
+
+ it 'is false for an unauthorized user' do
+ expect do
+ put api("/projects/#{target_project.id}/merge_requests/#{merge_request.iid}", target_project.owner), params: { state_event: 'close', remove_source_branch: true }
+ end.not_to change { merge_request.reload.merge_params }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['state']).to eq('closed')
+ expect(json_response['force_remove_source_branch']).to be false
+ end
+ end
end
context "to close a MR" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 585bf57fa4c..ffac7872464 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2684,6 +2684,22 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(400)
end
end
+
+ context 'when authenticated as developer' do
+ before do
+ group.add_developer(user)
+ end
+
+ context 'target namespace allows developers to create projects' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+
+ it 'fails transferring the project to the target namespace' do
+ put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id }
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
end
it_behaves_like 'custom attributes endpoints', 'projects' do
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
index a409f21a7e4..0a6bcb1badc 100644
--- a/spec/services/auto_merge/base_service_spec.rb
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -51,7 +51,7 @@ describe AutoMerge::BaseService do
expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'")
expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18')
expect(merge_request.merge_params['should_remove_source_branch']).to eq(false)
- expect(merge_request.merge_params['squash']).to eq(false)
+ expect(merge_request.squash).to eq(false)
expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md')
end
end
@@ -108,7 +108,6 @@ describe AutoMerge::BaseService do
'commit_message' => "Merge branch 'patch-12' into 'master'",
'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18",
'should_remove_source_branch' => false,
- 'squash' => false,
'squash_commit_message' => "Update README.md"
}
end
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index c396539cf56..1bebeacb4d4 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -59,8 +59,9 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
it 'sets the params, merge_user, and flag' do
expect(merge_request).to be_valid
expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
- expect(merge_request.merge_params).to include commit_message: 'Awesome message'
+ expect(merge_request.merge_params).to include 'commit_message' => 'Awesome message'
expect(merge_request.merge_user).to be user
+ expect(merge_request.auto_merge_strategy).to eq AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
end
it 'creates a system note' do
@@ -73,7 +74,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
end
context 'already approved' do
- let(:service) { described_class.new(project, user, new_key: true) }
+ let(:service) { described_class.new(project, user, should_remove_source_branch: true) }
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
before do
@@ -90,7 +91,7 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds)
service.execute(mr_merge_if_green_enabled)
- expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key)
+ expect(mr_merge_if_green_enabled.merge_params).to have_key('should_remove_source_branch')
end
end
end
diff --git a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
new file mode 100644
index 00000000000..5b653aa331c
--- /dev/null
+++ b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe MergeRequests::AssignsMergeParams do
+ it 'raises an error when used from an instance that does not respond to #current_user' do
+ define_class = -> { Class.new { include MergeRequests::AssignsMergeParams }.new }
+
+ expect { define_class.call }.to raise_error %r{can not be included in (.*) without implementing #current_user}
+ end
+
+ describe '#assign_allowed_merge_params' do
+ let(:merge_request) { build(:merge_request) }
+
+ let(:params) do
+ { commit_message: 'Commit Message',
+ 'should_remove_source_branch' => true,
+ unknown_symbol: 'Unknown symbol',
+ 'unknown_string' => 'Unknown String' }
+ end
+
+ subject(:merge_request_service) do
+ Class.new do
+ attr_accessor :current_user
+
+ include MergeRequests::AssignsMergeParams
+
+ def initialize(user)
+ @current_user = user
+ end
+ end
+ end
+
+ it 'only assigns known parameters to the merge request' do
+ service = merge_request_service.new(merge_request.author)
+
+ service.assign_allowed_merge_params(merge_request, params)
+
+ expect(merge_request.merge_params).to eq('commit_message' => 'Commit Message', 'should_remove_source_branch' => true)
+ end
+
+ it 'returns a hash without the known merge params' do
+ service = merge_request_service.new(merge_request.author)
+
+ result = service.assign_allowed_merge_params(merge_request, params)
+
+ expect(result).to eq({ 'unknown_symbol' => 'Unknown symbol', 'unknown_string' => 'Unknown String' })
+ end
+
+ context 'the force_remove_source_branch param' do
+ let(:params) { { force_remove_source_branch: true } }
+
+ it 'assigns the param if the user is allowed to do that' do
+ service = merge_request_service.new(merge_request.author)
+
+ result = service.assign_allowed_merge_params(merge_request, params)
+
+ expect(merge_request.force_remove_source_branch?).to be true
+ expect(result).to be_empty
+ end
+
+ it 'only removes the param if the user is not allowed to do that' do
+ service = merge_request_service.new(build(:user))
+
+ result = service.assign_allowed_merge_params(merge_request, params)
+
+ expect(merge_request.force_remove_source_branch?).to be_falsy
+ expect(result).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb
index 730fccc599e..a272a604184 100644
--- a/spec/services/error_tracking/list_projects_service_spec.rb
+++ b/spec/services/error_tracking/list_projects_service_spec.rb
@@ -50,6 +50,19 @@ describe ErrorTracking::ListProjectsService do
end
end
+ context 'masked param token' do
+ let(:params) { ActionController::Parameters.new(token: "*********", api_host: new_api_host) }
+
+ before do
+ expect(error_tracking_setting).to receive(:list_sentry_projects)
+ .and_return({ projects: [] })
+ end
+
+ it 'uses database token' do
+ expect { subject.execute }.not_to change { error_tracking_setting.token }
+ end
+ end
+
context 'sentry client raises exception' do
context 'Sentry::Client::Error' do
before do
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 46e86d5b4cb..9b358839c06 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -83,8 +83,9 @@ describe MergeRequests::BuildService do
expect(merge_request.force_remove_source_branch?).to be_truthy
end
- context 'with force_remove_source_branch parameter' do
+ context 'with force_remove_source_branch parameter when the user is authorized' do
let(:mr_params) { params.merge(force_remove_source_branch: '1') }
+ let(:source_project) { fork_project(project, user) }
let(:merge_request) { described_class.new(project, user, mr_params).execute }
it 'assigns force_remove_source_branch' do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 8c796475de0..741420d76a7 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -646,5 +646,29 @@ describe MergeRequests::UpdateService, :mailer do
expect(merge_request.allow_collaboration).to be_truthy
end
end
+
+ context 'updating `force_remove_source_branch`' do
+ let(:target_project) { create(:project, :repository, :public) }
+ let(:source_project) { fork_project(target_project, nil, repository: true) }
+ let(:user) { target_project.owner }
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: source_project,
+ source_branch: 'fixes',
+ target_project: target_project)
+ end
+
+ it "cannot be done by members of the target project when they don't have access" do
+ expect { update_merge_request(force_remove_source_branch: true) }
+ .not_to change { merge_request.reload.force_remove_source_branch? }.from(nil)
+ end
+
+ it 'can be done by members of the target project if they can push to the source project' do
+ source_project.add_developer(user)
+
+ expect { update_merge_request(force_remove_source_branch: true) }
+ .to change { merge_request.reload.force_remove_source_branch? }.from(nil).to(true)
+ end
+ end
end
end
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index b2f9fd6df79..81d59a98b9b 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -145,6 +145,27 @@ describe Projects::Operations::UpdateService do
end
end
+ context 'with masked param token' do
+ let(:params) do
+ {
+ error_tracking_setting_attributes: {
+ enabled: false,
+ token: '*' * 8
+ }
+ }
+ end
+
+ before do
+ create(:project_error_tracking_setting, project: project, token: 'token')
+ end
+
+ it 'does not update token' do
+ expect(result[:status]).to eq(:success)
+
+ expect(project.error_tracking_setting.token).to eq('token')
+ end
+ end
+
context 'with invalid parameters' do
let(:params) { {} }
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index 4def83513a4..239d28557ee 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -57,4 +57,108 @@ describe Projects::ParticipantsService do
end
end
end
+
+ describe '#project_members' do
+ subject(:usernames) { service.project_members.map { |member| member[:username] } }
+
+ context 'when there is a project in group namespace' do
+ set(:public_group) { create(:group, :public) }
+ set(:public_project) { create(:project, :public, namespace: public_group)}
+
+ set(:public_group_owner) { create(:user) }
+
+ let(:service) { described_class.new(public_project, create(:user)) }
+
+ before do
+ public_group.add_owner(public_group_owner)
+ end
+
+ it 'returns members of a group' do
+ expect(usernames).to include(public_group_owner.username)
+ end
+ end
+
+ context 'when there is a private group and a public project' do
+ set(:public_group) { create(:group, :public) }
+ set(:private_group) { create(:group, :private, :nested) }
+ set(:public_project) { create(:project, :public, namespace: public_group)}
+
+ set(:project_issue) { create(:issue, project: public_project)}
+
+ set(:public_group_owner) { create(:user) }
+ set(:private_group_member) { create(:user) }
+ set(:public_project_maintainer) { create(:user) }
+ set(:private_group_owner) { create(:user) }
+
+ set(:group_ancestor_owner) { create(:user) }
+
+ before(:context) do
+ public_group.add_owner public_group_owner
+ private_group.add_developer private_group_member
+ public_project.add_maintainer public_project_maintainer
+
+ private_group.add_owner private_group_owner
+ private_group.parent.add_owner group_ancestor_owner
+ end
+
+ context 'when the private group is invited to the public project' do
+ before(:context) do
+ create(:project_group_link, group: private_group, project: public_project)
+ end
+
+ context 'when a user who is outside the public project and the private group is signed in' do
+ let(:service) { described_class.new(public_project, create(:user)) }
+
+ it 'does not return the private group' do
+ expect(usernames).not_to include(private_group.name)
+ end
+
+ it 'does not return private group members' do
+ expect(usernames).not_to include(private_group_member.username)
+ end
+
+ it 'returns the project maintainer' do
+ expect(usernames).to include(public_project_maintainer.username)
+ end
+
+ it 'returns project members from an invited public group' do
+ invited_public_group = create(:group, :public)
+ invited_public_group.add_owner create(:user)
+
+ create(:project_group_link, group: invited_public_group, project: public_project)
+
+ expect(usernames).to include(invited_public_group.users.first.username)
+ end
+
+ it 'does not return ancestors of the private group' do
+ expect(usernames).not_to include(group_ancestor_owner.username)
+ end
+ end
+
+ context 'when private group owner is signed in' do
+ let(:service) { described_class.new(public_project, private_group_owner) }
+
+ it 'returns private group members' do
+ expect(usernames).to include(private_group_member.username)
+ end
+
+ it 'returns ancestors of the the private group' do
+ expect(usernames).to include(group_ancestor_owner.username)
+ end
+ end
+
+ context 'when the namespace owner of the public project is signed in' do
+ let(:service) { described_class.new(public_project, public_group_owner) }
+
+ it 'returns private group members' do
+ expect(usernames).to include(private_group_member.username)
+ end
+
+ it 'does not return members of the ancestral groups of the private group' do
+ expect(usernames).to include(group_ancestor_owner.username)
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 26d8ac9b479..298867f483b 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -222,6 +222,24 @@ describe Projects::TransferService do
it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') }
end
+ context 'target namespace allows developers to create projects' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+
+ context 'the user is a member of the target namespace with developer permissions' do
+ subject(:transfer_project_result) { transfer_project(project, user, group) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ it 'does not allow project transfer to the target namespace' do
+ expect(transfer_project_result).to eq false
+ expect(project.namespace).to eq(user.namespace)
+ expect(project.errors[:new_namespace]).to include('Transfer failed, please contact an admin.')
+ end
+ end
+ end
+
def transfer_project(project, user, new_namespace)
service = Projects::TransferService.new(project, user)
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
index b5149a0fcb1..bc95fcd6b88 100644
--- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -34,8 +34,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
+ # Several instances of where these specs are shared route the request
+ # through ApplicationController#route_not_found which does not involve
+ # the usual auth code from Devise, so does not increment the
+ # :user_unauthenticated_counter
+ #
+ unless params[:ignore_incrementing]
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ end
personal_access_token.update(scopes: [:read_user])
@@ -84,8 +91,15 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
end
it "doesn't log the user in otherwise", unless: params[:public] do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
+ # Several instances of where these specs are shared route the request
+ # through ApplicationController#route_not_found which does not involve
+ # the usual auth code from Devise, so does not increment the
+ # :user_unauthenticated_counter
+ #
+ unless params[:ignore_incrementing]
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ end
get path, params: default_params.merge(private_token: 'token')
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 4d2ad165fd6..6fb1d279456 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -129,6 +129,7 @@ module GraphqlHelpers
allow_unlimited_graphql_complexity
allow_unlimited_graphql_depth
+ allow_high_graphql_recursion
type = GitlabSchema.types[class_name.to_s]
return "" unless type
@@ -213,6 +214,23 @@ module GraphqlHelpers
end
end
+ def expect_graphql_errors_to_include(regexes_to_match)
+ raise "No errors. Was expecting to match #{regexes_to_match}" if graphql_errors.nil? || graphql_errors.empty?
+
+ error_messages = flattened_errors.collect { |error_hash| error_hash["message"] }
+ Array.wrap(regexes_to_match).flatten.each do |regex|
+ expect(error_messages).to include a_string_matching regex
+ end
+ end
+
+ def expect_graphql_errors_to_be_empty
+ expect(flattened_errors).to be_empty
+ end
+
+ def flattened_errors
+ Array.wrap(graphql_errors).flatten.compact
+ end
+
# Raises an error if no response is found
def graphql_mutation_response(mutation_name)
graphql_data.fetch(GraphqlHelpers.fieldnamerize(mutation_name))
@@ -260,6 +278,10 @@ module GraphqlHelpers
allow_any_instance_of(GitlabSchema).to receive(:max_depth).and_return nil
allow(GitlabSchema).to receive(:max_query_depth).with(any_args).and_return nil
end
+
+ def allow_high_graphql_recursion
+ allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::RecursionAnalyzer).to receive(:recursion_threshold).and_return 1000
+ end
end
# This warms our schema, doing this as part of loading the helpers to avoid
diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb
index f3f9abb7da2..914bf506320 100644
--- a/spec/support/shared_examples/controllers/todos_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb
@@ -39,7 +39,7 @@ shared_examples 'todos actions' do
post_create
end.to change { user.todos.count }.by(0)
- expect(response).to have_gitlab_http_status(parent.is_a?(Group) ? 401 : 302)
+ expect(response).to have_gitlab_http_status(302)
end
end
end