Update upstream source from tag 'upstream/15.1.4+ds1'

Update to upstream version '15.1.4+ds1'
with Debian dir cf886bfe92
This commit is contained in:
Pirate Praveen 2022-07-29 14:16:26 +02:00
commit 2f3c8b06da
75 changed files with 1538 additions and 364 deletions

View file

@ -2,6 +2,29 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 15.1.4 (2022-07-28)
### Security (18 changes)
- [Security datadog integration leaking](gitlab-org/security/gitlab@1aadbf61796ff95b4716fb8ef335c5a4dbdf8c6a) ([merge request](gitlab-org/security/gitlab!2594))
- [Prevent users who cannot admin a public project from viewing deploy keys](gitlab-org/security/gitlab@a69bec0e58f4ee0df0774dcde1ce1817d41daa7c) ([merge request](gitlab-org/security/gitlab!2641))
- [Add additional condition to accept invitation](gitlab-org/security/gitlab@b3480d20512d61b761da2a8772ec2bfe32182169) ([merge request](gitlab-org/security/gitlab!2655))
- [Update GITLAB_PAGES_VERSION](gitlab-org/security/gitlab@6161c0445e4908cdc152bee4be8b9d223df9f1d0) ([merge request](gitlab-org/security/gitlab!2584))
- [Add html_escape to build_details_entity](gitlab-org/security/gitlab@c0a82385320e144b55e400eff96f39aa56af33ef) ([merge request](gitlab-org/security/gitlab!2610))
- [Check permissions when filtering by contact or organization](gitlab-org/security/gitlab@5b8f2da656ec7839614d4a49f35778ed00b31b2b) ([merge request](gitlab-org/security/gitlab!2645))
- [Use author to run subscribed pipeline](gitlab-org/security/gitlab@982917161408cfe4c3191f352bb1d390dfa3c9fd) ([merge request](gitlab-org/security/gitlab!2558))
- [Remove prohibited branches after project import](gitlab-org/security/gitlab@48864bf7539176eb385f5ce792c8e369a2b79fe6) ([merge request](gitlab-org/security/gitlab!2589))
- [Remove feature flag `ci_yaml_limit_size`](gitlab-org/security/gitlab@b532b82ef2a0ef5b045932a56ffc06281df7a1d8) ([merge request](gitlab-org/security/gitlab!2630))
- [Maintainer can change the visibility of Project and Group](gitlab-org/security/gitlab@9556769a3a2fe2c030eb55e87b1465638d315358) ([merge request](gitlab-org/security/gitlab!2618))
- [Do not link unverified secondary emails with any users](gitlab-org/security/gitlab@331b1d6b590b01c3d45332a6c8e09def249c627e) ([merge request](gitlab-org/security/gitlab!2627))
- [Forbid exchanging access token for ROP flow to users required 2FA setup](gitlab-org/security/gitlab@15bc29c4ab878365356e59c0c8d2e0f361b71e70) ([merge request](gitlab-org/security/gitlab!2621))
- [Remove todos from confidential notes when user loses access](gitlab-org/security/gitlab@9672296987190cc4b08b1f32f3c45086880b06bc) ([merge request](gitlab-org/security/gitlab!2608))
- [Remove group_bot_user and group_access_token after group delete](gitlab-org/security/gitlab@9b1a5e7101e1addee8c3429f4937980020c5f6b3) ([merge request](gitlab-org/security/gitlab!2634))
- [Protect integration secrets](gitlab-org/security/gitlab@4920ac9b10e237cca5d279cbd3cf60daa829a71b) ([merge request](gitlab-org/security/gitlab!2585))
- [Protect Grafana and Sentry integrations](gitlab-org/security/gitlab@a5d33edb1c557e9bbd0a1a79c81e851c1d740e37) ([merge request](gitlab-org/security/gitlab!2576))
- [Fix IDOR in Jira issue show action](gitlab-org/security/gitlab@8b62e0c09c33c44f508266d7b8db4892105124ac) ([merge request](gitlab-org/security/gitlab!2648))
- [Limit proxied requests to Grafana API](gitlab-org/security/gitlab@4580e61bc22b04eda13e293c5868a26a1a6ee571) ([merge request](gitlab-org/security/gitlab!2597))
## 15.1.3 (2022-07-19) ## 15.1.3 (2022-07-19)
### Added (1 change) ### Added (1 change)

View file

@ -1 +1 @@
15.1.3 15.1.4

View file

@ -1 +1 @@
1.59.0 1.59.1

View file

@ -1 +1 @@
15.1.3 15.1.4

View file

@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches] skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches]
before_action :check_search_rate_limit!, only: [:users, :projects] before_action :check_search_rate_limit!, only: [:users, :projects]
before_action :authorize_admin_project, only: :deploy_keys_with_owners
feature_category :users, [:users, :user] feature_category :users, [:users, :user]
feature_category :projects, [:projects] feature_category :projects, [:projects]
@ -69,6 +70,10 @@ class AutocompleteController < ApplicationController
private private
def authorize_admin_project
render_403 unless Ability.allowed?(current_user, :admin_project, project)
end
def project def project
@project ||= Autocomplete::ProjectFinder @project ||= Autocomplete::ProjectFinder
.new(current_user, params) .new(current_user, params)

View file

@ -478,10 +478,14 @@ class IssuableFinder
end end
def by_crm_contact(items) def by_crm_contact(items)
return items unless can_filter_by_crm_contact?
Issuables::CrmContactFilter.new(params: original_params).filter(items) Issuables::CrmContactFilter.new(params: original_params).filter(items)
end end
def by_crm_organization(items) def by_crm_organization(items)
return items unless can_filter_by_crm_organization?
Issuables::CrmOrganizationFilter.new(params: original_params).filter(items) Issuables::CrmOrganizationFilter.new(params: original_params).filter(items)
end end
@ -494,4 +498,20 @@ class IssuableFinder
def feature_flag_scope def feature_flag_scope
params.group || params.project params.group || params.project
end end
def can_filter_by_crm_contact?
current_user&.can?(:read_crm_contact, root_group)
end
def can_filter_by_crm_organization?
current_user&.can?(:read_crm_organization, root_group)
end
def root_group
strong_memoize(:root_group) do
base_group = params.group || params.project&.group
base_group&.root_ancestor
end
end
end end

View file

@ -44,6 +44,8 @@ module ErrorTracking
key: Settings.attr_encrypted_db_key_base_32, key: Settings.attr_encrypted_db_key_base_32,
algorithm: 'aes-256-gcm' algorithm: 'aes-256-gcm'
before_validation :reset_token
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
# When a user enables the integrated error tracking # When a user enables the integrated error tracking
@ -182,6 +184,12 @@ module ErrorTracking
private private
def reset_token
if api_url_changed? && !encrypted_token_changed?
self.token = nil
end
end
def ensure_issue_belongs_to_project!(project_id_from_api) def ensure_issue_belongs_to_project!(project_id_from_api)
raise 'The Sentry issue appers to be outside of the configured Sentry project' if Integer(project_id_from_api) != ensure_sentry_project_id! raise 'The Sentry issue appers to be outside of the configured Sentry project' if Integer(project_id_from_api) != ensure_sentry_project_id!
end end

View file

@ -18,6 +18,8 @@ class GrafanaIntegration < ApplicationRecord
validates :enabled, inclusion: { in: [true, false] } validates :enabled, inclusion: { in: [true, false] }
before_validation :reset_token
scope :enabled, -> { where(enabled: true) } scope :enabled, -> { where(enabled: true) }
def client def client
@ -36,6 +38,12 @@ class GrafanaIntegration < ApplicationRecord
private private
def reset_token
if grafana_url_changed? && !encrypted_token_changed?
self.token = nil
end
end
def token def token
decrypt(:token, encrypted_token) decrypt(:token, encrypted_token)
end end

View file

@ -22,6 +22,7 @@ class WebHookLog < ApplicationRecord
validates :web_hook, presence: true validates :web_hook, presence: true
before_save :obfuscate_basic_auth before_save :obfuscate_basic_auth
before_save :redact_author_email
def self.recent def self.recent
where('created_at >= ?', 2.days.ago.beginning_of_day) where('created_at >= ?', 2.days.ago.beginning_of_day)
@ -52,4 +53,10 @@ class WebHookLog < ApplicationRecord
def obfuscate_basic_auth def obfuscate_basic_auth
self.url = safe_url self.url = safe_url
end end
def redact_author_email
return unless self.request_data.dig('commit', 'author', 'email').present?
self.request_data['commit']['author']['email'] = _('[REDACTED]')
end
end end

View file

@ -2,8 +2,35 @@
module Integrations module Integrations
class Campfire < Integration class Campfire < Integration
prop_accessor :token, :subdomain, :room SUBDOMAIN_REGEXP = %r{\A[a-z](?:[a-z0-9-]*[a-z0-9])?\z}i.freeze
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
validates :room,
allow_blank: true,
numericality: { only_integer: true, greater_than: 0 }
validates :subdomain,
allow_blank: true,
format: { with: SUBDOMAIN_REGEXP }, length: { in: 1..63 }
field :token,
type: 'password',
title: -> { s_('Campfire token') },
help: -> { s_('CampfireService|API authentication token from Campfire.') },
non_empty_password_title: -> { s_('ProjectService|Enter new token') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
placeholder: '',
required: true
field :subdomain,
title: -> { s_('Campfire subdomain (optional)') },
placeholder: '',
exposes_secrets: true,
help: -> { s_('CampfireService|The %{code_open}.campfirenow.com%{code_close} subdomain.') % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe } }
field :room,
title: -> { s_('Campfire room ID (optional)') },
placeholder: '123456',
help: -> { s_('CampfireService|From the end of the room URL.') }
def title def title
'Campfire' 'Campfire'
@ -22,35 +49,6 @@ module Integrations
'campfire' 'campfire'
end end
def fields
[
{
type: 'password',
name: 'token',
title: _('Campfire token'),
help: s_('CampfireService|API authentication token from Campfire.'),
non_empty_password_title: s_('ProjectService|Enter new token'),
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
placeholder: '',
required: true
},
{
type: 'text',
name: 'subdomain',
title: _('Campfire subdomain (optional)'),
placeholder: '',
help: s_('CampfireService|The %{code_open}.campfirenow.com%{code_close} subdomain.') % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
},
{
type: 'text',
name: 'room',
title: _('Campfire room ID (optional)'),
placeholder: '123456',
help: s_('CampfireService|From the end of the room URL.')
}
]
end
def self.supported_events def self.supported_events
%w(push) %w(push)
end end

View file

@ -13,6 +13,7 @@ module Integrations
field :drone_url, field :drone_url,
title: -> { s_('ProjectService|Drone server URL') }, title: -> { s_('ProjectService|Drone server URL') },
placeholder: 'http://drone.example.com', placeholder: 'http://drone.example.com',
exposes_secrets: true,
required: true required: true
field :token, field :token,

View file

@ -222,7 +222,9 @@ module Integrations
# support any events. # support any events.
end end
def find_issue(issue_key, rendered_fields: false, transitions: false) def find_issue(issue_key, rendered_fields: false, transitions: false, restrict_project_key: false)
return if restrict_project_key && parse_project_from_issue_key(issue_key) != project_key
expands = [] expands = []
expands << 'renderedFields' if rendered_fields expands << 'renderedFields' if rendered_fields
expands << 'transitions' if transitions expands << 'transitions' if transitions
@ -320,6 +322,10 @@ module Integrations
private private
def parse_project_from_issue_key(issue_key)
issue_key.gsub(Gitlab::Regex.jira_issue_key_project_key_extraction_regex, '')
end
def branch_name(commit) def branch_name(commit)
commit.first_ref_by_oid(project.repository) commit.first_ref_by_oid(project.repository)
end end

View file

