Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
59469a1782
commit
e4632f4c63
|
@ -33,6 +33,7 @@ Documentation-related MRs should be reviewed by a Technical Writer for a non-blo
|
||||||
- [ ] The headings should be something you'd do a Google search for. Instead of `Default behavior`, say something like `Default behavior when you close an issue`.
|
- [ ] The headings should be something you'd do a Google search for. Instead of `Default behavior`, say something like `Default behavior when you close an issue`.
|
||||||
- [ ] The headings (other than the page title) should be active. Instead of `Configuring GDK`, say something like `Configure GDK`.
|
- [ ] The headings (other than the page title) should be active. Instead of `Configuring GDK`, say something like `Configure GDK`.
|
||||||
- [ ] Any task steps should be written as a numbered list.
|
- [ ] Any task steps should be written as a numbered list.
|
||||||
|
- If the content still needs to be edited for topic types, you can create a follow-up issue with the ~"docs-technical-debt" label.
|
||||||
- [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review.
|
- [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review.
|
||||||
- [ ] Ensure a release milestone is set.
|
- [ ] Ensure a release milestone is set.
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
b6648dea882b755e747d7d6be7a2355a9f28b6ae
|
28e6eebf54f40a2ff820c996698c720b1a898266
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -124,7 +124,7 @@ gem 'fog-aws', '~> 3.9'
|
||||||
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
|
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
|
||||||
# Also see config/initializers/fog_core_patch.rb.
|
# Also see config/initializers/fog_core_patch.rb.
|
||||||
gem 'fog-core', '= 2.1.0'
|
gem 'fog-core', '= 2.1.0'
|
||||||
gem 'gitlab-fog-google', '~> 1.13', require: 'fog/google'
|
gem 'fog-google', '~> 1.15', require: 'fog/google'
|
||||||
gem 'fog-local', '~> 0.6'
|
gem 'fog-local', '~> 0.6'
|
||||||
gem 'fog-openstack', '~> 1.0'
|
gem 'fog-openstack', '~> 1.0'
|
||||||
gem 'fog-rackspace', '~> 0.1.1'
|
gem 'fog-rackspace', '~> 0.1.1'
|
||||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -412,6 +412,12 @@ GEM
|
||||||
excon (~> 0.58)
|
excon (~> 0.58)
|
||||||
formatador (~> 0.2)
|
formatador (~> 0.2)
|
||||||
mime-types
|
mime-types
|
||||||
|
fog-google (1.15.0)
|
||||||
|
fog-core (<= 2.1.0)
|
||||||
|
fog-json (~> 1.2)
|
||||||
|
fog-xml (~> 0.1.0)
|
||||||
|
google-api-client (>= 0.44.2, < 0.51)
|
||||||
|
google-cloud-env (~> 1.2)
|
||||||
fog-json (1.2.0)
|
fog-json (1.2.0)
|
||||||
fog-core
|
fog-core
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
|
@ -475,13 +481,6 @@ GEM
|
||||||
fog-json (~> 1.2.0)
|
fog-json (~> 1.2.0)
|
||||||
mime-types
|
mime-types
|
||||||
ms_rest_azure (~> 0.12.0)
|
ms_rest_azure (~> 0.12.0)
|
||||||
gitlab-fog-google (1.13.0)
|
|
||||||
addressable (>= 2.7.0)
|
|
||||||
fog-core (<= 2.1.0)
|
|
||||||
fog-json (~> 1.2)
|
|
||||||
fog-xml (~> 0.1.0)
|
|
||||||
google-api-client (>= 0.44.2, < 0.51)
|
|
||||||
google-cloud-env (~> 1.2)
|
|
||||||
gitlab-labkit (0.18.0)
|
gitlab-labkit (0.18.0)
|
||||||
actionpack (>= 5.0.0, < 7.0.0)
|
actionpack (>= 5.0.0, < 7.0.0)
|
||||||
activesupport (>= 5.0.0, < 7.0.0)
|
activesupport (>= 5.0.0, < 7.0.0)
|
||||||
|
@ -531,7 +530,7 @@ GEM
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.0)
|
||||||
rexml
|
rexml
|
||||||
signet (~> 0.12)
|
signet (~> 0.12)
|
||||||
google-cloud-env (1.4.0)
|
google-cloud-env (1.5.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-protobuf (3.17.1)
|
google-protobuf (3.17.1)
|
||||||
googleapis-common-protos-types (1.0.6)
|
googleapis-common-protos-types (1.0.6)
|
||||||
|
@ -1474,6 +1473,7 @@ DEPENDENCIES
|
||||||
fog-aliyun (~> 0.3)
|
fog-aliyun (~> 0.3)
|
||||||
fog-aws (~> 3.9)
|
fog-aws (~> 3.9)
|
||||||
fog-core (= 2.1.0)
|
fog-core (= 2.1.0)
|
||||||
|
fog-google (~> 1.15)
|
||||||
fog-local (~> 0.6)
|
fog-local (~> 0.6)
|
||||||
fog-openstack (~> 1.0)
|
fog-openstack (~> 1.0)
|
||||||
fog-rackspace (~> 0.1.1)
|
fog-rackspace (~> 0.1.1)
|
||||||
|
@ -1489,7 +1489,6 @@ DEPENDENCIES
|
||||||
gitlab-dangerfiles (~> 2.1.2)
|
gitlab-dangerfiles (~> 2.1.2)
|
||||||
gitlab-experiment (~> 0.5.4)
|
gitlab-experiment (~> 0.5.4)
|
||||||
gitlab-fog-azure-rm (~> 1.1.1)
|
gitlab-fog-azure-rm (~> 1.1.1)
|
||||||
gitlab-fog-google (~> 1.13)
|
|
||||||
gitlab-labkit (~> 0.18.0)
|
gitlab-labkit (~> 0.18.0)
|
||||||
gitlab-license (~> 1.5)
|
gitlab-license (~> 1.5)
|
||||||
gitlab-mail_room (~> 0.0.9)
|
gitlab-mail_room (~> 0.0.9)
|
||||||
|
|
|
@ -155,6 +155,7 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- eslint-disable-next-line vue/no-deprecated-functional-template -->
|
||||||
<template functional>
|
<template functional>
|
||||||
<div :class="$options.classNameMap(props)" class="diff-grid-row diff-tr line_holder">
|
<div :class="$options.classNameMap(props)" class="diff-grid-row diff-tr line_holder">
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -296,7 +296,7 @@ class Group < Namespace
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
||||||
GroupMember.add_users(
|
Members::Groups::CreatorService.add_users( # rubocop:todo CodeReuse/ServiceClass
|
||||||
self,
|
self,
|
||||||
users,
|
users,
|
||||||
access_level,
|
access_level,
|
||||||
|
@ -306,14 +306,13 @@ class Group < Namespace
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_user(user, access_level, current_user: nil, expires_at: nil, ldap: false)
|
def add_user(user, access_level, current_user: nil, expires_at: nil, ldap: false)
|
||||||
GroupMember.add_user(
|
Members::Groups::CreatorService.new(self, # rubocop:todo CodeReuse/ServiceClass
|
||||||
self,
|
user,
|
||||||
user,
|
access_level,
|
||||||
access_level,
|
current_user: current_user,
|
||||||
current_user: current_user,
|
expires_at: expires_at,
|
||||||
expires_at: expires_at,
|
ldap: ldap)
|
||||||
ldap: ldap
|
.execute
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_guest(user, current_user = nil)
|
def add_guest(user, current_user = nil)
|
||||||
|
|
|
@ -232,140 +232,9 @@ class Member < ApplicationRecord
|
||||||
find_by(invite_token: invite_token)
|
find_by(invite_token: invite_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_user(source, user, access_level, existing_members: nil, current_user: nil, expires_at: nil, ldap: false)
|
|
||||||
# rubocop: disable CodeReuse/ServiceClass
|
|
||||||
# `user` can be either a User object, User ID or an email to be invited
|
|
||||||
member = retrieve_member(source, user, existing_members)
|
|
||||||
access_level = retrieve_access_level(access_level)
|
|
||||||
|
|
||||||
return member unless can_update_member?(current_user, member)
|
|
||||||
|
|
||||||
set_member_attributes(
|
|
||||||
member,
|
|
||||||
access_level,
|
|
||||||
current_user: current_user,
|
|
||||||
expires_at: expires_at,
|
|
||||||
ldap: ldap
|
|
||||||
)
|
|
||||||
|
|
||||||
if member.request?
|
|
||||||
::Members::ApproveAccessRequestService.new(
|
|
||||||
current_user,
|
|
||||||
access_level: access_level
|
|
||||||
).execute(
|
|
||||||
member,
|
|
||||||
skip_authorization: ldap,
|
|
||||||
skip_log_audit_event: ldap
|
|
||||||
)
|
|
||||||
else
|
|
||||||
member.save
|
|
||||||
end
|
|
||||||
|
|
||||||
member
|
|
||||||
# rubocop: enable CodeReuse/ServiceClass
|
|
||||||
end
|
|
||||||
|
|
||||||
# Populates the attributes of a member.
|
|
||||||
#
|
|
||||||
# This logic resides in a separate method so that EE can extend this logic,
|
|
||||||
# without having to patch the `add_user` method directly.
|
|
||||||
def set_member_attributes(member, access_level, current_user: nil, expires_at: nil, ldap: false)
|
|
||||||
member.attributes = {
|
|
||||||
created_by: member.created_by || current_user,
|
|
||||||
access_level: access_level,
|
|
||||||
expires_at: expires_at
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_users(source, users, access_level, current_user: nil, expires_at: nil)
|
|
||||||
return [] unless users.present?
|
|
||||||
|
|
||||||
emails, users, existing_members = parse_users_list(source, users)
|
|
||||||
|
|
||||||
self.transaction do
|
|
||||||
(emails + users).map! do |user|
|
|
||||||
add_user(
|
|
||||||
source,
|
|
||||||
user,
|
|
||||||
access_level,
|
|
||||||
existing_members: existing_members,
|
|
||||||
current_user: current_user,
|
|
||||||
expires_at: expires_at
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def access_levels
|
|
||||||
Gitlab::Access.sym_options
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_email?(email)
|
def valid_email?(email)
|
||||||
Devise.email_regexp.match?(email)
|
Devise.email_regexp.match?(email)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def parse_users_list(source, list)
|
|
||||||
emails = []
|
|
||||||
user_ids = []
|
|
||||||
users = []
|
|
||||||
existing_members = {}
|
|
||||||
|
|
||||||
list.each do |item|
|
|
||||||
case item
|
|
||||||
when User
|
|
||||||
users << item
|
|
||||||
when Integer
|
|
||||||
user_ids << item
|
|
||||||
when /\A\d+\Z/
|
|
||||||
user_ids << item.to_i
|
|
||||||
when Devise.email_regexp
|
|
||||||
emails << item
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if user_ids.present?
|
|
||||||
users.concat(User.where(id: user_ids))
|
|
||||||
# the below will automatically discard invalid user_ids
|
|
||||||
existing_members = source.members_and_requesters.where(user_id: user_ids).index_by(&:user_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
[emails, users, existing_members]
|
|
||||||
end
|
|
||||||
|
|
||||||
# This method is used to find users that have been entered into the "Add members" field.
|
|
||||||
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
|
|
||||||
def retrieve_user(user)
|
|
||||||
return user if user.is_a?(User)
|
|
||||||
|
|
||||||
return User.find_by(id: user) if user.is_a?(Integer)
|
|
||||||
|
|
||||||
User.find_by_any_email(user) || user
|
|
||||||
end
|
|
||||||
|
|
||||||
def retrieve_member(source, user, existing_members)
|
|
||||||
user = retrieve_user(user)
|
|
||||||
|
|
||||||
if user.is_a?(User)
|
|
||||||
if existing_members
|
|
||||||
existing_members[user.id] || source.members.build(user_id: user.id)
|
|
||||||
else
|
|
||||||
source.members_and_requesters.find_or_initialize_by(user_id: user.id)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
source.members.build(invite_email: user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def retrieve_access_level(access_level)
|
|
||||||
access_levels.fetch(access_level) { access_level.to_i }
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_update_member?(current_user, member)
|
|
||||||
# There is no current user for bulk actions, in which case anything is allowed
|
|
||||||
!current_user || current_user.can?(:"update_#{member.type.underscore}", member)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def real_source_type
|
def real_source_type
|
||||||
|
|
|
@ -32,10 +32,6 @@ class GroupMember < Member
|
||||||
Gitlab::Access.options_with_owner
|
Gitlab::Access.options_with_owner
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.access_levels
|
|
||||||
Gitlab::Access.sym_options_with_owner
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.pluck_user_ids
|
def self.pluck_user_ids
|
||||||
pluck(:user_id)
|
pluck(:user_id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,7 +48,7 @@ class ProjectMember < Member
|
||||||
project_ids.each do |project_id|
|
project_ids.each do |project_id|
|
||||||
project = Project.find(project_id)
|
project = Project.find(project_id)
|
||||||
|
|
||||||
add_users(
|
Members::Projects::CreatorService.add_users( # rubocop:todo CodeReuse/ServiceClass
|
||||||
project,
|
project,
|
||||||
users,
|
users,
|
||||||
access_level,
|
access_level,
|
||||||
|
@ -80,12 +80,6 @@ class ProjectMember < Member
|
||||||
def access_level_roles
|
def access_level_roles
|
||||||
Gitlab::Access.options
|
Gitlab::Access.options
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def can_update_member?(current_user, member)
|
|
||||||
super || (member.owner? && member.new_record?)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def project
|
def project
|
||||||
|
|
|
@ -42,7 +42,7 @@ class ProjectTeam
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
||||||
ProjectMember.add_users(
|
Members::Projects::CreatorService.add_users( # rubocop:todo CodeReuse/ServiceClass
|
||||||
project,
|
project,
|
||||||
users,
|
users,
|
||||||
access_level,
|
access_level,
|
||||||
|
@ -52,13 +52,12 @@ class ProjectTeam
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_user(user, access_level, current_user: nil, expires_at: nil)
|
def add_user(user, access_level, current_user: nil, expires_at: nil)
|
||||||
ProjectMember.add_user(
|
Members::Projects::CreatorService.new(project, # rubocop:todo CodeReuse/ServiceClass
|
||||||
project,
|
user,
|
||||||
user,
|
access_level,
|
||||||
access_level,
|
current_user: current_user,
|
||||||
current_user: current_user,
|
expires_at: expires_at)
|
||||||
expires_at: expires_at
|
.execute
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove all users from project team
|
# Remove all users from project team
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Members
|
||||||
|
# This class serves as more of an app-wide way we add/create members
|
||||||
|
# All roads to add members should take this path.
|
||||||
|
class CreatorService
|
||||||
|
class << self
|
||||||
|
def parsed_access_level(access_level)
|
||||||
|
access_levels.fetch(access_level) { access_level.to_i }
|
||||||
|
end
|
||||||
|
|
||||||
|
def access_levels
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_users(source, users, access_level, current_user: nil, expires_at: nil)
|
||||||
|
return [] unless users.present?
|
||||||
|
|
||||||
|
emails, users, existing_members = parse_users_list(source, users)
|
||||||
|
|
||||||
|
Member.transaction do
|
||||||
|
(emails + users).map! do |user|
|
||||||
|
new(source,
|
||||||
|
user,
|
||||||
|
access_level,
|
||||||
|
existing_members: existing_members,
|
||||||
|
current_user: current_user,
|
||||||
|
expires_at: expires_at)
|
||||||
|
.execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_users_list(source, list)
|
||||||
|
emails = []
|
||||||
|
user_ids = []
|
||||||
|
users = []
|
||||||
|
existing_members = {}
|
||||||
|
|
||||||
|
list.each do |item|
|
||||||
|
case item
|
||||||
|
when User
|
||||||
|
users << item
|
||||||
|
when Integer
|
||||||
|
user_ids << item
|
||||||
|
when /\A\d+\Z/
|
||||||
|
user_ids << item.to_i
|
||||||
|
when Devise.email_regexp
|
||||||
|
emails << item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if user_ids.present?
|
||||||
|
users.concat(User.id_in(user_ids))
|
||||||
|
# the below will automatically discard invalid user_ids
|
||||||
|
existing_members = source.members_and_requesters.where(user_id: user_ids).index_by(&:user_id) # rubocop:todo CodeReuse/ActiveRecord
|
||||||
|
end
|
||||||
|
|
||||||
|
[emails, users, existing_members]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(source, user, access_level, **args)
|
||||||
|
@source = source
|
||||||
|
@user = user
|
||||||
|
@access_level = self.class.parsed_access_level(access_level)
|
||||||
|
@args = args
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
find_or_build_member
|
||||||
|
update_member
|
||||||
|
|
||||||
|
member
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :source, :user, :access_level, :member, :args
|
||||||
|
|
||||||
|
def update_member
|
||||||
|
return unless can_update_member?
|
||||||
|
|
||||||
|
member.attributes = member_attributes
|
||||||
|
|
||||||
|
if member.request?
|
||||||
|
approve_request
|
||||||
|
else
|
||||||
|
member.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_update_member?
|
||||||
|
# There is no current user for bulk actions, in which case anything is allowed
|
||||||
|
!current_user # inheriting classes will add more logic
|
||||||
|
end
|
||||||
|
|
||||||
|
# Populates the attributes of a member.
|
||||||
|
#
|
||||||
|
# This logic resides in a separate method so that EE can extend this logic,
|
||||||
|
# without having to patch the `add_user` method directly.
|
||||||
|
def member_attributes
|
||||||
|
{
|
||||||
|
created_by: member.created_by || current_user,
|
||||||
|
access_level: access_level,
|
||||||
|
expires_at: args[:expires_at]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def approve_request
|
||||||
|
::Members::ApproveAccessRequestService.new(current_user,
|
||||||
|
access_level: access_level)
|
||||||
|
.execute(
|
||||||
|
member,
|
||||||
|
skip_authorization: ldap,
|
||||||
|
skip_log_audit_event: ldap
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_user
|
||||||
|
args[:current_user]
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_or_build_member
|
||||||
|
@user = parse_user_param
|
||||||
|
|
||||||
|
@member = if user.is_a?(User)
|
||||||
|
find_or_initialize_member_by_user
|
||||||
|
else
|
||||||
|
source.members.build(invite_email: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This method is used to find users that have been entered into the "Add members" field.
|
||||||
|
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
|
||||||
|
def parse_user_param
|
||||||
|
case user
|
||||||
|
when User
|
||||||
|
user
|
||||||
|
when Integer
|
||||||
|
# might not return anything - this needs enhancement
|
||||||
|
User.find_by(id: user) # rubocop:todo CodeReuse/ActiveRecord
|
||||||
|
else
|
||||||
|
# must be an email or at least we'll consider it one
|
||||||
|
User.find_by_any_email(user) || user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_or_initialize_member_by_user
|
||||||
|
if existing_members
|
||||||
|
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/334062
|
||||||
|
# i'm not so sure this is needed as the parse_users_list looks at members_and_requesters...
|
||||||
|
# so it is like we could just do a find or initialize by here and be fine
|
||||||
|
existing_members[user.id] || source.members.build(user_id: user.id)
|
||||||
|
else
|
||||||
|
source.members_and_requesters.find_or_initialize_by(user_id: user.id) # rubocop:todo CodeReuse/ActiveRecord
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def existing_members
|
||||||
|
args[:existing_members]
|
||||||
|
end
|
||||||
|
|
||||||
|
def ldap
|
||||||
|
args[:ldap] || false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Members::CreatorService.prepend_mod_with('Members::CreatorService')
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Members
|
||||||
|
module Groups
|
||||||
|
class CreatorService < Members::CreatorService
|
||||||
|
def self.access_levels
|
||||||
|
Gitlab::Access.sym_options_with_owner
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def can_update_member?
|
||||||
|
super || current_user.can?(:update_group_member, member)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,7 +21,7 @@ module Members
|
||||||
def validate_invites!
|
def validate_invites!
|
||||||
super
|
super
|
||||||
|
|
||||||
# we need the below due to add_users hitting Member#parse_users_list and ignoring invalid emails
|
# we need the below due to add_users hitting Members::CreatorService.parse_users_list and ignoring invalid emails
|
||||||
# ideally we wouldn't need this, but we can't really change the add_users method
|
# ideally we wouldn't need this, but we can't really change the add_users method
|
||||||
valid, invalid = invites.partition { |email| Member.valid_email?(email) }
|
valid, invalid = invites.partition { |email| Member.valid_email?(email) }
|
||||||
@invites = valid
|
@invites = valid
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Members
|
||||||
|
module Projects
|
||||||
|
class CreatorService < Members::CreatorService
|
||||||
|
def self.access_levels
|
||||||
|
Gitlab::Access.sym_options
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def can_update_member?
|
||||||
|
super || current_user.can?(:update_project_member, member) || adding_a_new_owner?
|
||||||
|
end
|
||||||
|
|
||||||
|
def adding_a_new_owner?
|
||||||
|
# this condition is reached during testing setup a lot due to use of `.add_user`
|
||||||
|
member.owner? && member.new_record?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -231,7 +231,7 @@ standard Azure B2C user flows [do not send the OpenID `email` claim](https://git
|
||||||
other words, they do not work with the [`allow_single_sign_on` or `auto_link_user`
|
other words, they do not work with the [`allow_single_sign_on` or `auto_link_user`
|
||||||
parameters](../../integration/omniauth.md#initial-omniauth-configuration).
|
parameters](../../integration/omniauth.md#initial-omniauth-configuration).
|
||||||
With a standard Azure B2C policy, GitLab cannot create a new account or
|
With a standard Azure B2C policy, GitLab cannot create a new account or
|
||||||
link to an existing one with an e-mail address.
|
link to an existing one with an email address.
|
||||||
|
|
||||||
Carefully follow the instructions for [creating a custom policy](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy).
|
Carefully follow the instructions for [creating a custom policy](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy).
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ gitlab_rails['omniauth_providers'] = [
|
||||||
the respective client IDs in the XML policy files.
|
the respective client IDs in the XML policy files.
|
||||||
|
|
||||||
- Add `https://jwt.ms` as a redirect URI to the app, and use the [custom policy tester](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#test-the-custom-policy).
|
- Add `https://jwt.ms` as a redirect URI to the app, and use the [custom policy tester](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#test-the-custom-policy).
|
||||||
Make sure the payload includes `email` that matches the user's e-mail access.
|
Make sure the payload includes `email` that matches the user's email access.
|
||||||
|
|
||||||
- After you enable the custom policy, users might see "Invalid username or password" after they try to sign in. This might be a configuration
|
- After you enable the custom policy, users might see "Invalid username or password" after they try to sign in. This might be a configuration
|
||||||
issue with the `IdentityExperienceFramework` app. See [this Microsoft comment](https://docs.microsoft.com/en-us/answers/questions/50355/unable-to-sign-on-using-custom-policy.html?childToView=122370#comment-122370)
|
issue with the `IdentityExperienceFramework` app. See [this Microsoft comment](https://docs.microsoft.com/en-us/answers/questions/50355/unable-to-sign-on-using-custom-policy.html?childToView=122370#comment-122370)
|
||||||
|
|
|
@ -16,7 +16,7 @@ GitLab has several features based on receiving incoming email messages:
|
||||||
- [New merge request by email](../user/project/merge_requests/creating_merge_requests.md#new-merge-request-by-email):
|
- [New merge request by email](../user/project/merge_requests/creating_merge_requests.md#new-merge-request-by-email):
|
||||||
allow GitLab users to create a new merge request by sending an email to a
|
allow GitLab users to create a new merge request by sending an email to a
|
||||||
user-specific email address.
|
user-specific email address.
|
||||||
- [Service Desk](../user/project/service_desk.md): provide e-mail support to
|
- [Service Desk](../user/project/service_desk.md): provide email support to
|
||||||
your customers through GitLab.
|
your customers through GitLab.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
|
@ -119,7 +119,7 @@ an SMTP server, but you're not seeing mail delivered. Here's how to check the se
|
||||||
irb(main):003:0> Notify.test_email('youremail@email.com', 'Hello World', 'This is a test message').deliver_now
|
irb(main):003:0> Notify.test_email('youremail@email.com', 'Hello World', 'This is a test message').deliver_now
|
||||||
```
|
```
|
||||||
|
|
||||||
If you do not receive an e-mail and/or see an error message, then check
|
If you do not receive an email and/or see an error message, then check
|
||||||
your mail server settings.
|
your mail server settings.
|
||||||
|
|
||||||
## Advanced Issues
|
## Advanced Issues
|
||||||
|
|
|
@ -20,7 +20,7 @@ POST /import/github
|
||||||
| `repo_id` | integer | yes | GitHub repository ID |
|
| `repo_id` | integer | yes | GitHub repository ID |
|
||||||
| `new_name` | string | no | New repository name |
|
| `new_name` | string | no | New repository name |
|
||||||
| `target_namespace` | string | yes | Namespace to import repository into. Supports subgroups like `/namespace/subgroup`. |
|
| `target_namespace` | string | yes | Namespace to import repository into. Supports subgroups like `/namespace/subgroup`. |
|
||||||
| `github_hostname` | string | no | Custom GitHub enterprise hostname. Defaults to GitHub.com if `github_hostname` is not set. |
|
| `github_hostname` | string | no | Custom GitHub Enterprise hostname. Do not set for GitHub.com. |
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl --request POST \
|
curl --request POST \
|
||||||
|
@ -31,7 +31,8 @@ curl --request POST \
|
||||||
"personal_access_token": "aBc123abC12aBc123abC12abC123+_A/c123",
|
"personal_access_token": "aBc123abC12aBc123abC12abC123+_A/c123",
|
||||||
"repo_id": "12345",
|
"repo_id": "12345",
|
||||||
"target_namespace": "group/subgroup",
|
"target_namespace": "group/subgroup",
|
||||||
"new_name": "NEW-NAME"
|
"new_name": "NEW-NAME",
|
||||||
|
"github_hostname": "https://github.example.com"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -262,16 +262,18 @@ GET /groups/:id/billable_members
|
||||||
|
|
||||||
The supported values for the `sort` attribute are:
|
The supported values for the `sort` attribute are:
|
||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
| ------------------- | ------------------------ |
|
| ----------------------- | ---------------------------- |
|
||||||
| `access_level_asc` | Access level, ascending |
|
| `access_level_asc` | Access level, ascending |
|
||||||
| `access_level_desc` | Access level, descending |
|
| `access_level_desc` | Access level, descending |
|
||||||
| `last_joined` | Last joined |
|
| `last_joined` | Last joined |
|
||||||
| `name_asc` | Name, ascending |
|
| `name_asc` | Name, ascending |
|
||||||
| `name_desc` | Name, descending |
|
| `name_desc` | Name, descending |
|
||||||
| `oldest_joined` | Oldest joined |
|
| `oldest_joined` | Oldest joined |
|
||||||
| `oldest_sign_in` | Oldest sign in |
|
| `oldest_sign_in` | Oldest sign in |
|
||||||
| `recent_sign_in` | Recent sign in |
|
| `recent_sign_in` | Recent sign in |
|
||||||
|
| `last_activity_on_asc` | Last active date, ascending |
|
||||||
|
| `last_activity_on_desc` | Last active date, descending |
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members"
|
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members"
|
||||||
|
|
|
@ -258,7 +258,7 @@ listed in the descriptions of the relevant settings.
|
||||||
| `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. |
|
| `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. |
|
||||||
| `dns_rebinding_protection_enabled` | boolean | no | Enforce DNS rebinding attack protection. |
|
| `dns_rebinding_protection_enabled` | boolean | no | Enforce DNS rebinding attack protection. |
|
||||||
| `domain_denylist_enabled` | boolean | no | (**If enabled, requires:** `domain_denylist`) Allows blocking sign-ups from emails from specific domains. |
|
| `domain_denylist_enabled` | boolean | no | (**If enabled, requires:** `domain_denylist`) Allows blocking sign-ups from emails from specific domains. |
|
||||||
| `domain_denylist` | array of strings | no | Users with e-mail addresses that match these domain(s) **cannot** sign up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
|
| `domain_denylist` | array of strings | no | Users with email addresses that match these domain(s) **cannot** sign up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
|
||||||
| `domain_allowlist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
|
| `domain_allowlist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
|
||||||
| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
|
| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
|
||||||
| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
|
| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
|
||||||
|
@ -413,7 +413,7 @@ listed in the descriptions of the relevant settings.
|
||||||
| `unique_ips_limit_time_window` | integer | required by: `unique_ips_limit_enabled` | How many seconds an IP is counted towards the limit. |
|
| `unique_ips_limit_time_window` | integer | required by: `unique_ips_limit_enabled` | How many seconds an IP is counted towards the limit. |
|
||||||
| `usage_ping_enabled` | boolean | no | Every week GitLab reports license usage back to GitLab, Inc. |
|
| `usage_ping_enabled` | boolean | no | Every week GitLab reports license usage back to GitLab, Inc. |
|
||||||
| `user_default_external` | boolean | no | Newly registered users are external by default. |
|
| `user_default_external` | boolean | no | Newly registered users are external by default. |
|
||||||
| `user_default_internal_regex` | string | no | Specify an e-mail address regex pattern to identify default internal users. |
|
| `user_default_internal_regex` | string | no | Specify an email address regex pattern to identify default internal users. |
|
||||||
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. |
|
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. |
|
||||||
| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the `You won't be able to pull or push project code via SSH` warning shown to users with no uploaded SSH key. |
|
| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the `You won't be able to pull or push project code via SSH` warning shown to users with no uploaded SSH key. |
|
||||||
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
|
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
|
||||||
|
|
|
@ -1352,7 +1352,7 @@ Parameters:
|
||||||
|
|
||||||
- `id` (required) - ID of specified user
|
- `id` (required) - ID of specified user
|
||||||
- `email` (required) - email address
|
- `email` (required) - email address
|
||||||
- `skip_confirmation` (optional) - Skip confirmation and assume e-mail is verified - true or false (default)
|
- `skip_confirmation` (optional) - Skip confirmation and assume email is verified - true or false (default)
|
||||||
|
|
||||||
## Delete email for current user
|
## Delete email for current user
|
||||||
|
|
||||||
|
|
|
@ -91,8 +91,32 @@ To skip some checks based on tags when pushing, you can set the `LEFTHOOK_EXCLUD
|
||||||
LEFTHOOK_EXCLUDE=frontend,documentation git push ...
|
LEFTHOOK_EXCLUDE=frontend,documentation git push ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
As an alternative, you can create `lefthook-local.yml` with this structure:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
pre-push:
|
||||||
|
exclude_tags:
|
||||||
|
- frontend
|
||||||
|
- documentation
|
||||||
|
```
|
||||||
|
|
||||||
For more information, check out [Lefthook documentation](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly).
|
For more information, check out [Lefthook documentation](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly).
|
||||||
|
|
||||||
|
### Skip or enable a specific Lefthook check
|
||||||
|
|
||||||
|
To skip or enable a check based on its name when pushing, you can add `skip: true`
|
||||||
|
or `skip: false` to the `lefthook-local.yml` section for that hook. For instance,
|
||||||
|
you might want to enable the gettext check to detect issues with `locale/gitlab.pot`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
pre-push:
|
||||||
|
commands:
|
||||||
|
gettext:
|
||||||
|
skip: false
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, check out [this Lefthook documentation section](https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md#skipping-commands).
|
||||||
|
|
||||||
## Ruby, Rails, RSpec
|
## Ruby, Rails, RSpec
|
||||||
|
|
||||||
Our codebase style is defined and enforced by [RuboCop](https://github.com/rubocop-hq/rubocop).
|
Our codebase style is defined and enforced by [RuboCop](https://github.com/rubocop-hq/rubocop).
|
||||||
|
|
|
@ -60,6 +60,10 @@ Do not use. If the user doesn't find the process to be these things, we lose the
|
||||||
|
|
||||||
Do not use Latin abbreviations. Use **for example**, **such as**, **for instance**, or **like** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml))
|
Do not use Latin abbreviations. Use **for example**, **such as**, **for instance**, or **like** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml))
|
||||||
|
|
||||||
|
## email
|
||||||
|
|
||||||
|
Do not use **e-mail** with a hyphen. When plural, use **emails** or **email messages**.
|
||||||
|
|
||||||
## enable
|
## enable
|
||||||
|
|
||||||
See [the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/e/enable-enables) for guidance.
|
See [the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/e/enable-enables) for guidance.
|
||||||
|
|
|
@ -243,7 +243,7 @@ Ruby on Rails applications. In GitLab, Sidekiq performs the heavy
|
||||||
lifting of many activities, including:
|
lifting of many activities, including:
|
||||||
|
|
||||||
- Updating merge requests after a push.
|
- Updating merge requests after a push.
|
||||||
- Sending e-mails.
|
- Sending email messages.
|
||||||
- Updating user authorizations.
|
- Updating user authorizations.
|
||||||
- Processing CI builds and pipelines.
|
- Processing CI builds and pipelines.
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ in a timely manner:
|
||||||
this to `ProcessCommitWorker`.
|
this to `ProcessCommitWorker`.
|
||||||
- Redistribute/gerrymander Sidekiq processes by queue
|
- Redistribute/gerrymander Sidekiq processes by queue
|
||||||
types. Long-running jobs (for example, relating to project import) can often
|
types. Long-running jobs (for example, relating to project import) can often
|
||||||
squeeze out jobs that run fast (for example, delivering e-mail). [This technique
|
squeeze out jobs that run fast (for example, delivering email). [This technique
|
||||||
was used in to optimize our existing Sidekiq deployment](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/7219#note_218019483).
|
was used in to optimize our existing Sidekiq deployment](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/7219#note_218019483).
|
||||||
- Optimize jobs. Eliminating unnecessary work, reducing network calls
|
- Optimize jobs. Eliminating unnecessary work, reducing network calls
|
||||||
(including SQL and Gitaly), and optimizing processor time can yield significant
|
(including SQL and Gitaly), and optimizing processor time can yield significant
|
||||||
|
|
|
@ -67,7 +67,7 @@ The following changes are needed to enable Shibboleth:
|
||||||
your users appear to be authenticated by Shibboleth and Apache, but GitLab
|
your users appear to be authenticated by Shibboleth and Apache, but GitLab
|
||||||
rejects their account with a URI that contains "e-mail is invalid" then your
|
rejects their account with a URI that contains "e-mail is invalid" then your
|
||||||
Shibboleth Identity Provider or Attribute Authority may be asserting multiple
|
Shibboleth Identity Provider or Attribute Authority may be asserting multiple
|
||||||
e-mail addresses. In this instance, you might consider setting the
|
email addresses. In this instance, you might consider setting the
|
||||||
`multi_values` argument to `first`.
|
`multi_values` argument to `first`.
|
||||||
|
|
||||||
The file should look like this:
|
The file should look like this:
|
||||||
|
|
|
@ -392,10 +392,10 @@ end
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36617) in GitLab 13.2.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36617) in GitLab 13.2.
|
||||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/251234) in GitLab 13.5.
|
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/251234) in GitLab 13.5.
|
||||||
> - [Updated](https://gitlab.com/gitlab-org/gitlab/-/issues/220333) in GitLab 14.1
|
> - Showing related feature flags in issues [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220333) in GitLab 14.1.
|
||||||
|
|
||||||
You can link related issues to a feature flag. In the **Linked issues** section,
|
You can link related issues to a feature flag. In the **Linked issues** section,
|
||||||
click the `+` button and input the issue reference number or the full URL of the issue.
|
click the `+` button and input the issue reference number or the full URL of the issue.
|
||||||
The issue(s) will then appear in the related feautre flag and vice versa.
|
The issues then appear in the related feature flag and the other way round.
|
||||||
|
|
||||||
This feature is similar to the [linked issues](../user/project/issues/related_issues.md) feature.
|
This feature is similar to the [linked issues](../user/project/issues/related_issues.md) feature.
|
||||||
|
|
|
@ -14,7 +14,7 @@ reports in the Admin Area.
|
||||||
|
|
||||||
## Receiving notifications of abuse reports
|
## Receiving notifications of abuse reports
|
||||||
|
|
||||||
To receive notifications of new abuse reports by e-mail, follow these steps:
|
To receive notifications of new abuse reports by email, follow these steps:
|
||||||
|
|
||||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||||
1. On the left sidebar, select **Settings > Reporting**.
|
1. On the left sidebar, select **Settings > Reporting**.
|
||||||
|
|
|
@ -70,7 +70,7 @@ For more details on the specific data persisted in a group export, see the
|
||||||
|
|
||||||
![Export group panel](img/export_panel_v13_0.png)
|
![Export group panel](img/export_panel_v13_0.png)
|
||||||
|
|
||||||
1. After the export is generated, you should receive an e-mail with a link to the [exported contents](#exported-contents)
|
1. After the export is generated, you should receive an email with a link to the [exported contents](#exported-contents)
|
||||||
in a compressed tar archive, with contents in NDJSON format.
|
in a compressed tar archive, with contents in NDJSON format.
|
||||||
|
|
||||||
1. Alternatively, you can come back to the project settings and download the
|
1. Alternatively, you can come back to the project settings and download the
|
||||||
|
|
|
@ -51,7 +51,7 @@ The Bitbucket Server importer works as follows:
|
||||||
### User assignment
|
### User assignment
|
||||||
|
|
||||||
When issues/pull requests are being imported, the Bitbucket importer tries to
|
When issues/pull requests are being imported, the Bitbucket importer tries to
|
||||||
find the author's e-mail address with a confirmed e-mail address in the GitLab
|
find the author's email address with a confirmed email address in the GitLab
|
||||||
user database. If no such user is available, the project creator is set as
|
user database. If no such user is available, the project creator is set as
|
||||||
the author. The importer appends a note in the comment to mark the original
|
the author. The importer appends a note in the comment to mark the original
|
||||||
creator.
|
creator.
|
||||||
|
|
|
@ -231,7 +231,7 @@ _In GitLab 11.7, we updated the format of the generated email address.
|
||||||
However the older format is still supported, allowing existing aliases
|
However the older format is still supported, allowing existing aliases
|
||||||
or contacts to continue working._
|
or contacts to continue working._
|
||||||
|
|
||||||
### Adding patches when creating a merge request via e-mail
|
### Adding patches when creating a merge request via email
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22723) in GitLab 11.5.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22723) in GitLab 11.5.
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ might be slightly different. Follow the
|
||||||
sudo certbot certonly -a manual -d example.com --email your@email.com
|
sudo certbot certonly -a manual -d example.com --email your@email.com
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can register without adding an e-mail account,
|
Alternatively, you can register without adding an email account,
|
||||||
but you aren't notified about the certificate expiration's date:
|
but you aren't notified about the certificate expiration's date:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -234,7 +234,7 @@ The configuration options are the same as for configuring
|
||||||
> Introduced in [GitLab 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/214900)
|
> Introduced in [GitLab 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/214900)
|
||||||
|
|
||||||
Service Desk can be configured to read Microsoft Exchange Online mailboxes with the Microsoft
|
Service Desk can be configured to read Microsoft Exchange Online mailboxes with the Microsoft
|
||||||
Graph API instead of IMAP. Follow the [documentation in the incoming e-mail section for setting up an OAuth2 application for Microsoft Graph](../../administration/incoming_email.md#microsoft-graph).
|
Graph API instead of IMAP. Follow the [documentation in the incoming email section for setting up an OAuth2 application for Microsoft Graph](../../administration/incoming_email.md#microsoft-graph).
|
||||||
|
|
||||||
- Example for Omnibus GitLab installations:
|
- Example for Omnibus GitLab installations:
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,7 @@ To export a project and its data, follow these steps:
|
||||||
|
|
||||||
![Export button](img/import_export_export_button.png)
|
![Export button](img/import_export_export_button.png)
|
||||||
|
|
||||||
1. Once the export is generated, you should receive an e-mail with a link to
|
1. Once the export is generated, you should receive an email with a link to
|
||||||
download the file:
|
download the file:
|
||||||
|
|
||||||
![Email download link](img/import_export_mail_link.png)
|
![Email download link](img/import_export_mail_link.png)
|
||||||
|
|
|
@ -38,3 +38,9 @@ pre-push:
|
||||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||||
glob: 'doc/*.md'
|
glob: 'doc/*.md'
|
||||||
run: if command -v vale 2> /dev/null; then vale --config .vale.ini --minAlertLevel error {files}; else echo "Vale not found. Install Vale"; fi
|
run: if command -v vale 2> /dev/null; then vale --config .vale.ini --minAlertLevel error {files}; else echo "Vale not found. Install Vale"; fi
|
||||||
|
gettext:
|
||||||
|
skip: true # This is disabled by default. You can enable this check by adding skip: false in lefhook-local.yml https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md#skipping-commands
|
||||||
|
tags: backend frontend view haml
|
||||||
|
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD | while read file;do git diff --unified=1 $(git merge-base origin/master HEAD)..HEAD $file | grep -Fqe '_(' && echo $file;done; true
|
||||||
|
glob: "*.{haml,rb,js,vue}"
|
||||||
|
run: bin/rake gettext:updated_check
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
class BackupLogger < Gitlab::JsonLogger
|
||||||
|
def self.file_name_noext
|
||||||
|
'backup_json'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -42,10 +42,6 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def metrics_params
|
|
||||||
super.merge(project: project&.full_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def metrics_event
|
def metrics_event
|
||||||
:receive_email_service_desk
|
:receive_email_service_desk
|
||||||
end
|
end
|
||||||
|
|
|
@ -282,6 +282,7 @@ namespace :gitlab do
|
||||||
|
|
||||||
def puts_time(msg)
|
def puts_time(msg)
|
||||||
progress.puts "#{Time.now} -- #{msg}"
|
progress.puts "#{Time.now} -- #{msg}"
|
||||||
|
Gitlab::BackupLogger.info(message: "#{Rainbow.uncolor(msg)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def progress
|
def progress
|
||||||
|
|
|
@ -54,7 +54,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
|
||||||
it 'adds metric events for incoming and reply emails' do
|
it 'adds metric events for incoming and reply emails' do
|
||||||
metric_transaction = double('Gitlab::Metrics::WebTransaction', increment: true, observe: true)
|
metric_transaction = double('Gitlab::Metrics::WebTransaction', increment: true, observe: true)
|
||||||
allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current).and_return(metric_transaction)
|
allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current).and_return(metric_transaction)
|
||||||
expect(metric_transaction).to receive(:add_event).with(:receive_email_service_desk, anything)
|
expect(metric_transaction).to receive(:add_event).with(:receive_email_service_desk, { handler: 'Gitlab::Email::Handler::ServiceDeskHandler' })
|
||||||
expect(metric_transaction).to receive(:add_event).with(:service_desk_thank_you_email)
|
expect(metric_transaction).to receive(:add_event).with(:service_desk_thank_you_email)
|
||||||
|
|
||||||
receiver.execute
|
receiver.execute
|
||||||
|
|
|
@ -508,282 +508,6 @@ RSpec.describe Member do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.add_user' do
|
|
||||||
%w[project group].each do |source_type|
|
|
||||||
context "when source is a #{source_type}" do
|
|
||||||
let_it_be(:source, reload: true) { create(source_type, :public) }
|
|
||||||
let_it_be(:user) { create(:user) }
|
|
||||||
let_it_be(:admin) { create(:admin) }
|
|
||||||
|
|
||||||
it 'returns a <Source>Member object' do
|
|
||||||
member = described_class.add_user(source, user, :maintainer)
|
|
||||||
|
|
||||||
expect(member).to be_a "#{source_type.classify}Member".constantize
|
|
||||||
expect(member).to be_persisted
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when admin mode is enabled', :enable_admin_mode do
|
|
||||||
it 'sets members.created_by to the given admin current_user' do
|
|
||||||
member = described_class.add_user(source, user, :maintainer, current_user: admin)
|
|
||||||
|
|
||||||
expect(member.created_by).to eq(admin)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when admin mode is disabled' do
|
|
||||||
it 'rejects setting members.created_by to the given admin current_user' do
|
|
||||||
member = described_class.add_user(source, user, :maintainer, current_user: admin)
|
|
||||||
|
|
||||||
expect(member.created_by).to be_nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sets members.expires_at to the given expires_at' do
|
|
||||||
member = described_class.add_user(source, user, :maintainer, expires_at: Date.new(2016, 9, 22))
|
|
||||||
|
|
||||||
expect(member.expires_at).to eq(Date.new(2016, 9, 22))
|
|
||||||
end
|
|
||||||
|
|
||||||
described_class.access_levels.each do |sym_key, int_access_level|
|
|
||||||
it "accepts the :#{sym_key} symbol as access level" do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
member = described_class.add_user(source, user.id, sym_key)
|
|
||||||
|
|
||||||
expect(member.access_level).to eq(int_access_level)
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "accepts the #{int_access_level} integer as access level" do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
member = described_class.add_user(source, user.id, int_access_level)
|
|
||||||
|
|
||||||
expect(member.access_level).to eq(int_access_level)
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with no current_user' do
|
|
||||||
context 'when called with a known user id' do
|
|
||||||
it 'adds the user as a member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, user.id, :maintainer)
|
|
||||||
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with an unknown user id' do
|
|
||||||
it 'adds the user as a member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, non_existing_record_id, :maintainer)
|
|
||||||
|
|
||||||
expect(source.users.reload).not_to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with a user object' do
|
|
||||||
it 'adds the user as a member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, user, :maintainer)
|
|
||||||
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with a requester user object' do
|
|
||||||
before do
|
|
||||||
source.request_access(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'adds the requester as a member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
||||||
|
|
||||||
expect { described_class.add_user(source, user, :maintainer) }
|
|
||||||
.to raise_error(Gitlab::Access::AccessDeniedError)
|
|
||||||
|
|
||||||
expect(source.users.reload).not_to include(user)
|
|
||||||
expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with a known user email' do
|
|
||||||
it 'adds the user as a member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, user.email, :maintainer)
|
|
||||||
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with a known user secondary email' do
|
|
||||||
let(:secondary_email) { create(:email, email: 'secondary@example.com', user: user) }
|
|
||||||
|
|
||||||
it 'adds the user as a member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, secondary_email.email, :maintainer)
|
|
||||||
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with an unknown user email' do
|
|
||||||
it 'creates an invited member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, 'user@example.com', :maintainer)
|
|
||||||
|
|
||||||
expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with an unknown user email starting with a number' do
|
|
||||||
it 'creates an invited member', :aggregate_failures do
|
|
||||||
email_starting_with_number = "#{user.id}_email@example.com"
|
|
||||||
|
|
||||||
described_class.add_user(source, email_starting_with_number, :maintainer)
|
|
||||||
|
|
||||||
expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
|
|
||||||
expect(source.users.reload).not_to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when current_user can update member', :enable_admin_mode do
|
|
||||||
it 'creates the member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, user, :maintainer, current_user: admin)
|
|
||||||
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with a requester user object' do
|
|
||||||
before do
|
|
||||||
source.request_access(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'adds the requester as a member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
||||||
|
|
||||||
described_class.add_user(source, user, :maintainer, current_user: admin)
|
|
||||||
|
|
||||||
expect(source.users.reload).to include(user)
|
|
||||||
expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when current_user cannot update member' do
|
|
||||||
it 'does not create the member' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
|
|
||||||
member = described_class.add_user(source, user, :maintainer, current_user: user)
|
|
||||||
|
|
||||||
expect(source.users.reload).not_to include(user)
|
|
||||||
expect(member).not_to be_persisted
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when called with a requester user object' do
|
|
||||||
before do
|
|
||||||
source.request_access(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not destroy the requester' do
|
|
||||||
expect(source.users).not_to include(user)
|
|
||||||
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
||||||
|
|
||||||
described_class.add_user(source, user, :maintainer, current_user: user)
|
|
||||||
|
|
||||||
expect(source.users.reload).not_to include(user)
|
|
||||||
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when member already exists' do
|
|
||||||
before do
|
|
||||||
source.add_user(user, :developer)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with no current_user' do
|
|
||||||
it 'updates the member' do
|
|
||||||
expect(source.users).to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, user, :maintainer)
|
|
||||||
|
|
||||||
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when current_user can update member', :enable_admin_mode do
|
|
||||||
it 'updates the member' do
|
|
||||||
expect(source.users).to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, user, :maintainer, current_user: admin)
|
|
||||||
|
|
||||||
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when current_user cannot update member' do
|
|
||||||
it 'does not update the member' do
|
|
||||||
expect(source.users).to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(source, user, :maintainer, current_user: user)
|
|
||||||
|
|
||||||
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.add_users' do
|
|
||||||
%w[project group].each do |source_type|
|
|
||||||
context "when source is a #{source_type}" do
|
|
||||||
let_it_be(:source) { create(source_type, :public) }
|
|
||||||
let_it_be(:admin) { create(:admin) }
|
|
||||||
let_it_be(:user1) { create(:user) }
|
|
||||||
let_it_be(:user2) { create(:user) }
|
|
||||||
|
|
||||||
it 'returns a <Source>Member objects' do
|
|
||||||
members = described_class.add_users(source, [user1, user2], :maintainer)
|
|
||||||
|
|
||||||
expect(members).to be_a Array
|
|
||||||
expect(members.size).to eq(2)
|
|
||||||
expect(members.first).to be_a "#{source_type.classify}Member".constantize
|
|
||||||
expect(members.first).to be_persisted
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns an empty array' do
|
|
||||||
members = described_class.add_users(source, [], :maintainer)
|
|
||||||
|
|
||||||
expect(members).to be_a Array
|
|
||||||
expect(members).to be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'supports differents formats' do
|
|
||||||
list = ['joe@local.test', admin, user1.id, user2.id.to_s]
|
|
||||||
|
|
||||||
members = described_class.add_users(source, list, :maintainer)
|
|
||||||
|
|
||||||
expect(members.size).to eq(4)
|
|
||||||
expect(members.first).to be_invite
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#accept_request' do
|
describe '#accept_request' do
|
||||||
let(:member) { create(:project_member, requested_at: Time.current.utc) }
|
let(:member) { create(:project_member, requested_at: Time.current.utc) }
|
||||||
|
|
||||||
|
|
|
@ -47,27 +47,6 @@ RSpec.describe GroupMember do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.access_levels' do
|
|
||||||
it 'returns Gitlab::Access.options_with_owner' do
|
|
||||||
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.add_users' do
|
|
||||||
it 'adds the given users to the given group' do
|
|
||||||
group = create(:group)
|
|
||||||
users = create_list(:user, 2)
|
|
||||||
|
|
||||||
described_class.add_users(
|
|
||||||
group,
|
|
||||||
[users.first.id, users.second],
|
|
||||||
described_class::MAINTAINER
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(group.users).to include(users.first, users.second)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'members notifications', :group
|
it_behaves_like 'members notifications', :group
|
||||||
|
|
||||||
describe '#namespace_id' do
|
describe '#namespace_id' do
|
||||||
|
|
|
@ -23,19 +23,6 @@ RSpec.describe ProjectMember do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.add_user' do
|
|
||||||
it 'adds the user as a member' do
|
|
||||||
user = create(:user)
|
|
||||||
project = create(:project)
|
|
||||||
|
|
||||||
expect(project.users).not_to include(user)
|
|
||||||
|
|
||||||
described_class.add_user(project, user, :maintainer, current_user: project.owner)
|
|
||||||
|
|
||||||
expect(project.users.reload).to include(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#real_source_type' do
|
describe '#real_source_type' do
|
||||||
subject { create(:project_member).real_source_type }
|
subject { create(:project_member).real_source_type }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Members::Groups::CreatorService do
|
||||||
|
it_behaves_like 'member creation' do
|
||||||
|
let_it_be(:source, reload: true) { create(:group, :public) }
|
||||||
|
let_it_be(:member_type) { GroupMember }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.access_levels' do
|
||||||
|
it 'returns Gitlab::Access.options_with_owner' do
|
||||||
|
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Members::Projects::CreatorService do
|
||||||
|
it_behaves_like 'member creation' do
|
||||||
|
let_it_be(:source, reload: true) { create(:project, :public) }
|
||||||
|
let_it_be(:member_type) { ProjectMember }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.access_levels' do
|
||||||
|
it 'returns Gitlab::Access.sym_options' do
|
||||||
|
expect(described_class.access_levels).to eq(Gitlab::Access.sym_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -75,3 +75,259 @@ RSpec.shared_examples '#valid_level_roles' do |entity_name|
|
||||||
expect(presenter.valid_level_roles).to eq(expected_roles)
|
expect(presenter.valid_level_roles).to eq(expected_roles)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples_for "member creation" do
|
||||||
|
let_it_be(:user) { create(:user) }
|
||||||
|
let_it_be(:admin) { create(:admin) }
|
||||||
|
|
||||||
|
describe '#execute' do
|
||||||
|
it 'returns a Member object', :aggregate_failures do
|
||||||
|
member = described_class.new(source, user, :maintainer).execute
|
||||||
|
|
||||||
|
expect(member).to be_a member_type
|
||||||
|
expect(member).to be_persisted
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when admin mode is enabled', :enable_admin_mode do
|
||||||
|
it 'sets members.created_by to the given admin current_user' do
|
||||||
|
member = described_class.new(source, user, :maintainer, current_user: admin).execute
|
||||||
|
|
||||||
|
expect(member.created_by).to eq(admin)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when admin mode is disabled' do
|
||||||
|
it 'rejects setting members.created_by to the given admin current_user' do
|
||||||
|
member = described_class.new(source, user, :maintainer, current_user: admin).execute
|
||||||
|
|
||||||
|
expect(member.created_by).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets members.expires_at to the given expires_at' do
|
||||||
|
member = described_class.new(source, user, :maintainer, expires_at: Date.new(2016, 9, 22)).execute
|
||||||
|
|
||||||
|
expect(member.expires_at).to eq(Date.new(2016, 9, 22))
|
||||||
|
end
|
||||||
|
|
||||||
|
described_class.access_levels.each do |sym_key, int_access_level|
|
||||||
|
it "accepts the :#{sym_key} symbol as access level", :aggregate_failures do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
member = described_class.new(source, user.id, sym_key).execute
|
||||||
|
|
||||||
|
expect(member.access_level).to eq(int_access_level)
|
||||||
|
expect(source.users.reload).to include(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts the #{int_access_level} integer as access level", :aggregate_failures do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
member = described_class.new(source, user.id, int_access_level).execute
|
||||||
|
|
||||||
|
expect(member.access_level).to eq(int_access_level)
|
||||||
|
expect(source.users.reload).to include(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with no current_user' do
|
||||||
|
context 'when called with a known user id' do
|
||||||
|
it 'adds the user as a member' do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, user.id, :maintainer).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).to include(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with an unknown user id' do
|
||||||
|
it 'adds the user as a member' do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, non_existing_record_id, :maintainer).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).not_to include(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with a user object' do
|
||||||
|
it 'adds the user as a member' do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, user, :maintainer).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).to include(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with a requester user object' do
|
||||||
|
before do
|
||||||
|
source.request_access(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds the requester as a member', :aggregate_failures do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new(source, user, :maintainer).execute
|
||||||
|
end.to raise_error(Gitlab::Access::AccessDeniedError)
|
||||||
|
|
||||||
|
expect(source.users.reload).not_to include(user)
|
||||||
|
expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with a known user email' do
|
||||||
|
it 'adds the user as a member' do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, user.email, :maintainer).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).to include(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with an unknown user email' do
|
||||||
|
it 'creates an invited member' do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, 'user@example.com', :maintainer).execute
|
||||||
|
|
||||||
|
expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with an unknown user email starting with a number' do
|
||||||
|
it 'creates an invited member', :aggregate_failures do
|
||||||
|
email_starting_with_number = "#{user.id}_email@example.com"
|
||||||
|
|
||||||
|
described_class.new(source, email_starting_with_number, :maintainer).execute
|
||||||
|
|
||||||
|
expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
|
||||||
|
expect(source.users.reload).not_to include(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current_user can update member', :enable_admin_mode do
|
||||||
|
it 'creates the member' do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, user, :maintainer, current_user: admin).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).to include(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with a requester user object' do
|
||||||
|
before do
|
||||||
|
source.request_access(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds the requester as a member', :aggregate_failures do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
||||||
|
|
||||||
|
described_class.new(source, user, :maintainer, current_user: admin).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).to include(user)
|
||||||
|
expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current_user cannot update member' do
|
||||||
|
it 'does not create the member', :aggregate_failures do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
|
||||||
|
member = described_class.new(source, user, :maintainer, current_user: user).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).not_to include(user)
|
||||||
|
expect(member).not_to be_persisted
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with a requester user object' do
|
||||||
|
before do
|
||||||
|
source.request_access(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not destroy the requester', :aggregate_failures do
|
||||||
|
expect(source.users).not_to include(user)
|
||||||
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
||||||
|
|
||||||
|
described_class.new(source, user, :maintainer, current_user: user).execute
|
||||||
|
|
||||||
|
expect(source.users.reload).not_to include(user)
|
||||||
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when member already exists' do
|
||||||
|
before do
|
||||||
|
source.add_user(user, :developer)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with no current_user' do
|
||||||
|
it 'updates the member' do
|
||||||
|
expect(source.users).to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, user, :maintainer).execute
|
||||||
|
|
||||||
|
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current_user can update member', :enable_admin_mode do
|
||||||
|
it 'updates the member' do
|
||||||
|
expect(source.users).to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, user, :maintainer, current_user: admin).execute
|
||||||
|
|
||||||
|
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current_user cannot update member' do
|
||||||
|
it 'does not update the member' do
|
||||||
|
expect(source.users).to include(user)
|
||||||
|
|
||||||
|
described_class.new(source, user, :maintainer, current_user: user).execute
|
||||||
|
|
||||||
|
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.add_users' do
|
||||||
|
let_it_be(:user1) { create(:user) }
|
||||||
|
let_it_be(:user2) { create(:user) }
|
||||||
|
|
||||||
|
it 'returns a Member objects' do
|
||||||
|
members = described_class.add_users(source, [user1, user2], :maintainer)
|
||||||
|
|
||||||
|
expect(members).to be_a Array
|
||||||
|
expect(members.size).to eq(2)
|
||||||
|
expect(members.first).to be_a member_type
|
||||||
|
expect(members.first).to be_persisted
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
members = described_class.add_users(source, [], :maintainer)
|
||||||
|
|
||||||
|
expect(members).to be_a Array
|
||||||
|
expect(members).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'supports different formats' do
|
||||||
|
list = ['joe@local.test', admin, user1.id, user2.id.to_s]
|
||||||
|
|
||||||
|
members = described_class.add_users(source, list, :maintainer)
|
||||||
|
|
||||||
|
expect(members.size).to eq(4)
|
||||||
|
expect(members.first).to be_invite
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -209,6 +209,23 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
|
||||||
expect { run_rake_task("gitlab:backup:#{task}:create") }.to output(/Dumping /).to_stdout_from_any_process
|
expect { run_rake_task("gitlab:backup:#{task}:create") }.to output(/Dumping /).to_stdout_from_any_process
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'logs the progress to log file' do
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping database ... ")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "[SKIPPED]")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping repositories ...")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping uploads ... ")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping builds ... ")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping artifacts ... ")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping pages ... ")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping lfs objects ... ")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "Dumping container registry images ... ")
|
||||||
|
expect(Gitlab::BackupLogger).to receive(:info).with(message: "done").exactly(7).times
|
||||||
|
|
||||||
|
task_list.each do |task|
|
||||||
|
run_rake_task("gitlab:backup:#{task}:create")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue