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 (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.
|
||||
- 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.
|
||||
- [ ] 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.
|
||||
# Also see config/initializers/fog_core_patch.rb.
|
||||
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-openstack', '~> 1.0'
|
||||
gem 'fog-rackspace', '~> 0.1.1'
|
||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -412,6 +412,12 @@ GEM
|
|||
excon (~> 0.58)
|
||||
formatador (~> 0.2)
|
||||
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-core
|
||||
multi_json (~> 1.10)
|
||||
|
@ -475,13 +481,6 @@ GEM
|
|||
fog-json (~> 1.2.0)
|
||||
mime-types
|
||||
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)
|
||||
actionpack (>= 5.0.0, < 7.0.0)
|
||||
activesupport (>= 5.0.0, < 7.0.0)
|
||||
|
@ -531,7 +530,7 @@ GEM
|
|||
retriable (>= 2.0, < 4.0)
|
||||
rexml
|
||||
signet (~> 0.12)
|
||||
google-cloud-env (1.4.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-protobuf (3.17.1)
|
||||
googleapis-common-protos-types (1.0.6)
|
||||
|
@ -1474,6 +1473,7 @@ DEPENDENCIES
|
|||
fog-aliyun (~> 0.3)
|
||||
fog-aws (~> 3.9)
|
||||
fog-core (= 2.1.0)
|
||||
fog-google (~> 1.15)
|
||||
fog-local (~> 0.6)
|
||||
fog-openstack (~> 1.0)
|
||||
fog-rackspace (~> 0.1.1)
|
||||
|
@ -1489,7 +1489,6 @@ DEPENDENCIES
|
|||
gitlab-dangerfiles (~> 2.1.2)
|
||||
gitlab-experiment (~> 0.5.4)
|
||||
gitlab-fog-azure-rm (~> 1.1.1)
|
||||
gitlab-fog-google (~> 1.13)
|
||||
gitlab-labkit (~> 0.18.0)
|
||||
gitlab-license (~> 1.5)
|
||||
gitlab-mail_room (~> 0.0.9)
|
||||
|
|
|
@ -155,6 +155,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-deprecated-functional-template -->
|
||||
<template functional>
|
||||
<div :class="$options.classNameMap(props)" class="diff-grid-row diff-tr line_holder">
|
||||
<div
|
||||
|
|
|
@ -296,7 +296,7 @@ class Group < Namespace
|
|||
end
|
||||
|
||||
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,
|
||||
users,
|
||||
access_level,
|
||||
|
@ -306,14 +306,13 @@ class Group < Namespace
|
|||
end
|
||||
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil, ldap: false)
|
||||
GroupMember.add_user(
|
||||
self,
|
||||
user,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at,
|
||||
ldap: ldap
|
||||
)
|
||||
Members::Groups::CreatorService.new(self, # rubocop:todo CodeReuse/ServiceClass
|
||||
user,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at,
|
||||
ldap: ldap)
|
||||
.execute
|
||||
end
|
||||
|
||||
def add_guest(user, current_user = nil)
|
||||
|
|
|
@ -232,140 +232,9 @@ class Member < ApplicationRecord
|
|||
find_by(invite_token: invite_token)
|
||||
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)
|
||||
Devise.email_regexp.match?(email)
|
||||
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
|
||||
|
||||
def real_source_type
|
||||
|
|
|
@ -32,10 +32,6 @@ class GroupMember < Member
|
|||
Gitlab::Access.options_with_owner
|
||||
end
|
||||
|
||||
def self.access_levels
|
||||
Gitlab::Access.sym_options_with_owner
|
||||
end
|
||||
|
||||
def self.pluck_user_ids
|
||||
pluck(:user_id)
|
||||
end
|
||||
|
|
|
@ -48,7 +48,7 @@ class ProjectMember < Member
|
|||
project_ids.each do |project_id|
|
||||
project = Project.find(project_id)
|
||||
|
||||
add_users(
|
||||
Members::Projects::CreatorService.add_users( # rubocop:todo CodeReuse/ServiceClass
|
||||
project,
|
||||
users,
|
||||
access_level,
|
||||
|
@ -80,12 +80,6 @@ class ProjectMember < Member
|
|||
def access_level_roles
|
||||
Gitlab::Access.options
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_update_member?(current_user, member)
|
||||
super || (member.owner? && member.new_record?)
|
||||
end
|
||||
end
|
||||
|
||||
def project
|
||||
|
|
|
@ -42,7 +42,7 @@ class ProjectTeam
|
|||
end
|
||||
|
||||
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,
|
||||
users,
|
||||
access_level,
|
||||
|
@ -52,13 +52,12 @@ class ProjectTeam
|
|||
end
|
||||
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil)
|
||||
ProjectMember.add_user(
|
||||
project,
|
||||
user,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at
|
||||
)
|
||||
Members::Projects::CreatorService.new(project, # rubocop:todo CodeReuse/ServiceClass
|
||||
user,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at)
|
||||
.execute
|
||||
end
|
||||
|
||||
# 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!
|
||||
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
|
||||
valid, invalid = invites.partition { |email| Member.valid_email?(email) }
|
||||
@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`
|
||||
parameters](../../integration/omniauth.md#initial-omniauth-configuration).
|
||||
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).
|
||||
|
||||
|
@ -330,7 +330,7 @@ gitlab_rails['omniauth_providers'] = [
|
|||
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).
|
||||
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
|
||||
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):
|
||||
allow GitLab users to create a new merge request by sending an email to a
|
||||
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.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Advanced Issues
|
||||
|
|
|
@ -20,7 +20,7 @@ POST /import/github
|
|||
| `repo_id` | integer | yes | GitHub repository ID |
|
||||
| `new_name` | string | no | New repository name |
|
||||
| `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
|
||||
curl --request POST \
|
||||
|
@ -31,7 +31,8 @@ curl --request POST \
|
|||
"personal_access_token": "aBc123abC12aBc123abC12abC123+_A/c123",
|
||||
"repo_id": "12345",
|
||||
"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:
|
||||
|
||||
| Value | Description |
|
||||
| ------------------- | ------------------------ |
|
||||
| `access_level_asc` | Access level, ascending |
|
||||
| `access_level_desc` | Access level, descending |
|
||||
| `last_joined` | Last joined |
|
||||
| `name_asc` | Name, ascending |
|
||||
| `name_desc` | Name, descending |
|
||||
| `oldest_joined` | Oldest joined |
|
||||
| `oldest_sign_in` | Oldest sign in |
|
||||
| `recent_sign_in` | Recent sign in |
|
||||
| Value | Description |
|
||||
| ----------------------- | ---------------------------- |
|
||||
| `access_level_asc` | Access level, ascending |
|
||||
| `access_level_desc` | Access level, descending |
|
||||
| `last_joined` | Last joined |
|
||||
| `name_asc` | Name, ascending |
|
||||
| `name_desc` | Name, descending |
|
||||
| `oldest_joined` | Oldest joined |
|
||||
| `oldest_sign_in` | Oldest 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
|
||||
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. |
|
||||
| `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` | 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. |
|
||||
| `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. |
|
||||
|
@ -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. |
|
||||
| `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_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_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. |
|
||||
|
|
|
@ -1352,7 +1352,7 @@ Parameters:
|
|||
|
||||
- `id` (required) - ID of specified user
|
||||
- `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
|
||||
|
||||
|
|
|
@ -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 ...
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
### 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
|
||||
|
||||
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))
|
||||
|
||||
## email
|
||||
|
||||
Do not use **e-mail** with a hyphen. When plural, use **emails** or **email messages**.
|
||||
|
||||
## 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.
|
||||
|
|
|
@ -243,7 +243,7 @@ Ruby on Rails applications. In GitLab, Sidekiq performs the heavy
|
|||
lifting of many activities, including:
|
||||
|
||||
- Updating merge requests after a push.
|
||||
- Sending e-mails.
|
||||
- Sending email messages.
|
||||
- Updating user authorizations.
|
||||
- Processing CI builds and pipelines.
|
||||
|
||||
|
@ -276,7 +276,7 @@ in a timely manner:
|
|||
this to `ProcessCommitWorker`.
|
||||
- Redistribute/gerrymander Sidekiq processes by queue
|
||||
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).
|
||||
- Optimize jobs. Eliminating unnecessary work, reducing network calls
|
||||
(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
|
||||
rejects their account with a URI that contains "e-mail is invalid" then your
|
||||
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`.
|
||||
|
||||
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.
|
||||
> - [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,
|
||||
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.
|
||||
|
|
|
@ -14,7 +14,7 @@ reports in the Admin Area.
|
|||
|
||||
## 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 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)
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
the author. The importer appends a note in the comment to mark the original
|
||||
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
|
||||
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.
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ might be slightly different. Follow the
|
|||
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:
|
||||
|
||||
```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)
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ To export a project and its data, follow these steps:
|
|||
|
||||
![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:
|
||||
|
||||
![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
|
||||
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
|
||||
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
|
||||
|
||||
def metrics_params
|
||||
super.merge(project: project&.full_path)
|
||||
end
|
||||
|
||||
def metrics_event
|
||||
:receive_email_service_desk
|
||||
end
|
||||
|
|
|
@ -282,6 +282,7 @@ namespace :gitlab do
|
|||
|
||||
def puts_time(msg)
|
||||
progress.puts "#{Time.now} -- #{msg}"
|
||||
Gitlab::BackupLogger.info(message: "#{Rainbow.uncolor(msg)}")
|
||||
end
|
||||
|
||||
def progress
|
||||
|
|
|
@ -54,7 +54,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
|
|||
it 'adds metric events for incoming and reply emails' do
|
||||
metric_transaction = double('Gitlab::Metrics::WebTransaction', increment: true, observe: true)
|
||||
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)
|
||||
|
||||
receiver.execute
|
||||
|
|
|
@ -508,282 +508,6 @@ RSpec.describe Member do
|
|||
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
|
||||
let(:member) { create(:project_member, requested_at: Time.current.utc) }
|
||||
|
||||
|
|
|
@ -47,27 +47,6 @@ RSpec.describe GroupMember do
|
|||
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
|
||||
|
||||
describe '#namespace_id' do
|
||||
|
|
|
@ -23,19 +23,6 @@ RSpec.describe ProjectMember do
|
|||
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
|
||||
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)
|
||||
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
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in New Issue