@ -5,7 +5,27 @@ module Integrations
include HasWebHook include HasWebHook
extend Gitlab::Utils::Override extend Gitlab::Utils::Override
prop_accessor :username, :token, :server field :username,
title: -> { s_('Username') },
help: -> { s_('Enter your Packagist username.') },
placeholder: '',
required: true
field :token,
type: 'password',
title: -> { s_('Token') },
help: -> { s_('Enter your Packagist token.') },
non_empty_password_title: -> { s_('ProjectService|Enter new token') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
placeholder: '',
required: true
field :server,
title: -> { s_('Server (optional)') },
help: -> { s_('Enter your Packagist server. Defaults to https://packagist.org.') },
placeholder: 'https://packagist.org',
exposes_secrets: true,
required: false
validates :username, presence: true, if: :activated? validates :username, presence: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
@ -22,37 +42,6 @@ module Integrations
'packagist' 'packagist'
end end
def fields
[
{
type: 'text',
name: 'username',
title: _('Username'),
help: s_('Enter your Packagist username.'),
placeholder: '',
required: true
},
{
type: 'password',
name: 'token',
title: _('Token'),
help: s_('Enter your Packagist token.'),
non_empty_password_title: s_('ProjectService|Enter new token'),
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
placeholder: '',
required: true
},
{
type: 'text',
name: 'server',
title: _('Server (optional)'),
help: s_('Enter your Packagist server. Defaults to https://packagist.org.'),
placeholder: 'https://packagist.org',
required: false
}
]
end
def self.supported_events def self.supported_events
%w(push merge_request tag_push) %w(push merge_request tag_push)
end end

View file

@ -1,10 +1,33 @@
# frozen_string_literal: true # frozen_string_literal: true
module Integrations module Integrations
class Zentao < Integration class Zentao < BaseIssueTracker
include Gitlab::Routing include Gitlab::Routing
data_field :url, :api_url, :api_token, :zentao_product_xid self.field_storage = :data_fields
field :url,
title: -> { s_('ZentaoIntegration|ZenTao Web URL') },
placeholder: 'https://www.zentao.net',
help: -> { s_('ZentaoIntegration|Base URL of the ZenTao instance.') },
exposes_secrets: true,
required: true
field :api_url,
title: -> { s_('ZentaoIntegration|ZenTao API URL (optional)') },
help: -> { s_('ZentaoIntegration|If different from Web URL.') },
exposes_secrets: true
field :api_token,
type: 'password',
title: -> { s_('ZentaoIntegration|ZenTao API token') },
non_empty_password_title: -> { s_('ZentaoIntegration|Enter new ZenTao API token') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
required: true
field :zentao_product_xid,
title: -> { s_('ZentaoIntegration|ZenTao Product ID') },
required: true
validates :url, public_url: true, presence: true, if: :activated? validates :url, public_url: true, presence: true, if: :activated?
validates :api_url, public_url: true, allow_blank: true validates :api_url, public_url: true, allow_blank: true
@ -19,6 +42,17 @@ module Integrations
zentao_tracker_data || self.build_zentao_tracker_data zentao_tracker_data || self.build_zentao_tracker_data
end end
alias_method :project_url, :url
def set_default_data
return unless issues_tracker.present?
return if url
data_fields.url ||= issues_tracker['url']
data_fields.api_url ||= issues_tracker['api_url']
end
def title def title
'ZenTao' 'ZenTao'
end end
@ -47,39 +81,6 @@ module Integrations
%w() %w()
end end
def fields
[
{
type: 'text',
name: 'url',
title: s_('ZentaoIntegration|ZenTao Web URL'),
placeholder: 'https://www.zentao.net',
help: s_('ZentaoIntegration|Base URL of the ZenTao instance.'),
required: true
},
{
type: 'text',
name: 'api_url',
title: s_('ZentaoIntegration|ZenTao API URL (optional)'),
help: s_('ZentaoIntegration|If different from Web URL.')
},
{
type: 'password',
name: 'api_token',
title: s_('ZentaoIntegration|ZenTao API token'),
non_empty_password_title: s_('ZentaoIntegration|Enter new ZenTao API token'),
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
required: true
},
{
type: 'text',
name: 'zentao_product_xid',
title: s_('ZentaoIntegration|ZenTao Product ID'),
required: true
}
]
end
private private
def client def client

View file

@ -2023,6 +2023,7 @@ class Project < ApplicationRecord
end end
def after_import def after_import
repository.remove_prohibited_branches
repository.expire_content_cache repository.expire_content_cache
wiki.repository.expire_content_cache wiki.repository.expire_content_cache

View file

@ -1168,6 +1168,16 @@ class Repository
@cache ||= Gitlab::RepositoryCache.new(self) @cache ||= Gitlab::RepositoryCache.new(self)
end end
def remove_prohibited_branches
return unless exists?
prohibited_branches = raw_repository.branch_names.select { |name| name.match(/\A\h{40}\z/) }
return if prohibited_branches.blank?
prohibited_branches.each { |name| raw_repository.delete_branch(name) }
end
private private
# TODO Genericize finder, later split this on finders by Ref or Oid # TODO Genericize finder, later split this on finders by Ref or Oid

View file

@ -70,8 +70,6 @@ class Snippet < ApplicationRecord
}, },
if: :content_changed? if: :content_changed?
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
after_create :create_statistics after_create :create_statistics
# Scopes # Scopes

View file

@ -74,6 +74,7 @@ class Todo < ApplicationRecord
scope :for_commit, -> (id) { where(commit_id: id) } scope :for_commit, -> (id) { where(commit_id: id) }
scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: [:route, :owner] }]) } scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: [:route, :owner] }]) }
scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) } scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
scope :for_internal_notes, -> { joins(:note).where(note: { confidential: true }) }
enum resolved_by_action: { system_done: 0, api_all_done: 1, api_done: 2, mark_all_done: 3, mark_done: 4 }, _prefix: :resolved_by enum resolved_by_action: { system_done: 0, api_all_done: 1, api_done: 2, mark_all_done: 3, mark_done: 4 }, _prefix: :resolved_by

View file

@ -602,23 +602,24 @@ class User < ApplicationRecord
end end
end end
# Find a User by their primary email or any associated secondary email # Find a User by their primary email or any associated confirmed secondary email
def find_by_any_email(email, confirmed: false) def find_by_any_email(email, confirmed: false)
return unless email return unless email
by_any_email(email, confirmed: confirmed).take by_any_email(email, confirmed: confirmed).take
end end
# Returns a relation containing all the users for the given email addresses # Returns a relation containing all found users by their primary email
# or any associated confirmed secondary email
# #
# @param emails [String, Array<String>] email addresses to check # @param emails [String, Array<String>] email addresses to check
# @param confirmed [Boolean] Only return users where the email is confirmed # @param confirmed [Boolean] Only return users where the primary email is confirmed
def by_any_email(emails, confirmed: false) def by_any_email(emails, confirmed: false)
from_users = by_user_email(emails) from_users = by_user_email(emails)
from_users = from_users.confirmed if confirmed from_users = from_users.confirmed if confirmed
from_emails = by_emails(emails) from_emails = by_emails(emails).merge(Email.confirmed)
from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed from_emails = from_emails.confirmed if confirmed
items = [from_users, from_emails] items = [from_users, from_emails]
@ -723,6 +724,7 @@ class User < ApplicationRecord
matched_by_email_user_id = email_table matched_by_email_user_id = email_table
.project(email_table[:user_id]) .project(email_table[:user_id])
.where(email_table[:email].eq(email_address)) .where(email_table[:email].eq(email_address))
.where(email_table[:confirmed_at].not_eq(nil))
.take(1) # at most 1 record as there is a unique constraint .take(1) # at most 1 record as there is a unique constraint
where( where(
@ -1435,7 +1437,7 @@ class User < ApplicationRecord
all_emails = [] all_emails = []
all_emails << email unless temp_oauth_email? all_emails << email unless temp_oauth_email?
all_emails << private_commit_email if include_private_email all_emails << private_commit_email if include_private_email
all_emails.concat(emails.map(&:email)) all_emails.concat(emails.filter_map { |email| email.email if email.confirmed? })
all_emails.uniq all_emails.uniq
end end

View file

@ -151,7 +151,7 @@ class BuildDetailsEntity < Ci::JobEntity
# We do not return the invalid_dependencies for all scenarios see https://gitlab.com/gitlab-org/gitlab/-/issues/287772#note_914406387 # We do not return the invalid_dependencies for all scenarios see https://gitlab.com/gitlab-org/gitlab/-/issues/287772#note_914406387
punctuation = invalid_dependencies.empty? ? '.' : ': ' punctuation = invalid_dependencies.empty? ? '.' : ': '
_("This job could not start because it could not retrieve the needed artifacts%{punctuation}%{invalid_dependencies}") % _("This job could not start because it could not retrieve the needed artifacts%{punctuation}%{invalid_dependencies}") %
{ invalid_dependencies: invalid_dependencies, punctuation: punctuation } { invalid_dependencies: html_escape(invalid_dependencies), punctuation: punctuation }
end end
def help_message(docs_url) def help_message(docs_url)

View file

@ -5,7 +5,7 @@ module UpdateVisibilityLevel
def valid_visibility_level_change?(target, new_visibility) def valid_visibility_level_change?(target, new_visibility)
return true unless new_visibility return true unless new_visibility
new_visibility_level = Gitlab::VisibilityLevel.level_value(new_visibility) new_visibility_level = Gitlab::VisibilityLevel.level_value(new_visibility, fallback_value: nil)
if new_visibility_level != target.visibility_level_value if new_visibility_level != target.visibility_level_value
unless can?(current_user, :change_visibility_level, target) && unless can?(current_user, :change_visibility_level, target) &&

View file

@ -15,6 +15,10 @@ module Grafana
self.reactive_cache_work_type = :external_dependency self.reactive_cache_work_type = :external_dependency
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
SUPPORTED_DATASOURCE_PATTERN = %r{\A\d+\z}.freeze
SUPPORTED_PROXY_PATH = Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter::PROXY_PATH
attr_accessor :project, :datasource_id, :proxy_path, :query_params attr_accessor :project, :datasource_id, :proxy_path, :query_params
# @param project_id [Integer] Project id for which grafana is configured. # @param project_id [Integer] Project id for which grafana is configured.
@ -38,6 +42,7 @@ module Grafana
end end
def execute def execute
return cannot_proxy_response unless can_proxy?
return cannot_proxy_response unless client return cannot_proxy_response unless client
with_reactive_cache(*cache_key) { |result| result } with_reactive_cache(*cache_key) { |result| result }
@ -69,6 +74,11 @@ module Grafana
private private
def can_proxy?
SUPPORTED_PROXY_PATH == proxy_path &&
SUPPORTED_DATASOURCE_PATTERN.match?(datasource_id)
end
def client def client
project.grafana_integration&.client project.grafana_integration&.client
end end

View file

@ -35,6 +35,8 @@ module Groups
user_ids_for_project_authorizations_refresh = obtain_user_ids_for_project_authorizations_refresh user_ids_for_project_authorizations_refresh = obtain_user_ids_for_project_authorizations_refresh
destroy_group_bots
group.destroy group.destroy
if user_ids_for_project_authorizations_refresh.present? if user_ids_for_project_authorizations_refresh.present?
@ -76,6 +78,19 @@ module Groups
group.users_ids_of_direct_members group.users_ids_of_direct_members
end end
# rubocop:disable CodeReuse/ActiveRecord
def destroy_group_bots
bot_ids = group.members_and_requesters.joins(:user).merge(User.project_bot).pluck(:user_id)
current_user_id = current_user.id
group.run_after_commit do
bot_ids.each do |user_id|
DeleteUserWorker.perform_async(current_user_id, user_id, skip_authorization: true)
end
end
end
# rubocop:enable CodeReuse/ActiveRecord
end end
end end

View file

@ -41,11 +41,20 @@ module Todos
end end
def remove_confidential_resource_todos def remove_confidential_resource_todos
# Deletes todos for confidential issues
Todo Todo
.for_target(confidential_issues.select(:id)) .for_target(confidential_issues.select(:id))
.for_type(Issue.name) .for_type(Issue.name)
.for_user(user) .for_user(user)
.delete_all .delete_all
# Deletes todos for internal notes on unauthorized projects
Todo
.for_type(Issue.name)
.for_internal_notes
.for_project(non_authorized_reporter_projects) # Only Reporter+ can read internal notes
.for_user(user)
.delete_all
end end
def remove_project_todos def remove_project_todos

View file

@ -1,8 +0,0 @@
---
name: ci_yaml_limit_size
introduced_by_url: https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3126
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/29875
milestone: '12.0'
type: development
group: group::pipeline authoring
default_enabled: true

View file

@ -144,6 +144,9 @@ ci_subscriptions_projects:
- table: projects - table: projects
column: upstream_project_id column: upstream_project_id
on_delete: async_delete on_delete: async_delete
- table: users
column: author_id
on_delete: async_delete
ci_triggers: ci_triggers:
- table: users - table: users
column: owner_id column: owner_id

View file

@ -24,7 +24,11 @@ Doorkeeper.configure do
resource_owner_from_credentials do |routes| resource_owner_from_credentials do |routes|
user = Gitlab::Auth.find_with_user_password(params[:username], params[:password], increment_failed_attempts: true) user = Gitlab::Auth.find_with_user_password(params[:username], params[:password], increment_failed_attempts: true)
user unless user.try(:two_factor_enabled?)
next unless user
next if user.two_factor_enabled? || Gitlab::Auth::TwoFactorAuthVerifier.new(user).two_factor_authentication_enforced?
user
end end
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
class AddAuthorToCiSubscriptionsProjects < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'index_ci_subscriptions_projects_author_id'
def up
unless column_exists?(:ci_subscriptions_projects, :author_id)
add_column :ci_subscriptions_projects, :author_id, :bigint
end
add_concurrent_index :ci_subscriptions_projects, :author_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :ci_subscriptions_projects, INDEX_NAME
remove_column :ci_subscriptions_projects, :author_id
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
class AddIndexWithTargetTypeToTodos < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_FOR_PROJECTS_NAME = 'index_requirements_project_id_user_id_id_and_target_type'
INDEX_FOR_TARGET_TYPE_NAME = 'index_requirements_user_id_and_target_type'
def up
add_concurrent_index :todos, [:project_id, :user_id, :id, :target_type], name: INDEX_FOR_PROJECTS_NAME
add_concurrent_index :todos, [:user_id, :target_type], name: INDEX_FOR_TARGET_TYPE_NAME
end
def down
remove_concurrent_index_by_name :todos, INDEX_FOR_PROJECTS_NAME
remove_concurrent_index_by_name :todos, INDEX_FOR_TARGET_TYPE_NAME
end
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
class RemoveDeprecatedIndexesFromTodos < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
PROJECTS_INDEX = 'index_todos_on_project_id_and_user_id_and_id'
USERS_INDEX = 'index_todos_on_user_id'
# These indexes are deprecated in favor of two new ones
# added in a previous migration:
#
# * index_requirements_project_id_user_id_target_type_and_id
# * index_requirements_user_id_and_target_type
def up
remove_concurrent_index_by_name :todos, PROJECTS_INDEX
remove_concurrent_index_by_name :todos, USERS_INDEX
end
def down
add_concurrent_index :todos, [:project_id, :user_id, :id], name: PROJECTS_INDEX
add_concurrent_index :todos, :user_id, name: USERS_INDEX
end
end

View file

@ -0,0 +1 @@
8726707f9f4bb8d256886c592b6a11ba8487de24f5340c837800f5ce0c32df9d

View file

@ -0,0 +1 @@
f6638435457f57f5c566e107de4f4557a1d87b5dd27acc9e5345999197d18e6e

View file

@ -0,0 +1 @@
ff9ad44a43be82867da8e0f51e68a2284065cab6b2eb7cf6496108dce1cdd657

View file

@ -13202,7 +13202,8 @@ ALTER SEQUENCE ci_stages_id_seq OWNED BY ci_stages.id;
CREATE TABLE ci_subscriptions_projects ( CREATE TABLE ci_subscriptions_projects (
id bigint NOT NULL, id bigint NOT NULL,
downstream_project_id bigint NOT NULL, downstream_project_id bigint NOT NULL,
upstream_project_id bigint NOT NULL upstream_project_id bigint NOT NULL,
author_id bigint
); );
CREATE SEQUENCE ci_subscriptions_projects_id_seq CREATE SEQUENCE ci_subscriptions_projects_id_seq
@ -27529,6 +27530,8 @@ CREATE INDEX index_ci_stages_on_pipeline_id_and_position ON ci_stages USING btre
CREATE INDEX index_ci_stages_on_project_id ON ci_stages USING btree (project_id); CREATE INDEX index_ci_stages_on_project_id ON ci_stages USING btree (project_id);
CREATE INDEX index_ci_subscriptions_projects_author_id ON ci_subscriptions_projects USING btree (author_id);
CREATE INDEX index_ci_subscriptions_projects_on_upstream_project_id ON ci_subscriptions_projects USING btree (upstream_project_id); CREATE INDEX index_ci_subscriptions_projects_on_upstream_project_id ON ci_subscriptions_projects USING btree (upstream_project_id);
CREATE UNIQUE INDEX index_ci_subscriptions_projects_unique_subscription ON ci_subscriptions_projects USING btree (downstream_project_id, upstream_project_id); CREATE UNIQUE INDEX index_ci_subscriptions_projects_unique_subscription ON ci_subscriptions_projects USING btree (downstream_project_id, upstream_project_id);
@ -29261,6 +29264,10 @@ CREATE INDEX index_requirements_on_title_trigram ON requirements USING gin (titl
CREATE INDEX index_requirements_on_updated_at ON requirements USING btree (updated_at); CREATE INDEX index_requirements_on_updated_at ON requirements USING btree (updated_at);
CREATE INDEX index_requirements_project_id_user_id_id_and_target_type ON todos USING btree (project_id, user_id, id, target_type);
CREATE INDEX index_requirements_user_id_and_target_type ON todos USING btree (user_id, target_type);
CREATE INDEX index_resource_iteration_events_on_issue_id ON resource_iteration_events USING btree (issue_id); CREATE INDEX index_resource_iteration_events_on_issue_id ON resource_iteration_events USING btree (issue_id);
CREATE INDEX index_resource_iteration_events_on_iteration_id ON resource_iteration_events USING btree (iteration_id); CREATE INDEX index_resource_iteration_events_on_iteration_id ON resource_iteration_events USING btree (iteration_id);
@ -29565,12 +29572,8 @@ CREATE INDEX index_todos_on_note_id ON todos USING btree (note_id);
CREATE INDEX index_todos_on_project_id_and_id ON todos USING btree (project_id, id); CREATE INDEX index_todos_on_project_id_and_id ON todos USING btree (project_id, id);
CREATE INDEX index_todos_on_project_id_and_user_id_and_id ON todos USING btree (project_id, user_id, id);
CREATE INDEX index_todos_on_target_type_and_target_id ON todos USING btree (target_type, target_id); CREATE INDEX index_todos_on_target_type_and_target_id ON todos USING btree (target_type, target_id);
CREATE INDEX index_todos_on_user_id ON todos USING btree (user_id);
CREATE INDEX index_todos_on_user_id_and_id_done ON todos USING btree (user_id, id) WHERE ((state)::text = 'done'::text); CREATE INDEX index_todos_on_user_id_and_id_done ON todos USING btree (user_id, id) WHERE ((state)::text = 'done'::text);
CREATE INDEX index_todos_on_user_id_and_id_pending ON todos USING btree (user_id, id) WHERE ((state)::text = 'pending'::text); CREATE INDEX index_todos_on_user_id_and_id_pending ON todos USING btree (user_id, id) WHERE ((state)::text = 'pending'::text);

View file

@ -747,12 +747,6 @@ You can change these limits in the [GitLab Rails console](operations/rails_conso
ApplicationSetting.update!(max_yaml_depth: 125) ApplicationSetting.update!(max_yaml_depth: 125)
``` ```
To disable this limitation entirely, disable the feature flag in the console:
```ruby
Feature.disable(:ci_yaml_limit_size)
```
### Limit dotenv variables ### Limit dotenv variables
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321552) in GitLab 14.5. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321552) in GitLab 14.5.

View file

@ -41,8 +41,6 @@ module Gitlab
end end
def too_big? def too_big?
return false unless Feature.enabled?(:ci_yaml_limit_size)
!deep_size.valid? !deep_size.valid?
end end

View file

@ -418,6 +418,10 @@ module Gitlab
@jira_issue_key_regex ||= /[A-Z][A-Z_0-9]+-\d+/ @jira_issue_key_regex ||= /[A-Z][A-Z_0-9]+-\d+/
end end
def jira_issue_key_project_key_extraction_regex
@jira_issue_key_project_key_extraction_regex ||= /-\d+/
end
def jira_transition_id_regex def jira_transition_id_regex
@jira_transition_id_regex ||= /\d+/ @jira_transition_id_regex ||= /\d+/
end end

View file

@ -18,6 +18,8 @@ module Gitlab
scope :public_to_user, -> (user = nil) do scope :public_to_user, -> (user = nil) do
where(visibility_level: VisibilityLevel.levels_for_user(user)) where(visibility_level: VisibilityLevel.levels_for_user(user))
end end
alias_method :visibility_level=, :visibility=
end end
PRIVATE = 0 unless const_defined?(:PRIVATE) PRIVATE = 0 unless const_defined?(:PRIVATE)
@ -108,10 +110,10 @@ module Gitlab
options.key(level.to_i) || s_('VisibilityLevel|Unknown') options.key(level.to_i) || s_('VisibilityLevel|Unknown')
end end
def level_value(level) def level_value(level, fallback_value: PRIVATE)
return level.to_i if level.to_i.to_s == level.to_s && string_options.key(level.to_i) return level.to_i if level.to_i.to_s == level.to_s && string_options.key(level.to_i)
string_options[level] || PRIVATE string_options[level] || fallback_value
end end
def string_level(level) def string_level(level)

View file

@ -10,8 +10,8 @@ require_relative 'api/default_options'
# Each desired feature flag state is specified as 'feature-flag=state'. This allows us to run package-and-qa with the # Each desired feature flag state is specified as 'feature-flag=state'. This allows us to run package-and-qa with the
# feature flag set to the desired state. # feature flag set to the desired state.
# #
# For example, if the specified files included `config/feature_flags/development/ci_yaml_limit_size.yml` and the desired # For example, if the specified files included `config/feature_flags/development/ci_awesome_feature.yml` and the desired
# state as specified by the second argument was enabled, the value returned would be `ci_yaml_limit_size=enabled` # state as specified by the second argument was enabled, the value returned would be `ci_awesome_feature=enabled`
class GetFeatureFlagsFromFiles class GetFeatureFlagsFromFiles
def initialize(options) def initialize(options)

View file

@ -378,43 +378,27 @@ RSpec.describe AutocompleteController do
end end
context 'GET deploy_keys_with_owners' do context 'GET deploy_keys_with_owners' do
let!(:deploy_key) { create(:deploy_key, user: user) } let_it_be(:public_project) { create(:project, :public) }
let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) } let_it_be(:user) { create(:user) }
let_it_be(:deploy_key) { create(:deploy_key, user: user) }
let_it_be(:deploy_keys_project) do
create(:deploy_keys_project, :write_access, project: public_project, deploy_key: deploy_key)
end
context 'unauthorized user' do context 'unauthorized user' do
it 'returns a not found response' do it 'returns a not found response' do
get(:deploy_keys_with_owners, params: { project_id: project.id }) get(:deploy_keys_with_owners, params: { project_id: public_project.id })
expect(response).to have_gitlab_http_status(:redirect) expect(response).to have_gitlab_http_status(:redirect)
end end
end end
context 'when the user who can read the project is logged in' do context 'when the user is logged in' do
before do before do
sign_in(user) sign_in(user)
end end
context 'and they cannot read the project' do context 'with a non-existing project' do
it 'returns a not found response' do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(false)
get(:deploy_keys_with_owners, params: { project_id: project.id })
expect(response).to have_gitlab_http_status(:not_found)
end
end
it 'renders the deploy key in a json payload, with its owner' do
get(:deploy_keys_with_owners, params: { project_id: project.id })
expect(json_response.count).to eq(1)
expect(json_response.first['title']).to eq(deploy_key.title)
expect(json_response.first['owner']['id']).to eq(deploy_key.user.id)
expect(json_response.first['deploy_keys_projects']).to be_nil
end
context 'with an unknown project' do
it 'returns a not found response' do it 'returns a not found response' do
get(:deploy_keys_with_owners, params: { project_id: 9999 }) get(:deploy_keys_with_owners, params: { project_id: 9999 })
@ -422,19 +406,46 @@ RSpec.describe AutocompleteController do
end end
end end
context 'and the user cannot read the owner of the key' do context 'with an existing project' do
before do context 'when user cannot admin project' do
allow(Ability).to receive(:allowed?).and_call_original it 'returns a forbidden response' do
allow(Ability).to receive(:allowed?).with(user, :read_user, deploy_key.user).and_return(false) get(:deploy_keys_with_owners, params: { project_id: public_project.id })
expect(response).to have_gitlab_http_status(:forbidden)
end
end end
it 'returns a payload without owner' do context 'when user can admin project' do
get(:deploy_keys_with_owners, params: { project_id: project.id }) before do
public_project.add_maintainer(user)
end
expect(json_response.count).to eq(1) context 'and user can read owner of key' do
expect(json_response.first['title']).to eq(deploy_key.title) it 'renders the deploy keys in a json payload, with owner' do
expect(json_response.first['owner']).to be_nil get(:deploy_keys_with_owners, params: { project_id: public_project.id })
expect(json_response.first['deploy_keys_projects']).to be_nil
expect(json_response.count).to eq(1)
expect(json_response.first['title']).to eq(deploy_key.title)
expect(json_response.first['owner']['id']).to eq(deploy_key.user.id)
expect(json_response.first['deploy_keys_projects']).to be_nil
end
end
context 'and user cannot read owner of key' do
before do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(user, :read_user, deploy_key.user).and_return(false)
end
it 'returns a payload without owner' do
get(:deploy_keys_with_owners, params: { project_id: public_project.id })
expect(json_response.count).to eq(1)
expect(json_response.first['title']).to eq(deploy_key.title)
expect(json_response.first['owner']).to be_nil
expect(json_response.first['deploy_keys_projects']).to be_nil
end
end
end end
end end
end end

View file

@ -143,14 +143,28 @@ RSpec.describe InvitesController do
context 'when user exists with the invited email as secondary email' do context 'when user exists with the invited email as secondary email' do
before do before do
secondary_email = create(:email, user: user, email: 'foo@example.com')
member.update!(invite_email: secondary_email.email) member.update!(invite_email: secondary_email.email)
end end
it 'is redirected to a new session with invite email param' do context 'when secondary email is confirmed' do
request let(:secondary_email) { create(:email, :confirmed, user: user, email: 'foo@example.com') }
expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email)) it 'is redirected to a new session with invite email param' do
request
expect(response).to redirect_to(new_user_session_path(invite_email: member.invite_email))
end
end
context 'when secondary email is unconfirmed' do
let(:secondary_email) { create(:email, user: user, email: 'foo@example.com') }
it 'is redirected to a new registration with invite email param and flash message', :aggregate_failures do
request
expect(response).to redirect_to(new_user_registration_path(invite_email: member.invite_email))
expect(flash[:notice]).to eq 'To accept this invitation, create an account or sign in.'
end
end end
end end

View file

@ -61,7 +61,7 @@ RSpec.describe 'GPG signed commits' do
let(:user_2) do let(:user_2) do
create(:user, email: GpgHelpers::User2.emails.first, username: 'bette.cartwright', name: 'Bette Cartwright').tap do |user| create(:user, email: GpgHelpers::User2.emails.first, username: 'bette.cartwright', name: 'Bette Cartwright').tap do |user|
# secondary, unverified email # secondary, unverified email
create :email, user: user, email: GpgHelpers::User2.emails.last create :email, user: user, email: 'mail@koffeinfrei.org'
end end
end end
@ -83,10 +83,11 @@ RSpec.describe 'GPG signed commits' do
end end
end end
it 'unverified signature: user email does not match the committer email, but is the same user' do it 'unverified signature: gpg key email does not match the committer_email but is the same user when the committer_email belongs to the user as a confirmed secondary email' do
user_2_key user_2_key
user_2.emails.find_by(email: 'mail@koffeinfrei.org').confirm
visit project_commit_path(project, GpgHelpers::DIFFERING_EMAIL_SHA) visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)
wait_for_all_requests wait_for_all_requests
page.find('.gpg-status-box', text: 'Unverified').click page.find('.gpg-status-box', text: 'Unverified').click
@ -99,7 +100,7 @@ RSpec.describe 'GPG signed commits' do
end end
end end
it 'unverified signature: user email does not match the committer email' do it 'unverified signature: gpg key email does not match the committer_email when the committer_email belongs to the user as a unconfirmed secondary email' do
user_2_key user_2_key
visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA) visit project_commit_path(project, GpgHelpers::SIGNED_COMMIT_SHA)

View file

@ -30,6 +30,9 @@ RSpec.describe Resolvers::IssuesResolver do
before_all do before_all do
project.add_developer(current_user) project.add_developer(current_user)
project.add_reporter(reporter) project.add_reporter(reporter)
create(:crm_settings, group: group, enabled: true)
create(:label_link, label: label1, target: issue1) create(:label_link, label: label1, target: issue1)
create(:label_link, label: label1, target: issue2) create(:label_link, label: label1, target: issue2)
create(:label_link, label: label2, target: issue2) create(:label_link, label: label2, target: issue2)
@ -399,6 +402,8 @@ RSpec.describe Resolvers::IssuesResolver do
let_it_be(:crm_issue3) { create(:issue, project: project) } let_it_be(:crm_issue3) { create(:issue, project: project) }
before_all do before_all do
group.add_developer(current_user)
create(:issue_customer_relations_contact, issue: crm_issue1, contact: contact1) create(:issue_customer_relations_contact, issue: crm_issue1, contact: contact1)
create(:issue_customer_relations_contact, issue: crm_issue2, contact: contact2) create(:issue_customer_relations_contact, issue: crm_issue2, contact: contact2)
create(:issue_customer_relations_contact, issue: crm_issue3, contact: contact3) create(:issue_customer_relations_contact, issue: crm_issue3, contact: contact3)
@ -631,13 +636,13 @@ RSpec.describe Resolvers::IssuesResolver do
end end
it 'finds a specific issue with iid', :request_store do it 'finds a specific issue with iid', :request_store do
result = batch_sync(max_queries: 7) { resolve_issues(iid: issue1.iid).to_a } result = batch_sync(max_queries: 8) { resolve_issues(iid: issue1.iid).to_a }
expect(result).to contain_exactly(issue1) expect(result).to contain_exactly(issue1)
end end
it 'batches queries that only include IIDs', :request_store do it 'batches queries that only include IIDs', :request_store do
result = batch_sync(max_queries: 7) do result = batch_sync(max_queries: 8) do
[issue1, issue2] [issue1, issue2]
.map { |issue| resolve_issues(iid: issue.iid.to_s) } .map { |issue| resolve_issues(iid: issue.iid.to_s) }
.flat_map(&:to_a) .flat_map(&:to_a)
@ -647,7 +652,7 @@ RSpec.describe Resolvers::IssuesResolver do
end end
it 'finds a specific issue with iids', :request_store do it 'finds a specific issue with iids', :request_store do
result = batch_sync(max_queries: 7) do result = batch_sync(max_queries: 8) do
resolve_issues(iids: [issue1.iid]).to_a resolve_issues(iids: [issue1.iid]).to_a
end end

View file

@ -120,12 +120,6 @@ RSpec.describe Gitlab::Config::Loader::Yaml do
it 'returns false' do it 'returns false' do
expect(loader).not_to be_valid expect(loader).not_to be_valid
end end
it 'returns true if "ci_yaml_limit_size" feature flag is disabled' do
stub_feature_flags(ci_yaml_limit_size: false)
expect(loader).to be_valid
end
end end
describe '#load!' do describe '#load!' do

View file

@ -274,7 +274,46 @@ RSpec.describe Gitlab::Gpg::Commit do
it_behaves_like 'returns the cached signature on second call' it_behaves_like 'returns the cached signature on second call'
end end
context 'user email does not match the committer email, but is the same user' do context 'gpg key email does not match the committer_email but is the same user when the committer_email belongs to the user as a confirmed secondary email' do
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
let(:user) do
create(:user, email: GpgHelpers::User1.emails.first).tap do |user|
create :email, :confirmed, user: user, email: GpgHelpers::User2.emails.first
end
end
let!(:gpg_key) do
create :gpg_key, key: GpgHelpers::User1.public_key, user: user
end
before do
allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
.with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data
]
)
end
it 'returns an invalid signature' do
expect(described_class.new(commit).signature).to have_attributes(
commit_sha: commit_sha,
project: project,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: GpgHelpers::User1.names.first,
gpg_key_user_email: GpgHelpers::User1.emails.first,
verification_status: 'same_user_different_email'
)
end
it_behaves_like 'returns the cached signature on second call'
end
context 'gpg key email does not match the committer_email when the committer_email belongs to the user as a unconfirmed secondary email' do
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first } let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
let(:user) do let(:user) do
@ -306,7 +345,7 @@ RSpec.describe Gitlab::Gpg::Commit do
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid, gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: GpgHelpers::User1.names.first, gpg_key_user_name: GpgHelpers::User1.names.first,
gpg_key_user_email: GpgHelpers::User1.emails.first, gpg_key_user_email: GpgHelpers::User1.emails.first,
verification_status: 'same_user_different_email' verification_status: 'other_user'
) )
end end

View file

@ -571,7 +571,6 @@ project:
- remove_source_branch_after_merge - remove_source_branch_after_merge
- deleting_user - deleting_user
- upstream_projects - upstream_projects
- downstream_projects
- upstream_project_subscriptions - upstream_project_subscriptions
- downstream_project_subscriptions - downstream_project_subscriptions
- service_desk_setting - service_desk_setting

View file

@ -20,17 +20,30 @@ RSpec.describe Gitlab::LegacyGithubImport::UserFormatter do
expect(user.gitlab_id).to eq gl_user.id expect(user.gitlab_id).to eq gl_user.id
end end
it 'returns GitLab user id when user primary email matches GitHub email' do it 'returns GitLab user id when user confirmed primary email matches GitHub email' do
gl_user = create(:user, email: octocat.email) gl_user = create(:user, email: octocat.email)
expect(user.gitlab_id).to eq gl_user.id expect(user.gitlab_id).to eq gl_user.id
end end
it 'returns GitLab user id when any of user linked emails matches GitHub email' do it 'returns GitLab user id when user unconfirmed primary email matches GitHub email' do
gl_user = create(:user, :unconfirmed, email: octocat.email)
expect(user.gitlab_id).to eq gl_user.id
end
it 'returns GitLab user id when user confirmed secondary email matches GitHub email' do
gl_user = create(:user, email: 'johndoe@example.com')
create(:email, :confirmed, user: gl_user, email: octocat.email)
expect(user.gitlab_id).to eq gl_user.id
end
it 'returns nil when user unconfirmed secondary email matches GitHub email' do
gl_user = create(:user, email: 'johndoe@example.com') gl_user = create(:user, email: 'johndoe@example.com')
create(:email, user: gl_user, email: octocat.email) create(:email, user: gl_user, email: octocat.email)
expect(user.gitlab_id).to eq gl_user.id expect(user.gitlab_id).to be_nil
end end
end end

View file

@ -4,20 +4,52 @@ require 'spec_helper'
RSpec.describe Gitlab::VisibilityLevel do RSpec.describe Gitlab::VisibilityLevel do
describe '.level_value' do describe '.level_value' do
it 'converts "public" to integer value' do where(:string_value, :integer_value) do
expect(described_class.level_value('public')).to eq(Gitlab::VisibilityLevel::PUBLIC) [
['private', described_class::PRIVATE],
['internal', described_class::INTERNAL],
['public', described_class::PUBLIC]
]
end end
it 'converts string integer to integer value' do with_them do
expect(described_class.level_value('20')).to eq(20) it "converts '#{params[:string_value]}' to integer value #{params[:integer_value]}" do
expect(described_class.level_value(string_value)).to eq(integer_value)
end
it "converts string integer '#{params[:integer_value]}' to integer value #{params[:integer_value]}" do
expect(described_class.level_value(integer_value.to_s)).to eq(integer_value)
end
it 'defaults to PRIVATE when string integer value is not valid' do
expect(described_class.level_value(integer_value.to_s + 'r')).to eq(described_class::PRIVATE)
expect(described_class.level_value(integer_value.to_s + ' ')).to eq(described_class::PRIVATE)
expect(described_class.level_value('r' + integer_value.to_s)).to eq(described_class::PRIVATE)
end
it 'defaults to PRIVATE when string value is not valid' do
expect(described_class.level_value(string_value.capitalize)).to eq(described_class::PRIVATE)
expect(described_class.level_value(string_value + ' ')).to eq(described_class::PRIVATE)
expect(described_class.level_value(string_value + 'r')).to eq(described_class::PRIVATE)
end
end end
it 'defaults to PRIVATE when string value is not valid' do it 'defaults to PRIVATE when string value is not valid' do
expect(described_class.level_value('invalid')).to eq(Gitlab::VisibilityLevel::PRIVATE) expect(described_class.level_value('invalid')).to eq(described_class::PRIVATE)
end end
it 'defaults to PRIVATE when integer value is not valid' do it 'defaults to PRIVATE when integer value is not valid' do
expect(described_class.level_value(100)).to eq(Gitlab::VisibilityLevel::PRIVATE) expect(described_class.level_value(100)).to eq(described_class::PRIVATE)
end
context 'when `fallback_value` is set to `nil`' do
it 'returns `nil` when string value is not valid' do
expect(described_class.level_value('invalid', fallback_value: nil)).to be_nil
end
it 'returns `nil` when integer value is not valid' do
expect(described_class.level_value(100, fallback_value: nil)).to be_nil
end
end end
end end

View file

@ -226,27 +226,45 @@ RSpec.describe Commit do
end end
describe '#committer' do describe '#committer' do
context 'with a confirmed e-mail' do context "when committer_email is the user's primary email" do
it 'returns the user' do context 'when the user email is confirmed' do
user = create(:user, email: commit.committer_email) let!(:user) { create(:user, email: commit.committer_email) }
expect(commit.committer).to eq(user) it 'returns the user' do
expect(commit.committer).to eq(user)
expect(commit.committer(confirmed: false)).to eq(user)
end
end
context 'when the user email is unconfirmed' do
let!(:user) { create(:user, :unconfirmed, email: commit.committer_email) }
it 'returns the user according to confirmed argument' do
expect(commit.committer).to be_nil
expect(commit.committer(confirmed: false)).to eq(user)
end
end end
end end
context 'with an unconfirmed e-mail' do context "when committer_email is the user's secondary email" do
let(:user) { create(:user) } let!(:user) { create(:user) }
before do context 'when the user email is confirmed' do
create(:email, user: user, email: commit.committer_email) let!(:email) { create(:email, :confirmed, user: user, email: commit.committer_email) }
it 'returns the user' do
expect(commit.committer).to eq(user)
expect(commit.committer(confirmed: false)).to eq(user)
end
end end
it 'returns no user' do context 'when the user email is unconfirmed' do
expect(commit.committer).to be_nil let!(:email) { create(:email, user: user, email: commit.committer_email) }
end
it 'returns the user' do it 'does not return the user' do
expect(commit.committer(confirmed: false)).to eq(user) expect(commit.committer).to be_nil
expect(commit.committer(confirmed: false)).to be_nil
end
end end
end end
end end

View file

@ -121,6 +121,38 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end end
end end
end end
describe 'before_validation :reset_token' do
context 'when a token was previously set' do
subject { create(:project_error_tracking_setting, project: project) }
it 'resets token if url changed' do
subject.api_url = 'http://sentry.com/api/0/projects/org-slug/proj-slug/'
expect(subject).not_to be_valid
expect(subject.token).to be_nil
end
it "does not reset token if new url is set together with the same token" do
subject.api_url = 'http://sentrytest.com/api/0/projects/org-slug/proj-slug/'
current_token = subject.token
subject.token = current_token
expect(subject).to be_valid
expect(subject.token).to eq(current_token)
expect(subject.api_url).to eq('http://sentrytest.com/api/0/projects/org-slug/proj-slug/')
end
it 'does not reset token if new url is set together with a new token' do
subject.api_url = 'http://sentrytest.com/api/0/projects/org-slug/proj-slug/'
subject.token = 'token'
expect(subject).to be_valid
expect(subject.token).to eq('token')
expect(subject.api_url).to eq('http://sentrytest.com/api/0/projects/org-slug/proj-slug/')
end
end
end
end end
describe '.extract_sentry_external_url' do describe '.extract_sentry_external_url' do

View file

@ -86,4 +86,38 @@ RSpec.describe GrafanaIntegration do
end end
end end
end end
describe 'Callbacks' do
describe 'before_validation :reset_token' do
context 'when a token was previously set' do
subject(:grafana_integration) { create(:grafana_integration) }
it 'resets token if url changed' do
grafana_integration.grafana_url = 'http://gitlab1.com'
expect(grafana_integration).not_to be_valid
expect(grafana_integration.send(:token)).to be_nil
end
it "does not reset token if new url is set together with the same token" do
grafana_integration.grafana_url = 'http://gitlab_edited.com'
current_token = grafana_integration.send(:token)
grafana_integration.token = current_token
expect(grafana_integration).to be_valid
expect(grafana_integration.send(:token)).to eq(current_token)
expect(grafana_integration.grafana_url).to eq('http://gitlab_edited.com')
end
it 'does not reset token if new url is set together with a new token' do
grafana_integration.grafana_url = 'http://gitlab_edited.com'
grafana_integration.token = 'token'
expect(grafana_integration).to be_valid
expect(grafana_integration.send(:token)).to eq('token')
expect(grafana_integration.grafana_url).to eq('http://gitlab_edited.com')
end
end
end
end
end end

View file

@ -30,15 +30,12 @@ RSpec.describe WebHookLog do
end end
describe '#save' do describe '#save' do
let(:web_hook_log) { build(:web_hook_log, url: url) }
let(:url) { 'http://example.com' }
subject { web_hook_log.save! }
it { is_expected.to eq(true) }
context 'with basic auth credentials' do context 'with basic auth credentials' do
let(:url) { 'http://test:123@example.com'} let(:web_hook_log) { build(:web_hook_log, url: 'http://test:123@example.com') }
subject { web_hook_log.save! }
it { is_expected.to eq(true) }
it 'obfuscates the basic auth credentials' do it 'obfuscates the basic auth credentials' do
subject subject
@ -46,6 +43,30 @@ RSpec.describe WebHookLog do
expect(web_hook_log.url).to eq('http://*****:*****@example.com') expect(web_hook_log.url).to eq('http://*****:*****@example.com')
end end
end end
context 'with author email' do
let(:author) { create(:user) }
let(:web_hook_log) { create(:web_hook_log, request_data: data) }
let(:data) do
{
commit: {
author: {
name: author.name,
email: author.email
}
}
}.deep_stringify_keys
end
it "redacts author's email" do
expect(web_hook_log.request_data['commit']).to match a_hash_including(
'author' => {
'name' => author.name,
'email' => _('[REDACTED]')
}
)
end
end
end end
describe '.delete_batch_for' do describe '.delete_batch_for' do

View file

@ -5,7 +5,17 @@ require 'spec_helper'
RSpec.describe Integrations::Campfire do RSpec.describe Integrations::Campfire do
include StubRequests include StubRequests
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { described_class.new }
end
describe 'Validations' do describe 'Validations' do
it { is_expected.to validate_numericality_of(:room).is_greater_than(0).only_integer }
it { is_expected.to validate_length_of(:subdomain).is_at_most(63) }
it { is_expected.to allow_value("foo").for(:subdomain) }
it { is_expected.not_to allow_value("foo.bar").for(:subdomain) }
it { is_expected.not_to allow_value("foo.bar/#").for(:subdomain) }
context 'when integration is active' do context 'when integration is active' do
before do before do
subject.active = true subject.active = true

View file

@ -7,6 +7,10 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
subject(:integration) { described_class.new } subject(:integration) { described_class.new }
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { subject }
end
describe 'validations' do describe 'validations' do
context 'active' do context 'active' do
before do before do

View file

@ -12,6 +12,7 @@ RSpec.describe Integrations::Jira do
let(:api_url) { 'http://api-jira.example.com' } let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' } let(:username) { 'jira-username' }
let(:password) { 'jira-password' } let(:password) { 'jira-password' }
let(:project_key) { nil }
let(:transition_id) { 'test27' } let(:transition_id) { 'test27' }
let(:server_info_results) { { 'deploymentType' => 'Cloud' } } let(:server_info_results) { { 'deploymentType' => 'Cloud' } }
let(:jira_integration) do let(:jira_integration) do
@ -19,7 +20,8 @@ RSpec.describe Integrations::Jira do
project: project, project: project,
url: url, url: url,
username: username, username: username,
password: password password: password,
project_key: project_key
) )
end end
@ -533,6 +535,22 @@ RSpec.describe Integrations::Jira do
expect(WebMock).to have_requested(:get, issue_url) expect(WebMock).to have_requested(:get, issue_url)
end end
end end
context 'with restricted restrict_project_key option' do
subject(:find_issue) { jira_integration.find_issue(issue_key, restrict_project_key: true) }
it { is_expected.to eq(nil) }
context 'and project_key matches' do
let(:project_key) { 'JIRA' }
it 'calls the Jira API to get the issue' do
find_issue
expect(WebMock).to have_requested(:get, issue_url)
end
end
end
end end
describe '#close_issue' do describe '#close_issue' do

View file

@ -29,6 +29,10 @@ RSpec.describe Integrations::Packagist do
let(:hook_url) { "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" } let(:hook_url) { "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" }
end end
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { described_class.new(packagist_params) }
end
describe '#execute' do describe '#execute' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }

View file

@ -9,6 +9,31 @@ RSpec.describe Integrations::Zentao do
let(:zentao_product_xid) { '3' } let(:zentao_product_xid) { '3' }
let(:zentao_integration) { create(:zentao_integration) } let(:zentao_integration) { create(:zentao_integration) }
it_behaves_like Integrations::ResetSecretFields do
let(:integration) { zentao_integration }
end
describe 'set_default_data' do
let(:project) { create(:project, :repository) }
context 'when gitlab.yml was initialized' do
it 'is prepopulated with the settings' do
settings = {
'zentao' => {
'url' => 'http://zentao.sample/projects/project_a',
'api_url' => 'http://zentao.sample/api'
}
}
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
integration = project.create_zentao_integration(active: true)
expect(integration.url).to eq('http://zentao.sample/projects/project_a')
expect(integration.api_url).to eq('http://zentao.sample/api')
end
end
end
describe '#create' do describe '#create' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:params) do let(:params) do

View file

@ -5617,6 +5617,7 @@ RSpec.describe Project, factory_default: :keep do
let(:import_state) { create(:import_state, project: project) } let(:import_state) { create(:import_state, project: project) }
it 'runs the correct hooks' do it 'runs the correct hooks' do
expect(project.repository).to receive(:remove_prohibited_branches)
expect(project.repository).to receive(:expire_content_cache) expect(project.repository).to receive(:expire_content_cache)
expect(project.wiki.repository).to receive(:expire_content_cache) expect(project.wiki.repository).to receive(:expire_content_cache)
expect(import_state).to receive(:finish) expect(import_state).to receive(:finish)

View file

@ -3360,4 +3360,62 @@ RSpec.describe Repository do
end end
end end
end end
describe '#remove_prohibited_branches' do
let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759d597' }
before do
allow(repository.raw_repository).to receive(:branch_names).and_return([branch_name])
end
context 'when prohibited branch exists' do
it 'deletes prohibited branch' do
expect(repository.raw_repository).to receive(:delete_branch).with(branch_name)
repository.remove_prohibited_branches
end
end
shared_examples 'does not delete branch' do
it 'returns without removing the branch' do
expect(repository.raw_repository).not_to receive(:delete_branch)
repository.remove_prohibited_branches
end
end
context 'when branch name is 40-characters long but not hexadecimal' do
let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759d59s' }
include_examples 'does not delete branch'
end
context 'when branch name is hexadecimal' do
context 'when branch name is less than 40-characters long' do
let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759d' }
include_examples 'does not delete branch'
end
context 'when branch name is more than 40-characters long' do
let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759dfdfd' }
include_examples 'does not delete branch'
end
end
context 'when prohibited branch does not exist' do
let(:branch_name) { 'main' }
include_examples 'does not delete branch'
end
context 'when raw repository does not exist' do
before do
allow(repository).to receive(:exists?).and_return(false)
end
include_examples 'does not delete branch'
end
end
end end

View file

@ -36,8 +36,6 @@ RSpec.describe Snippet do
it { is_expected.to validate_presence_of(:content) } it { is_expected.to validate_presence_of(:content) }
it { is_expected.to validate_inclusion_of(:visibility_level).in_array(Gitlab::VisibilityLevel.values) }
it do it do
allow(Gitlab::CurrentSettings).to receive(:snippet_size_limit).and_return(1) allow(Gitlab::CurrentSettings).to receive(:snippet_size_limit).and_return(1)

View file

@ -475,4 +475,14 @@ RSpec.describe Todo do
it { is_expected.to contain_exactly(user1.id, user2.id) } it { is_expected.to contain_exactly(user1.id, user2.id) }
end end
describe '.for_internal_notes' do
it 'returns todos created from internal notes' do
internal_note = create(:note, confidential: true )
todo = create(:todo, note: internal_note)
create(:todo)
expect(described_class.for_internal_notes).to contain_exactly(todo)
end
end
end end

View file

@ -2634,6 +2634,14 @@ RSpec.describe User do
expect(described_class.find_by_any_email(private_email, confirmed: true)).to eq(user) expect(described_class.find_by_any_email(private_email, confirmed: true)).to eq(user)
end end
it 'finds user through private commit email when user is unconfirmed' do
user = create(:user, :unconfirmed)
private_email = user.private_commit_email
expect(described_class.find_by_any_email(private_email)).to eq(user)
expect(described_class.find_by_any_email(private_email, confirmed: true)).to eq(user)
end
it 'finds by primary email' do it 'finds by primary email' do
user = create(:user, email: 'foo@example.com') user = create(:user, email: 'foo@example.com')
@ -2641,6 +2649,13 @@ RSpec.describe User do
expect(described_class.find_by_any_email(user.email, confirmed: true)).to eq user expect(described_class.find_by_any_email(user.email, confirmed: true)).to eq user
end end
it 'finds by primary email when user is unconfirmed according to confirmed argument' do
user = create(:user, :unconfirmed, email: 'foo@example.com')
expect(described_class.find_by_any_email(user.email)).to eq user
expect(described_class.find_by_any_email(user.email, confirmed: true)).to be_nil
end
it 'finds by uppercased email' do it 'finds by uppercased email' do
user = create(:user, email: 'foo@example.com') user = create(:user, email: 'foo@example.com')
@ -2649,35 +2664,47 @@ RSpec.describe User do
end end
context 'finds by secondary email' do context 'finds by secondary email' do
let(:user) { email.user } context 'when primary email is confirmed' do
let(:user) { email.user }
context 'primary email confirmed' do context 'when secondary email is confirmed' do
context 'secondary email confirmed' do
let!(:email) { create(:email, :confirmed, email: 'foo@example.com') } let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
it 'finds user respecting the confirmed flag' do it 'finds user' do
expect(described_class.find_by_any_email(email.email)).to eq user expect(described_class.find_by_any_email(email.email)).to eq user
expect(described_class.find_by_any_email(email.email, confirmed: true)).to eq user expect(described_class.find_by_any_email(email.email, confirmed: true)).to eq user
end end
end end
context 'secondary email not confirmed' do context 'when secondary email is unconfirmed' do
let!(:email) { create(:email, email: 'foo@example.com') } let!(:email) { create(:email, email: 'foo@example.com') }
it 'finds user respecting the confirmed flag' do it 'does not find user' do
expect(described_class.find_by_any_email(email.email)).to eq user expect(described_class.find_by_any_email(email.email)).to be_nil
expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
end end
end end
end end
context 'primary email not confirmed' do context 'when primary email is unconfirmed' do
let(:user) { create(:user, :unconfirmed) } let(:user) { create(:user, :unconfirmed) }
let!(:email) { create(:email, :confirmed, user: user, email: 'foo@example.com') }
it 'finds user respecting the confirmed flag' do context 'when secondary email is confirmed' do
expect(described_class.find_by_any_email(email.email)).to eq user let!(:email) { create(:email, :confirmed, user: user, email: 'foo@example.com') }
expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
it 'finds user according to confirmed argument' do
expect(described_class.find_by_any_email(email.email)).to eq user
expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
end
end
context 'when secondary email is unconfirmed' do
let!(:email) { create(:email, user: user, email: 'foo@example.com') }
it 'does not find user' do
expect(described_class.find_by_any_email(email.email)).to be_nil
expect(described_class.find_by_any_email(email.email, confirmed: true)).to be_nil
end
end end
end end
end end
@ -2685,13 +2712,6 @@ RSpec.describe User do
it 'returns nil when nothing found' do it 'returns nil when nothing found' do
expect(described_class.find_by_any_email('')).to be_nil expect(described_class.find_by_any_email('')).to be_nil
end end
it 'returns nil when user is not confirmed' do
user = create(:user, :unconfirmed, email: 'foo@example.com')
expect(described_class.find_by_any_email(user.email, confirmed: false)).to eq(user)
expect(described_class.find_by_any_email(user.email, confirmed: true)).to be_nil
end
end end
describe '.by_any_email' do describe '.by_any_email' do
@ -2700,32 +2720,99 @@ RSpec.describe User do
.to be_a_kind_of(ActiveRecord::Relation) .to be_a_kind_of(ActiveRecord::Relation)
end end
it 'returns a relation of users' do it 'returns empty relation of users when nothing found' do
user = create(:user) expect(described_class.by_any_email('')).to be_empty
expect(described_class.by_any_email(user.email)).to eq([user])
end end
it 'returns a relation of users for confirmed users' do it 'returns a relation of users for confirmed primary emails' do
user = create(:user) user = create(:user)
expect(described_class.by_any_email(user.email, confirmed: true)).to eq([user]) expect(described_class.by_any_email(user.email)).to match_array([user])
expect(described_class.by_any_email(user.email, confirmed: true)).to match_array([user])
end end
it 'finds user through a private commit email' do it 'returns a relation of users for unconfirmed primary emails according to confirmed argument' do
user = create(:user, :unconfirmed)
expect(described_class.by_any_email(user.email)).to match_array([user])
expect(described_class.by_any_email(user.email, confirmed: true)).to be_empty
end
it 'finds users through private commit emails' do
user = create(:user) user = create(:user)
private_email = user.private_commit_email private_email = user.private_commit_email
expect(described_class.by_any_email(private_email)).to eq([user]) expect(described_class.by_any_email(private_email)).to match_array([user])
expect(described_class.by_any_email(private_email, confirmed: true)).to eq([user]) expect(described_class.by_any_email(private_email, confirmed: true)).to match_array([user])
end
it 'finds unconfirmed users through private commit emails' do
user = create(:user, :unconfirmed)
private_email = user.private_commit_email
expect(described_class.by_any_email(private_email)).to match_array([user])
expect(described_class.by_any_email(private_email, confirmed: true)).to match_array([user])
end end
it 'finds user through a private commit email in an array' do it 'finds user through a private commit email in an array' do
user = create(:user) user = create(:user)
private_email = user.private_commit_email private_email = user.private_commit_email
expect(described_class.by_any_email([private_email])).to eq([user]) expect(described_class.by_any_email([private_email])).to match_array([user])
expect(described_class.by_any_email([private_email], confirmed: true)).to eq([user]) expect(described_class.by_any_email([private_email], confirmed: true)).to match_array([user])
end
it 'finds by uppercased email' do
user = create(:user, email: 'foo@example.com')
expect(described_class.by_any_email(user.email.upcase)).to match_array([user])
expect(described_class.by_any_email(user.email.upcase, confirmed: true)).to match_array([user])
end
context 'finds by secondary email' do
context 'when primary email is confirmed' do
let(:user) { email.user }
context 'when secondary email is confirmed' do
let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
it 'finds user' do
expect(described_class.by_any_email(email.email)).to match_array([user])
expect(described_class.by_any_email(email.email, confirmed: true)).to match_array([user])
end
end
context 'when secondary email is unconfirmed' do
let!(:email) { create(:email, email: 'foo@example.com') }
it 'does not find user' do
expect(described_class.by_any_email(email.email)).to be_empty
expect(described_class.by_any_email(email.email, confirmed: true)).to be_empty
end
end
end
context 'when primary email is unconfirmed' do
let(:user) { create(:user, :unconfirmed) }
context 'when secondary email is confirmed' do
let!(:email) { create(:email, :confirmed, user: user, email: 'foo@example.com') }
it 'finds user according to confirmed argument' do
expect(described_class.by_any_email(email.email)).to match_array([user])
expect(described_class.by_any_email(email.email, confirmed: true)).to be_empty
end
end
context 'when secondary email is unconfirmed' do
let!(:email) { create(:email, user: user, email: 'foo@example.com') }
it 'does not find user' do
expect(described_class.by_any_email(email.email)).to be_empty
expect(described_class.by_any_email(email.email, confirmed: true)).to be_empty
end
end
end
end end
end end
@ -2739,7 +2826,10 @@ RSpec.describe User do
let_it_be(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@example.com') } let_it_be(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@example.com') }
let_it_be(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@example.com') } let_it_be(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@example.com') }
let_it_be(:email) { create(:email, user: user, email: 'alias@example.com') } let_it_be(:unconfirmed_user) { create(:user, :unconfirmed, name: 'not verified', username: 'notverified') }
let_it_be(:unconfirmed_secondary_email) { create(:email, user: user, email: 'alias@example.com') }
let_it_be(:confirmed_secondary_email) { create(:email, :confirmed, user: user, email: 'alias2@example.com') }
describe 'name user and email relative ordering' do describe 'name user and email relative ordering' do
let_it_be(:named_alexander) { create(:user, name: 'Alexander Person', username: 'abcd', email: 'abcd@example.com') } let_it_be(:named_alexander) { create(:user, name: 'Alexander Person', username: 'abcd', email: 'abcd@example.com') }
@ -2797,16 +2887,25 @@ RSpec.describe User do
it 'does not return users with a matching private email' do it 'does not return users with a matching private email' do
expect(described_class.search(user.email)).to be_empty expect(described_class.search(user.email)).to be_empty
expect(described_class.search(email.email)).to be_empty expect(described_class.search(unconfirmed_secondary_email.email)).to be_empty
expect(described_class.search(confirmed_secondary_email.email)).to be_empty
end end
context 'with private emails search' do context 'with private emails search' do
it 'returns users with matching private email' do it 'returns users with matching private primary email' do
expect(described_class.search(user.email, with_private_emails: true)).to match_array([user]) expect(described_class.search(user.email, with_private_emails: true)).to match_array([user])
end end
it 'returns users with matching private secondary email' do it 'returns users with matching private unconfirmed primary email' do
expect(described_class.search(email.email, with_private_emails: true)).to match_array([user]) expect(described_class.search(unconfirmed_user.email, with_private_emails: true)).to match_array([unconfirmed_user])
end
it 'returns users with matching private confirmed secondary email' do
expect(described_class.search(confirmed_secondary_email.email, with_private_emails: true)).to match_array([user])
end
it 'does not return users with matching private unconfirmed secondary email' do
expect(described_class.search(unconfirmed_secondary_email.email, with_private_emails: true)).to be_empty
end end
end end
end end
@ -3049,47 +3148,108 @@ RSpec.describe User do
describe '#accept_pending_invitations!' do describe '#accept_pending_invitations!' do
let(:user) { create(:user, email: 'user@email.com') } let(:user) { create(:user, email: 'user@email.com') }
let(:confirmed_secondary_email) { create(:email, :confirmed, email: 'confirmedsecondary@example.com', user: user) }
let(:unconfirmed_secondary_email) { create(:email, email: 'unconfirmedsecondary@example.com', user: user) }
let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) } let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) }
let!(:group_member_invite) { create(:group_member, :invited, invite_email: user.email) } let!(:group_member_invite) { create(:group_member, :invited, invite_email: user.email) }
let!(:external_project_member_invite) { create(:project_member, :invited, invite_email: 'external@email.com') } let!(:external_project_member_invite) { create(:project_member, :invited, invite_email: 'external@email.com') }
let!(:external_group_member_invite) { create(:group_member, :invited, invite_email: 'external@email.com') } let!(:external_group_member_invite) { create(:group_member, :invited, invite_email: 'external@email.com') }
let!(:project_member_invite_via_confirmed_secondary_email) { create(:project_member, :invited, invite_email: confirmed_secondary_email.email) }
let!(:group_member_invite_via_confirmed_secondary_email) { create(:group_member, :invited, invite_email: confirmed_secondary_email.email) }
let!(:project_member_invite_via_unconfirmed_secondary_email) { create(:project_member, :invited, invite_email: unconfirmed_secondary_email.email) }
let!(:group_member_invite_via_unconfirmed_secondary_email) { create(:group_member, :invited, invite_email: unconfirmed_secondary_email.email) }
it 'accepts all the user members pending invitations and returns the accepted_members' do it 'accepts all the user members pending invitations and returns the accepted_members' do
accepted_members = user.accept_pending_invitations! accepted_members = user.accept_pending_invitations!
expect(accepted_members).to match_array([project_member_invite, group_member_invite]) expect(accepted_members).to match_array(
[
project_member_invite,
group_member_invite,
project_member_invite_via_confirmed_secondary_email,
group_member_invite_via_confirmed_secondary_email
]
)
expect(group_member_invite.reload).not_to be_invite expect(group_member_invite.reload).not_to be_invite
expect(project_member_invite.reload).not_to be_invite expect(project_member_invite.reload).not_to be_invite
expect(external_project_member_invite.reload).to be_invite expect(external_project_member_invite.reload).to be_invite
expect(external_group_member_invite.reload).to be_invite expect(external_group_member_invite.reload).to be_invite
expect(project_member_invite_via_confirmed_secondary_email.reload).not_to be_invite
expect(group_member_invite_via_confirmed_secondary_email.reload).not_to be_invite
expect(project_member_invite_via_unconfirmed_secondary_email.reload).to be_invite
expect(group_member_invite_via_unconfirmed_secondary_email.reload).to be_invite
end end
end end
describe '#all_emails' do describe '#all_emails' do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:email_confirmed) { create :email, user: user, confirmed_at: Time.current } let!(:unconfirmed_secondary_email) { create(:email, user: user) }
let!(:email_unconfirmed) { create :email, user: user } let!(:confirmed_secondary_email) { create(:email, :confirmed, user: user) }
it 'returns all emails' do
expect(user.all_emails).to contain_exactly(
user.email,
user.private_commit_email,
confirmed_secondary_email.email
)
end
context 'when the primary email is confirmed' do
it 'includes the primary email' do
expect(user.all_emails).to include(user.email)
end
end
context 'when the primary email is unconfirmed' do
let!(:user) { create(:user, :unconfirmed) }
it 'includes the primary email' do
expect(user.all_emails).to include(user.email)
end
end
context 'when the primary email is temp email for oauth' do
let!(:user) { create(:omniauth_user, :unconfirmed, email: 'temp-email-for-oauth-user@gitlab.localhost') }
it 'does not include the primary email' do
expect(user.all_emails).not_to include(user.email)
end
end
context 'when `include_private_email` is true' do context 'when `include_private_email` is true' do
it 'returns all emails' do it 'includes the private commit email' do
expect(user.reload.all_emails).to contain_exactly( expect(user.all_emails).to include(user.private_commit_email)
user.email,
user.private_commit_email,
email_unconfirmed.email,
email_confirmed.email
)
end end
end end
context 'when `include_private_email` is false' do context 'when `include_private_email` is false' do
it 'does not include the private commit email' do it 'does not include the private commit email' do
expect(user.reload.all_emails(include_private_email: false)).to contain_exactly( expect(user.all_emails(include_private_email: false)).not_to include(
user.email, user.private_commit_email
email_unconfirmed.email,
email_confirmed.email
) )
end end
end end
context 'when the secondary email is confirmed' do
it 'includes the secondary email' do
expect(user.all_emails).to include(confirmed_secondary_email.email)
end
end
context 'when the secondary email is unconfirmed' do
it 'does not include the secondary email' do
expect(user.all_emails).not_to include(unconfirmed_secondary_email.email)
end
end
end end
describe '#verified_emails' do describe '#verified_emails' do

View file

@ -7,6 +7,7 @@ RSpec.describe API::Invitations do
let_it_be(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
let_it_be(:access_requester) { create(:user) } let_it_be(:access_requester) { create(:user) }
let_it_be(:stranger) { create(:user) } let_it_be(:stranger) { create(:user) }
let_it_be(:unconfirmed_stranger) { create(:user, :unconfirmed) }
let(:email) { 'email1@example.com' } let(:email) { 'email1@example.com' }
let(:email2) { 'email2@example.com' } let(:email2) { 'email2@example.com' }
@ -78,6 +79,46 @@ RSpec.describe API::Invitations do
end.to change { source.members.invite.count }.by(1) end.to change { source.members.invite.count }.by(1)
end end
it 'adds a new member by confirmed primary email' do
expect do
post invitations_url(source, maintainer),
params: { email: stranger.email, access_level: Member::DEVELOPER }
expect(response).to have_gitlab_http_status(:created)
end.to change { source.members.non_invite.count }.by(1)
end
it 'adds a new member by unconfirmed primary email' do
expect do
post invitations_url(source, maintainer),
params: { email: unconfirmed_stranger.email, access_level: Member::DEVELOPER }
expect(response).to have_gitlab_http_status(:created)
end.to change { source.members.non_invite.count }.by(1)
end
it 'adds a new member by confirmed secondary email' do
secondary_email = create(:email, :confirmed, email: 'secondary@example.com', user: stranger)
expect do
post invitations_url(source, maintainer),
params: { email: secondary_email.email, access_level: Member::DEVELOPER }
expect(response).to have_gitlab_http_status(:created)
end.to change { source.members.non_invite.count }.by(1)
end
it 'adds a new member as an invite for unconfirmed secondary email' do
secondary_email = create(:email, email: 'secondary@example.com', user: stranger)
expect do
post invitations_url(source, maintainer),
params: { email: secondary_email.email, access_level: Member::DEVELOPER }
expect(response).to have_gitlab_http_status(:created)
end.to change { source.members.invite.count }.by(1).and change { source.members.non_invite.count }.by(0)
end
it 'adds a new member by user_id' do it 'adds a new member by user_id' do
expect do expect do
post invitations_url(source, maintainer), post invitations_url(source, maintainer),

View file

@ -25,6 +25,40 @@ RSpec.describe 'OAuth tokens' do
end end
end end
context 'when 2FA enforced' do
let_it_be(:user) { create(:user, otp_grace_period_started_at: 1.day.ago) }
before do
stub_application_setting(require_two_factor_authentication: true)
end
context 'when grace period expired' do
before do
stub_application_setting(two_factor_grace_period: 0)
end
it 'does not create an access token' do
request_oauth_token(user, client_basic_auth_header(client))
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('invalid_grant')
end
end
context 'when grace period is not expired' do
before do
stub_application_setting(two_factor_grace_period: 72)
end
it 'creates an access token' do
request_oauth_token(user, client_basic_auth_header(client))
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['access_token']).not_to be_nil
end
end
end
context 'when user does not have 2FA enabled' do context 'when user does not have 2FA enabled' do
context 'when no client credentials provided' do context 'when no client credentials provided' do
it 'creates an access token' do it 'creates an access token' do

View file

@ -1198,6 +1198,81 @@ RSpec.describe API::Users do
end end
end end
context 'when user with a primary email exists' do
context 'when the primary email is confirmed' do
let!(:confirmed_user) { create(:user, email: 'foo@example.com') }
it 'returns 409 conflict error' do
expect do
post api('/users', admin),
params: {
name: 'foo',
email: confirmed_user.email,
password: 'password',
username: 'TEST'
}
end.to change { User.count }.by(0)
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']).to eq('Email has already been taken')
end
end
context 'when the primary email is unconfirmed' do
let!(:unconfirmed_user) { create(:user, :unconfirmed, email: 'foo@example.com') }
it 'returns 409 conflict error' do
expect do
post api('/users', admin),
params: {
name: 'foo',
email: unconfirmed_user.email,
password: 'password',
username: 'TEST'
}
end.to change { User.count }.by(0)
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']).to eq('Email has already been taken')
end
end
end
context 'when user with a secondary email exists' do
context 'when the secondary email is confirmed' do
let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
it 'returns 409 conflict error' do
expect do
post api('/users', admin),
params: {
name: 'foo',
email: email.email,
password: 'password',
username: 'TEST'
}
end.to change { User.count }.by(0)
expect(response).to have_gitlab_http_status(:conflict)
expect(json_response['message']).to eq('Email has already been taken')
end
end
context 'when the secondary email is unconfirmed' do
let!(:email) { create(:email, email: 'foo@example.com') }
it 'does not create user' do
expect do
post api('/users', admin),
params: {
name: 'foo',
email: email.email,
password: 'password',
username: 'TEST'
}
end.to change { User.count }.by(0)
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
context "scopes" do context "scopes" do
let(:user) { admin } let(:user) { admin }
let(:path) { '/users' } let(:path) { '/users' }
@ -1554,6 +1629,54 @@ RSpec.describe API::Users do
expect(@user.reload.username).to eq(@user.username) expect(@user.reload.username).to eq(@user.username)
end end
end end
context 'when user with a primary email exists' do
context 'when the primary email is confirmed' do
let!(:confirmed_user) { create(:user, email: 'foo@example.com') }
it 'returns 409 conflict error' do
put api("/users/#{user.id}", admin), params: { email: confirmed_user.email }
expect(response).to have_gitlab_http_status(:conflict)
expect(user.reload.email).not_to eq(confirmed_user.email)
end
end
context 'when the primary email is unconfirmed' do
let!(:unconfirmed_user) { create(:user, :unconfirmed, email: 'foo@example.com') }
it 'returns 409 conflict error' do
put api("/users/#{user.id}", admin), params: { email: unconfirmed_user.email }
expect(response).to have_gitlab_http_status(:conflict)
expect(user.reload.email).not_to eq(unconfirmed_user.email)
end
end
end
context 'when user with a secondary email exists' do
context 'when the secondary email is confirmed' do
let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
it 'returns 409 conflict error' do
put api("/users/#{user.id}", admin), params: { email: email.email }
expect(response).to have_gitlab_http_status(:conflict)
expect(user.reload.email).not_to eq(email.email)
end
end
context 'when the secondary email is unconfirmed' do
let!(:email) { create(:email, email: 'foo@example.com') }
it 'does not update email' do
put api("/users/#{user.id}", admin), params: { email: email.email }
expect(response).to have_gitlab_http_status(:bad_request)
expect(user.reload.email).not_to eq(email.email)
end
end
end
end end
describe "PUT /user/:id/credit_card_validation" do describe "PUT /user/:id/credit_card_validation" do
@ -2118,6 +2241,50 @@ RSpec.describe API::Users do
expect(json_response['confirmed_at']).not_to be_nil expect(json_response['confirmed_at']).not_to be_nil
end end
context 'when user with a primary email exists' do
context 'when the primary email is confirmed' do
let!(:confirmed_user) { create(:user, email: 'foo@example.com') }
it 'returns 400 error' do
post api("/users/#{user.id}/emails", admin), params: { email: confirmed_user.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when the primary email is unconfirmed' do
let!(:unconfirmed_user) { create(:user, :unconfirmed, email: 'foo@example.com') }
it 'returns 400 error' do
post api("/users/#{user.id}/emails", admin), params: { email: unconfirmed_user.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
context 'when user with a secondary email exists' do
context 'when the secondary email is confirmed' do
let!(:email) { create(:email, :confirmed, email: 'foo@example.com') }
it 'returns 400 error' do
post api("/users/#{user.id}/emails", admin), params: { email: email.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when the secondary email is unconfirmed' do
let!(:email) { create(:email, email: 'foo@example.com') }
it 'returns 400 error' do
post api("/users/#{user.id}/emails", admin), params: { email: email.email }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end end
describe 'GET /user/:id/emails' do describe 'GET /user/:id/emails' do

View file

@ -170,6 +170,24 @@ RSpec.describe BuildDetailsEntity do
expect(message).to include('could not retrieve the needed artifacts.') expect(message).to include('could not retrieve the needed artifacts.')
end end
end end
context 'when dependency contains invalid dependency names' do
invalid_name = 'XSS<a href=# data-disable-with="<img src=x onerror=alert(document.domain)>">'
let!(:test1) { create(:ci_build, :success, :expired, pipeline: pipeline, name: invalid_name, stage_idx: 0) }
let!(:build) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: { dependencies: [invalid_name] }) }
before do
build.pipeline.unlocked!
build.drop!(:missing_dependency_failure)
end
it { is_expected.to include(failure_reason: 'missing_dependency_failure') }
it 'escapes the invalid dependency names' do
escaped_name = html_escape(invalid_name)
expect(message).to include(escaped_name)
end
end
end end
context 'when a build has environment with latest deployment' do context 'when a build has environment with latest deployment' do

View file

@ -50,12 +50,8 @@ RSpec.describe Grafana::ProxyService do
describe '#execute' do describe '#execute' do
subject(:result) { service.execute } subject(:result) { service.execute }
context 'when grafana integration is not configured' do shared_examples 'missing proxy support' do
before do it 'returns API not supported error' do
allow(project).to receive(:grafana_integration).and_return(nil)
end
it 'returns error' do
expect(result).to eq( expect(result).to eq(
status: :error, status: :error,
message: 'Proxy support for this API is not available currently' message: 'Proxy support for this API is not available currently'
@ -63,6 +59,40 @@ RSpec.describe Grafana::ProxyService do
end end
end end
context 'with unsupported proxy path' do
where(:proxy_path) do
%w[
/api/vl/query_range
api/vl/query_range/
api/vl/labels
api/v2/query_range
../../../org/users
]
end
with_them do
include_examples 'missing proxy support'
end
end
context 'with unsupported datasource_id' do
where(:datasource_id) do
['', '-1', '1str', 'str1', '../../1', '1/../..', "1\n1"]
end
with_them do
include_examples 'missing proxy support'
end
end
context 'when grafana integration is not configured' do
before do
allow(project).to receive(:grafana_integration).and_return(nil)
end
include_examples 'missing proxy support'
end
context 'with caching', :use_clean_rails_memory_store_caching do context 'with caching', :use_clean_rails_memory_store_caching do
context 'when value not present in cache' do context 'when value not present in cache' do
it 'returns nil' do it 'returns nil' do

View file

@ -35,6 +35,20 @@ RSpec.describe Groups::DestroyService do
it { expect(NotificationSetting.unscoped.all).not_to include(notification_setting) } it { expect(NotificationSetting.unscoped.all).not_to include(notification_setting) }
end end
context 'bot tokens', :sidekiq_might_not_need_inline do
it 'removes group bot', :aggregate_failures do
bot = create(:user, :project_bot)
group.add_developer(bot)
token = create(:personal_access_token, user: bot)
destroy_group(group, user, async)
expect(PersonalAccessToken.find_by(id: token.id)).to be_nil
expect(User.find_by(id: bot.id)).to be_nil
expect(User.find_by(id: user.id)).not_to be_nil
end
end
context 'mattermost team', :sidekiq_might_not_need_inline do context 'mattermost team', :sidekiq_might_not_need_inline do
let!(:chat_team) { create(:chat_team, namespace: group) } let!(:chat_team) { create(:chat_team, namespace: group) }

View file

@ -242,6 +242,69 @@ RSpec.describe Groups::UpdateService do
end end
end end
context 'when user is not group owner' do
context 'when group is private' do
before do
private_group.add_maintainer(user)
end
it 'does not update the group to public' do
result = described_class.new(private_group, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC).execute
expect(result).to eq(false)
expect(private_group.errors.count).to eq(1)
expect(private_group).to be_private
end
it 'does not update the group to public with tricky value' do
result = described_class.new(private_group, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC.to_s + 'r').execute
expect(result).to eq(false)
expect(private_group.errors.count).to eq(1)
expect(private_group).to be_private
end
end
context 'when group is public' do
before do
public_group.add_maintainer(user)
end
it 'does not update the group to private' do
result = described_class.new(public_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE).execute
expect(result).to eq(false)
expect(public_group.errors.count).to eq(1)
expect(public_group).to be_public
end
it 'does not update the group to private with invalid string value' do
result = described_class.new(public_group, user, visibility_level: 'invalid').execute
expect(result).to eq(false)
expect(public_group.errors.count).to eq(1)
expect(public_group).to be_public
end
it 'does not update the group to private with valid string value' do
result = described_class.new(public_group, user, visibility_level: 'private').execute
expect(result).to eq(false)
expect(public_group.errors.count).to eq(1)
expect(public_group).to be_public
end
# See https://gitlab.com/gitlab-org/gitlab/-/issues/359910
it 'does not update the group to private because of Active Record typecasting' do
result = described_class.new(public_group, user, visibility_level: 'public').execute
expect(result).to eq(true)
expect(public_group.errors.count).to eq(0)
expect(public_group).to be_public
end
end
end
context 'when updating #emails_disabled' do context 'when updating #emails_disabled' do
let(:service) { described_class.new(internal_group, user, emails_disabled: true) } let(:service) { described_class.new(internal_group, user, emails_disabled: true) }

View file

@ -30,8 +30,8 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_
end end
end end
context 'when email belongs to an existing user as a secondary email' do context 'when email belongs to an existing user as a confirmed secondary email' do
let(:secondary_email) { create(:email, email: 'secondary@example.com', user: project_user) } let(:secondary_email) { create(:email, :confirmed, email: 'secondary@example.com', user: project_user) }
let(:params) { { email: secondary_email.email } } let(:params) { { email: secondary_email.email } }
it 'adds an existing user to members', :aggregate_failures do it 'adds an existing user to members', :aggregate_failures do
@ -42,6 +42,18 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_
end end
end end
context 'when email belongs to an existing user as an unconfirmed secondary email' do
let(:unconfirmed_secondary_email) { create(:email, email: 'secondary@example.com', user: project_user) }
let(:params) { { email: unconfirmed_secondary_email.email } }
it 'does not link the email with any user and successfully creates a member as an invite for that email' do
expect_to_create_members(count: 1)
expect(result[:status]).to eq(:success)
expect(project.users).not_to include project_user
expect(project.members.last).to be_invite
end
end
context 'when invites are passed as array' do context 'when invites are passed as array' do
context 'with emails' do context 'with emails' do
let(:params) { { email: %w[email@example.org email2@example.org] } } let(:params) { { email: %w[email@example.org email2@example.org] } }
@ -291,6 +303,19 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_
end end
end end
context 'with unconfirmed primary email' do
let_it_be(:unconfirmed_user) { create(:user, :unconfirmed) }
let(:params) { { email: unconfirmed_user.email } }
it 'adds an existing user to members' do
expect_to_create_members(count: 1)
expect(result[:status]).to eq(:success)
expect(project.users).to include unconfirmed_user
expect(project.members.last).not_to be_invite
end
end
context 'with user_id' do context 'with user_id' do
let(:params) { { user_id: project_user.id } } let(:params) { { user_id: project_user.id } }
@ -375,8 +400,8 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_
expect(existing_member.reset.access_level).to eq ProjectMember::MAINTAINER expect(existing_member.reset.access_level).to eq ProjectMember::MAINTAINER
end end
context 'when email belongs to an existing user as a secondary email' do context 'when email belongs to an existing user as a confirmed secondary email' do
let(:secondary_email) { create(:email, email: 'secondary@example.com', user: existing_member.user) } let(:secondary_email) { create(:email, :confirmed, email: 'secondary@example.com', user: existing_member.user) }
let(:params) { { email: "#{secondary_email.email}" } } let(:params) { { email: "#{secondary_email.email}" } }
it 'allows re-invite to an already invited email' do it 'allows re-invite to an already invited email' do

View file

@ -306,6 +306,11 @@ RSpec.describe Projects::Operations::UpdateService do
let(:params) do let(:params) do
{ {
error_tracking_setting_attributes: { error_tracking_setting_attributes: {
api_host: 'https://sentrytest.gitlab.com/',
project: {
slug: 'sentry-project',
organization_slug: 'sentry-org'
},
enabled: false, enabled: false,
token: '*' * 8 token: '*' * 8
} }
@ -313,7 +318,7 @@ RSpec.describe Projects::Operations::UpdateService do
end end
before do before do
create(:project_error_tracking_setting, project: project, token: 'token') create(:project_error_tracking_setting, project: project, token: 'token', api_url: 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/')
end end
it 'does not update token' do it 'does not update token' do

View file

@ -120,6 +120,65 @@ RSpec.describe Projects::UpdateService do
end end
end end
context 'when user is not project owner' do
let_it_be(:maintainer) { create(:user) }
before do
project.add_maintainer(maintainer)
end
context 'when project is private' do
it 'does not update the project to public' do
result = update_project(project, maintainer, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_private
end
it 'does not update the project to public with tricky value' do
result = update_project(project, maintainer, visibility_level: Gitlab::VisibilityLevel::PUBLIC.to_s + 'r')
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_private
end
end
context 'when project is public' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
it 'does not update the project to private' do
result = update_project(project, maintainer, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_public
end
it 'does not update the project to private with invalid string value' do
result = update_project(project, maintainer, visibility_level: 'invalid')
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_public
end
it 'does not update the project to private with valid string value' do
result = update_project(project, maintainer, visibility_level: 'private')
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_public
end
# See https://gitlab.com/gitlab-org/gitlab/-/issues/359910
it 'does not update the project to private because of Active Record typecasting' do
result = update_project(project, maintainer, visibility_level: 'public')
expect(result).to eq({ status: :success })
expect(project).to be_public
end
end
end
context 'when updating shared runners' do context 'when updating shared runners' do
context 'can enable shared runners' do context 'can enable shared runners' do
let(:group) { create(:group, shared_runners_enabled: true) } let(:group) { create(:group, shared_runners_enabled: true) }

View file

@ -3,21 +3,24 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Todos::Destroy::EntityLeaveService do RSpec.describe Todos::Destroy::EntityLeaveService do
let_it_be(:user, reload: true) { create(:user) } let_it_be(:user, reload: true) { create(:user) }
let_it_be(:user2, reload: true) { create(:user) } let_it_be(:user2, reload: true) { create(:user) }
let_it_be_with_refind(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, :private, group: group) }
let(:group) { create(:group, :private) } let(:issue) { create(:issue, project: project) }
let(:project) { create(:project, :private, group: group) } let(:issue_c) { create(:issue, project: project, confidential: true) }
let(:issue) { create(:issue, project: project) } let!(:todo_group_user) { create(:todo, user: user, group: group) }
let(:issue_c) { create(:issue, project: project, confidential: true) } let!(:todo_group_user2) { create(:todo, user: user2, group: group) }
let!(:todo_group_user) { create(:todo, user: user, group: group) } let(:mr) { create(:merge_request, source_project: project) }
let!(:todo_group_user2) { create(:todo, user: user2, group: group) } let!(:todo_mr_user) { create(:todo, user: user, target: mr, project: project) }
let!(:todo_issue_user) { create(:todo, user: user, target: issue, project: project) }
let(:mr) { create(:merge_request, source_project: project) } let!(:todo_issue_c_user) { create(:todo, user: user, target: issue_c, project: project) }
let!(:todo_mr_user) { create(:todo, user: user, target: mr, project: project) }
let!(:todo_issue_user) { create(:todo, user: user, target: issue, project: project) }
let!(:todo_issue_c_user) { create(:todo, user: user, target: issue_c, project: project) }
let!(:todo_issue_c_user2) { create(:todo, user: user2, target: issue_c, project: project) } let!(:todo_issue_c_user2) { create(:todo, user: user2, target: issue_c, project: project) }
let(:internal_note) { create(:note, noteable: issue, project: project, confidential: true ) }
let!(:todo_for_internal_note) do
create(:todo, user: user, target: issue, project: project, note: internal_note)
end
shared_examples 'using different access permissions' do shared_examples 'using different access permissions' do
before do before do
@ -34,20 +37,28 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
it { does_not_remove_any_todos } it { does_not_remove_any_todos }
end end
shared_examples 'removes only confidential issues todos' do shared_examples 'removes confidential issues and internal notes todos' do
it { removes_only_confidential_issues_todos } it { removes_confidential_issues_and_internal_notes_todos }
end
shared_examples 'removes only internal notes todos' do
it { removes_only_internal_notes_todos }
end end
def does_not_remove_any_todos def does_not_remove_any_todos
expect { subject }.not_to change { Todo.count } expect { subject }.not_to change { Todo.count }
end end
def removes_only_confidential_issues_todos def removes_only_internal_notes_todos
expect { subject }.to change { Todo.count }.from(6).to(5) expect { subject }.to change { Todo.count }.from(7).to(6)
end end
def removes_confidential_issues_and_merge_request_todos def removes_confidential_issues_and_internal_notes_todos
expect { subject }.to change { Todo.count }.from(6).to(4) expect { subject }.to change { Todo.count }.from(7).to(5)
end
def removes_confidential_issues_and_internal_notes_and_merge_request_todos
expect { subject }.to change { Todo.count }.from(7).to(4)
expect(user.todos).to match_array([todo_issue_user, todo_group_user]) expect(user.todos).to match_array([todo_issue_user, todo_group_user])
end end
@ -70,7 +81,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'when project is private' do context 'when project is private' do
context 'when user is not a member of the project' do context 'when user is not a member of the project' do
it 'removes project todos for the provided user' do it 'removes project todos for the provided user' do
expect { subject }.to change { Todo.count }.from(6).to(3) expect { subject }.to change { Todo.count }.from(7).to(3)
expect(user.todos).to match_array([todo_group_user]) expect(user.todos).to match_array([todo_group_user])
expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2]) expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2])
@ -81,11 +92,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
where(:group_access, :project_access, :method_name) do where(:group_access, :project_access, :method_name) do
[ [
[nil, :reporter, :does_not_remove_any_todos], [nil, :reporter, :does_not_remove_any_todos],
[nil, :guest, :removes_confidential_issues_and_merge_request_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos],
[:reporter, nil, :does_not_remove_any_todos], [:reporter, nil, :does_not_remove_any_todos],
[:guest, nil, :removes_confidential_issues_and_merge_request_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_and_merge_request_todos],
[:guest, :reporter, :does_not_remove_any_todos], [:guest, :reporter, :does_not_remove_any_todos],
[:guest, :guest, :removes_confidential_issues_and_merge_request_todos] [:guest, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos]
] ]
end end
@ -97,11 +108,12 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
# a private project in an internal/public group is valid # a private project in an internal/public group is valid
context 'when project is private in an internal/public group' do context 'when project is private in an internal/public group' do
let(:group) { create(:group, :internal) } let_it_be(:group) { create(:group, :internal) }
let_it_be(:project) { create(:project, :private, group: group) }
context 'when user is not a member of the project' do context 'when user is not a member of the project' do
it 'removes project todos for the provided user' do it 'removes project todos for the provided user' do
expect { subject }.to change { Todo.count }.from(6).to(3) expect { subject }.to change { Todo.count }.from(7).to(3)
expect(user.todos).to match_array([todo_group_user]) expect(user.todos).to match_array([todo_group_user])
expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2]) expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2])
@ -112,11 +124,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
where(:group_access, :project_access, :method_name) do where(:group_access, :project_access, :method_name) do
[ [
[nil, :reporter, :does_not_remove_any_todos], [nil, :reporter, :does_not_remove_any_todos],
[nil, :guest, :removes_confidential_issues_and_merge_request_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos],
[:reporter, nil, :does_not_remove_any_todos], [:reporter, nil, :does_not_remove_any_todos],
[:guest, nil, :removes_confidential_issues_and_merge_request_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_and_merge_request_todos],
[:guest, :reporter, :does_not_remove_any_todos], [:guest, :reporter, :does_not_remove_any_todos],
[:guest, :guest, :removes_confidential_issues_and_merge_request_todos] [:guest, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos]
] ]
end end
@ -142,7 +154,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'confidential issues' do context 'confidential issues' do
context 'when a user is not an author of confidential issue' do context 'when a user is not an author of confidential issue' do
it_behaves_like 'removes only confidential issues todos' it_behaves_like 'removes confidential issues and internal notes todos'
end end
context 'when a user is an author of confidential issue' do context 'when a user is an author of confidential issue' do
@ -150,7 +162,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
issue_c.update!(author: user) issue_c.update!(author: user)
end end
it_behaves_like 'does not remove any todos' it_behaves_like 'removes only internal notes todos'
end end
context 'when a user is an assignee of confidential issue' do context 'when a user is an assignee of confidential issue' do
@ -158,18 +170,18 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
issue_c.assignees << user issue_c.assignees << user
end end
it_behaves_like 'does not remove any todos' it_behaves_like 'removes only internal notes todos'
end end
context 'access permissions' do context 'access permissions' do
where(:group_access, :project_access, :method_name) do where(:group_access, :project_access, :method_name) do
[ [
[nil, :reporter, :does_not_remove_any_todos], [nil, :reporter, :does_not_remove_any_todos],
[nil, :guest, :removes_only_confidential_issues_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_todos],
[:reporter, nil, :does_not_remove_any_todos], [:reporter, nil, :does_not_remove_any_todos],
[:guest, nil, :removes_only_confidential_issues_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_todos],
[:guest, :reporter, :does_not_remove_any_todos], [:guest, :reporter, :does_not_remove_any_todos],
[:guest, :guest, :removes_only_confidential_issues_todos] [:guest, :guest, :removes_confidential_issues_and_internal_notes_todos]
] ]
end end
@ -186,7 +198,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
it 'removes only users issue todos' do it 'removes only users issue todos' do
expect { subject }.to change { Todo.count }.from(6).to(5) expect { subject }.to change { Todo.count }.from(7).to(5)
end end
end end
end end
@ -199,7 +211,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'when group is private' do context 'when group is private' do
context 'when a user leaves a group' do context 'when a user leaves a group' do
it 'removes group and subproject todos for the user' do it 'removes group and subproject todos for the user' do
expect { subject }.to change { Todo.count }.from(6).to(2) expect { subject }.to change { Todo.count }.from(7).to(2)
expect(user.todos).to be_empty expect(user.todos).to be_empty
expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2]) expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2])
@ -210,11 +222,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
where(:group_access, :project_access, :method_name) do where(:group_access, :project_access, :method_name) do
[ [
[nil, :reporter, :does_not_remove_any_todos], [nil, :reporter, :does_not_remove_any_todos],
[nil, :guest, :removes_confidential_issues_and_merge_request_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos],
[:reporter, nil, :does_not_remove_any_todos], [:reporter, nil, :does_not_remove_any_todos],
[:guest, nil, :removes_confidential_issues_and_merge_request_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_and_merge_request_todos],
[:guest, :reporter, :does_not_remove_any_todos], [:guest, :reporter, :does_not_remove_any_todos],
[:guest, :guest, :removes_confidential_issues_and_merge_request_todos] [:guest, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos]
] ]
end end
@ -224,12 +236,12 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
context 'with nested groups' do context 'with nested groups' do
let(:parent_group) { create(:group, :public) } let_it_be_with_refind(:parent_group) { create(:group, :public) }
let(:parent_subgroup) { create(:group)} let_it_be_with_refind(:parent_subgroup) { create(:group) }
let(:subgroup) { create(:group, :private, parent: group) } let_it_be(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: group) } let_it_be(:subgroup2) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) } let_it_be(:subproject) { create(:project, group: subgroup) }
let(:subproject2) { create(:project, group: subgroup2) } let_it_be(:subproject2) { create(:project, group: subgroup2) }
let!(:todo_subproject_user) { create(:todo, user: user, project: subproject) } let!(:todo_subproject_user) { create(:todo, user: user, project: subproject) }
let!(:todo_subproject2_user) { create(:todo, user: user, project: subproject2) } let!(:todo_subproject2_user) { create(:todo, user: user, project: subproject2) }
@ -238,6 +250,10 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
let!(:todo_subproject_user2) { create(:todo, user: user2, project: subproject) } let!(:todo_subproject_user2) { create(:todo, user: user2, project: subproject) }
let!(:todo_subpgroup_user2) { create(:todo, user: user2, group: subgroup) } let!(:todo_subpgroup_user2) { create(:todo, user: user2, group: subgroup) }
let!(:todo_parent_group_user) { create(:todo, user: user, group: parent_group) } let!(:todo_parent_group_user) { create(:todo, user: user, group: parent_group) }
let(:subproject_internal_note) { create(:note, noteable: issue, project: project, confidential: true ) }
let!(:todo_for_internal_subproject_note) do
create(:todo, user: user, target: issue, project: project, note: subproject_internal_note)
end
before do before do
group.update!(parent: parent_group) group.update!(parent: parent_group)
@ -245,7 +261,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'when the user is not a member of any groups/projects' do context 'when the user is not a member of any groups/projects' do
it 'removes todos for the user including subprojects todos' do it 'removes todos for the user including subprojects todos' do
expect { subject }.to change { Todo.count }.from(13).to(5) expect { subject }.to change { Todo.count }.from(15).to(5)
expect(user.todos).to eq([todo_parent_group_user]) expect(user.todos).to eq([todo_parent_group_user])
expect(user2.todos) expect(user2.todos)
@ -269,7 +285,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
it 'does not remove group and subproject todos' do it 'does not remove group and subproject todos' do
expect { subject }.to change { Todo.count }.from(13).to(8) expect { subject }.to change { Todo.count }.from(15).to(8)
expect(user.todos) expect(user.todos)
.to match_array( .to match_array(
@ -288,7 +304,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
it 'does not remove subproject and group todos' do it 'does not remove subproject and group todos' do
expect { subject }.to change { Todo.count }.from(13).to(8) expect { subject }.to change { Todo.count }.from(15).to(8)
expect(user.todos) expect(user.todos)
.to match_array( .to match_array(
@ -319,13 +335,13 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'access permissions' do context 'access permissions' do
where(:group_access, :project_access, :method_name) do where(:group_access, :project_access, :method_name) do
[ [
[nil, nil, :removes_only_confidential_issues_todos], [nil, nil, :removes_confidential_issues_and_internal_notes_todos],
[nil, :reporter, :does_not_remove_any_todos], [nil, :reporter, :does_not_remove_any_todos],
[nil, :guest, :removes_only_confidential_issues_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_todos],
[:reporter, nil, :does_not_remove_any_todos], [:reporter, nil, :does_not_remove_any_todos],
[:guest, nil, :removes_only_confidential_issues_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_todos],
[:guest, :reporter, :does_not_remove_any_todos], [:guest, :reporter, :does_not_remove_any_todos],
[:guest, :guest, :removes_only_confidential_issues_todos] [:guest, :guest, :removes_confidential_issues_and_internal_notes_todos]
] ]
end end

View file

@ -66,6 +66,8 @@ Integration.available_integration_names.each do |integration|
hash.merge!(k => 'foo@bar.com') hash.merge!(k => 'foo@bar.com')
elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior
hash.merge!(k => "match_any") hash.merge!(k => "match_any")
elsif integration == 'campfire' && k = :room
hash.merge!(k => '1234')
else else
hash.merge!(k => "someword") hash.merge!(k => "someword")
end end

View file

@ -903,42 +903,65 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
end end
end end
context 'filtering by crm contact' do context 'crm filtering' do
let_it_be(:contact1) { create(:contact, group: group) } let_it_be(:root_group) { create(:group) }
let_it_be(:contact2) { create(:contact, group: group) } let_it_be(:group) { create(:group, parent: root_group) }
let_it_be(:project_crm) { create(:project, :public, group: group) }
let_it_be(:organization) { create(:organization, group: root_group) }
let_it_be(:contact1) { create(:contact, group: root_group, organization: organization) }
let_it_be(:contact2) { create(:contact, group: root_group, organization: organization) }
let_it_be(:contact1_item1) { create(factory, project: project1) } let_it_be(:contact1_item1) { create(factory, project: project_crm) }
let_it_be(:contact1_item2) { create(factory, project: project1) } let_it_be(:contact1_item2) { create(factory, project: project_crm) }
let_it_be(:contact2_item1) { create(factory, project: project1) } let_it_be(:contact2_item1) { create(factory, project: project_crm) }
let_it_be(:item_no_contact) { create(factory, project: project_crm) }
let(:params) { { crm_contact_id: contact1.id } } let_it_be(:all_project_issues) do
[contact1_item1, contact1_item2, contact2_item1, item_no_contact]
it 'returns for that contact' do
create(:issue_customer_relations_contact, issue: contact1_item1, contact: contact1)
create(:issue_customer_relations_contact, issue: contact1_item2, contact: contact1)
create(:issue_customer_relations_contact, issue: contact2_item1, contact: contact2)
expect(items).to contain_exactly(contact1_item1, contact1_item2)
end end
end
context 'filtering by crm organization' do before do
let_it_be(:organization) { create(:organization, group: group) } create(:crm_settings, group: root_group, enabled: true)
let_it_be(:contact1) { create(:contact, group: group, organization: organization) }
let_it_be(:contact2) { create(:contact, group: group, organization: organization) }
let_it_be(:contact1_item1) { create(factory, project: project1) }
let_it_be(:contact1_item2) { create(factory, project: project1) }
let_it_be(:contact2_item1) { create(factory, project: project1) }
let(:params) { { crm_organization_id: organization.id } }
it 'returns for that contact' do
create(:issue_customer_relations_contact, issue: contact1_item1, contact: contact1) create(:issue_customer_relations_contact, issue: contact1_item1, contact: contact1)
create(:issue_customer_relations_contact, issue: contact1_item2, contact: contact1) create(:issue_customer_relations_contact, issue: contact1_item2, contact: contact1)
create(:issue_customer_relations_contact, issue: contact2_item1, contact: contact2) create(:issue_customer_relations_contact, issue: contact2_item1, contact: contact2)
end
expect(items).to contain_exactly(contact1_item1, contact1_item2, contact2_item1) context 'filtering by crm contact' do
let(:params) { { project_id: project_crm.id, crm_contact_id: contact1.id } }
context 'when the user can read crm contacts' do
it 'returns for that contact' do
root_group.add_reporter(user)
expect(items).to contain_exactly(contact1_item1, contact1_item2)
end
end
context 'when the user can not read crm contacts' do
it 'does not filter by contact' do
expect(items).to match_array(all_project_issues)
end
end
end
context 'filtering by crm organization' do
let(:params) { { project_id: project_crm.id, crm_organization_id: organization.id } }
context 'when the user can read crm organization' do
it 'returns for that organization' do
root_group.add_reporter(user)
expect(items).to contain_exactly(contact1_item1, contact1_item2, contact2_item1)
end
end
context 'when the user can not read crm organization' do
it 'does not filter by organization' do
expect(items).to match_array(all_project_issues)
end
end
end end
end end