Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
025197ea
Commit
025197ea
authored
Mar 04, 2021
by
Doug Stull
Committed by
Etienne Baqué
Mar 04, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor insite service for members api
- streamline code and cleanup.
parent
c2389dcf
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
185 additions
and
115 deletions
+185
-115
app/services/members/invite_service.rb
app/services/members/invite_service.rb
+41
-56
locale/gitlab.pot
locale/gitlab.pot
+12
-6
spec/requests/api/invitations_spec.rb
spec/requests/api/invitations_spec.rb
+6
-6
spec/services/members/invite_service_spec.rb
spec/services/members/invite_service_spec.rb
+126
-47
No files found.
app/services/members/invite_service.rb
View file @
025197ea
...
@@ -2,112 +2,97 @@
...
@@ -2,112 +2,97 @@
module
Members
module
Members
class
InviteService
<
Members
::
BaseService
class
InviteService
<
Members
::
BaseService
DEFAULT_LIMIT
=
100
BlankEmailsError
=
Class
.
new
(
StandardError
)
TooManyEmailsError
=
Class
.
new
(
StandardError
)
attr_reader
:errors
def
initialize
(
*
args
)
super
def
initialize
(
current_user
,
params
)
@current_user
,
@params
=
current_user
,
params
.
dup
@errors
=
{}
@errors
=
{}
@emails
=
params
[
:email
]
&
.
split
(
','
)
&
.
uniq
&
.
flatten
end
end
def
execute
(
source
)
def
execute
(
source
)
return
error
(
s_
(
'Email cannot be blank'
))
if
params
[
:email
].
blank?
validate_emails!
emails
=
params
[
:email
].
split
(
','
).
uniq
.
flatten
@source
=
source
return
error
(
s_
(
"Too many users specified (limit is %{user_limit})"
)
%
{
user_limit:
user_limit
})
if
emails
.
each
(
&
method
(
:process_email
))
user_limit
&&
emails
.
size
>
user_limit
result
rescue
BlankEmailsError
,
TooManyEmailsError
=>
e
emails
.
each
do
|
email
|
error
(
e
.
message
)
next
if
existing_member?
(
source
,
email
)
next
if
existing_invite?
(
source
,
email
)
next
if
existing_request?
(
source
,
email
)
if
existing_user?
(
email
)
add_existing_user_as_member
(
current_user
,
source
,
params
,
email
)
next
end
invite_new_member_and_user
(
current_user
,
source
,
params
,
email
)
end
return
success
unless
errors
.
any?
error
(
errors
)
end
end
private
private
def
invite_new_member_and_user
(
current_user
,
source
,
params
,
email
)
attr_reader
:source
,
:errors
,
:emails
new_member
=
(
source
.
class
.
name
+
'Member'
).
constantize
.
create
(
source_id:
source
.
id
,
user_id:
nil
,
access_level:
params
[
:access_level
],
invite_email:
email
,
created_by_id:
current_user
.
id
,
expires_at:
params
[
:expires_at
])
unless
new_member
.
valid?
&&
new_member
.
persisted?
errors
[
params
[
:email
]]
=
new_member
.
errors
.
full_messages
.
to_sentence
end
end
def
add_existing_user_as_member
(
current_user
,
source
,
params
,
email
)
def
validate_emails!
new_member
=
create_member
(
current_user
,
existing_user
(
email
),
source
,
params
.
merge
({
invite_email:
email
}))
raise
BlankEmailsError
,
s_
(
'AddMember|Email cannot be blank'
)
if
emails
.
blank?
unless
new_member
.
valid?
&&
new_member
.
persisted?
if
user_limit
&&
emails
.
size
>
user_limit
errors
[
email
]
=
new_member
.
errors
.
full_messages
.
to_sentence
raise
TooManyEmailsError
,
s_
(
"AddMember|Too many users specified (limit is %{user_limit})"
)
%
{
user_limit:
user_limit
}
end
end
end
end
def
create_member
(
current_user
,
user
,
source
,
params
)
def
user_limit
source
.
add_user
(
user
,
params
[
:access_level
],
current_user:
current_user
,
expires_at:
params
[
:expires_at
])
limit
=
params
.
fetch
(
:limit
,
Members
::
CreateService
::
DEFAULT_LIMIT
)
limit
<
0
?
nil
:
limit
end
end
def
user_limit
def
process_email
(
email
)
limit
=
params
.
fetch
(
:limit
,
DEFAULT_LIMIT
)
return
if
existing_member?
(
email
)
return
if
existing_invite?
(
email
)
return
if
existing_request?
(
email
)
limit
&&
limit
<
0
?
nil
:
limit
add_member
(
email
)
end
end
def
existing_member?
(
source
,
email
)
def
existing_member?
(
email
)
existing_member
=
source
.
members
.
with_user_by_email
(
email
).
exists?
existing_member
=
source
.
members
.
with_user_by_email
(
email
).
exists?
if
existing_member
if
existing_member
errors
[
email
]
=
"Already a member of
#{
source
.
name
}
"
errors
[
email
]
=
s_
(
"AddMember|Already a member of %{source_name}"
)
%
{
source_name:
source
.
name
}
return
true
return
true
end
end
false
false
end
end
def
existing_invite?
(
source
,
email
)
def
existing_invite?
(
email
)
existing_invite
=
source
.
members
.
search_invite_email
(
email
).
exists?
existing_invite
=
source
.
members
.
search_invite_email
(
email
).
exists?
if
existing_invite
if
existing_invite
errors
[
email
]
=
"Member already invited to
#{
source
.
name
}
"
errors
[
email
]
=
s_
(
"AddMember|Member already invited to %{source_name}"
)
%
{
source_name:
source
.
name
}
return
true
return
true
end
end
false
false
end
end
def
existing_request?
(
source
,
email
)
def
existing_request?
(
email
)
existing_request
=
source
.
requesters
.
with_user_by_email
(
email
).
exists?
existing_request
=
source
.
requesters
.
with_user_by_email
(
email
).
exists?
if
existing_request
if
existing_request
errors
[
email
]
=
"Member cannot be invited because they already requested to join
#{
source
.
name
}
"
errors
[
email
]
=
s_
(
"AddMember|Member cannot be invited because they already requested to join %{source_name}"
)
%
{
source_name:
source
.
name
}
return
true
return
true
end
end
false
false
end
end
def
existing_user
(
email
)
def
add_member
(
email
)
User
.
find_by_email
(
email
)
new_member
=
source
.
add_user
(
email
,
params
[
:access_level
],
current_user:
current_user
,
expires_at:
params
[
:expires_at
])
errors
[
email
]
=
new_member
.
errors
.
full_messages
.
to_sentence
if
new_member
.
invalid?
end
end
def
existing_user?
(
email
)
def
result
existing_user
(
email
).
present?
if
errors
.
any?
error
(
errors
)
else
success
end
end
end
end
end
end
end
locale/gitlab.pot
View file @
025197ea
...
@@ -1951,9 +1951,21 @@ msgstr ""
...
@@ -1951,9 +1951,21 @@ msgstr ""
msgid "AddContextCommits|Add/remove"
msgid "AddContextCommits|Add/remove"
msgstr ""
msgstr ""
msgid "AddMember|Already a member of %{source_name}"
msgstr ""
msgid "AddMember|Email cannot be blank"
msgstr ""
msgid "AddMember|Invite limit of %{daily_invites} per day exceeded"
msgid "AddMember|Invite limit of %{daily_invites} per day exceeded"
msgstr ""
msgstr ""
msgid "AddMember|Member already invited to %{source_name}"
msgstr ""
msgid "AddMember|Member cannot be invited because they already requested to join %{source_name}"
msgstr ""
msgid "AddMember|No users specified."
msgid "AddMember|No users specified."
msgstr ""
msgstr ""
...
@@ -11050,9 +11062,6 @@ msgstr ""
...
@@ -11050,9 +11062,6 @@ msgstr ""
msgid "Email address to use for Support Desk"
msgid "Email address to use for Support Desk"
msgstr ""
msgstr ""
msgid "Email cannot be blank"
msgstr ""
msgid "Email could not be sent"
msgid "Email could not be sent"
msgstr ""
msgstr ""
...
@@ -31261,9 +31270,6 @@ msgstr ""
...
@@ -31261,9 +31270,6 @@ msgstr ""
msgid "Too many projects enabled. You will need to manage them via the console or the API."
msgid "Too many projects enabled. You will need to manage them via the console or the API."
msgstr ""
msgstr ""
msgid "Too many users specified (limit is %{user_limit})"
msgstr ""
msgid "Too much data"
msgid "Too much data"
msgstr ""
msgstr ""
...
...
spec/requests/api/invitations_spec.rb
View file @
025197ea
...
@@ -3,14 +3,14 @@
...
@@ -3,14 +3,14 @@
require
'spec_helper'
require
'spec_helper'
RSpec
.
describe
API
::
Invitations
do
RSpec
.
describe
API
::
Invitations
do
let
(
:maintainer
)
{
create
(
:user
,
username:
'maintainer_user'
)
}
let
_it_be
(
:maintainer
)
{
create
(
:user
,
username:
'maintainer_user'
)
}
let
(
:developer
)
{
create
(
:user
)
}
let
_it_be
(
:developer
)
{
create
(
:user
)
}
let
(
:access_requester
)
{
create
(
:user
)
}
let
_it_be
(
:access_requester
)
{
create
(
:user
)
}
let
(
:stranger
)
{
create
(
:user
)
}
let
_it_be
(
:stranger
)
{
create
(
:user
)
}
let
(
:email
)
{
'email1@example.com'
}
let
(
:email
)
{
'email1@example.com'
}
let
(
:email2
)
{
'email2@example.com'
}
let
(
:email2
)
{
'email2@example.com'
}
let
(
:project
)
do
let
_it_be
(
:project
)
do
create
(
:project
,
:public
,
creator_id:
maintainer
.
id
,
namespace:
maintainer
.
namespace
)
do
|
project
|
create
(
:project
,
:public
,
creator_id:
maintainer
.
id
,
namespace:
maintainer
.
namespace
)
do
|
project
|
project
.
add_developer
(
developer
)
project
.
add_developer
(
developer
)
project
.
add_maintainer
(
maintainer
)
project
.
add_maintainer
(
maintainer
)
...
@@ -18,7 +18,7 @@ RSpec.describe API::Invitations do
...
@@ -18,7 +18,7 @@ RSpec.describe API::Invitations do
end
end
end
end
let
!
(
:group
)
do
let
_it_be
(
:group
,
reload:
true
)
do
create
(
:group
,
:public
)
do
|
group
|
create
(
:group
,
:public
)
do
|
group
|
group
.
add_developer
(
developer
)
group
.
add_developer
(
developer
)
group
.
add_owner
(
maintainer
)
group
.
add_owner
(
maintainer
)
...
...
spec/services/members/invite_service_spec.rb
View file @
025197ea
...
@@ -2,76 +2,155 @@
...
@@ -2,76 +2,155 @@
require
'spec_helper'
require
'spec_helper'
RSpec
.
describe
Members
::
InviteService
do
RSpec
.
describe
Members
::
InviteService
,
:aggregate_failures
do
let
(
:project
)
{
create
(
:project
)
}
let_it_be
(
:project
)
{
create
(
:project
)
}
let
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:user
)
{
project
.
owner
}
let
(
:project_user
)
{
create
(
:user
)
}
let_it_be
(
:project_user
)
{
create
(
:user
)
}
let
(
:params
)
{
{}
}
before
do
let
(
:base_params
)
{
{
access_level:
Gitlab
::
Access
::
GUEST
}
}
project
.
add_maintainer
(
user
)
subject
(
:result
)
{
described_class
.
new
(
user
,
base_params
.
merge
(
params
)).
execute
(
project
)
}
context
'when email is previously unused by current members'
do
let
(
:params
)
{
{
email:
'email@example.org'
}
}
it
'successfully creates a member'
do
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
1
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
end
end
end
it
'adds an existing user to members'
do
context
'when emails are passed as an array'
do
params
=
{
email:
project_user
.
email
.
to_s
,
access_level:
Gitlab
::
Access
::
GUEST
}
let
(
:params
)
{
{
email:
%w[email@example.org email2@example.org]
}
}
result
=
described_class
.
new
(
user
,
params
).
execute
(
project
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
it
'successfully creates members'
do
expect
(
project
.
users
).
to
include
project_user
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
2
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
end
end
end
it
'creates a new user for an unknown email address'
do
context
'when emails are passed as an empty string'
do
params
=
{
email:
'email@example.org'
,
access_level:
Gitlab
::
Access
::
GUEST
}
let
(
:params
)
{
{
email:
''
}
}
result
=
described_class
.
new
(
user
,
params
).
execute
(
project
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
it
'returns an error'
do
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
eq
(
'Email cannot be blank'
)
end
end
end
it
'limits the number of emails to 100'
do
context
'when email param is not included'
do
emails
=
Array
.
new
(
101
).
map
{
|
n
|
"email
#{
n
}
@example.com"
}
it
'returns an error'
do
params
=
{
email:
emails
,
access_level:
Gitlab
::
Access
::
GUEST
}
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
eq
(
'Email cannot be blank'
)
end
end
result
=
described_class
.
new
(
user
,
params
).
execute
(
project
)
context
'when email is not a valid email'
do
let
(
:params
)
{
{
email:
'_bogus_'
}
}
expect
(
result
[
:status
]).
to
eq
(
:error
)
it
'returns an error'
do
expect
(
result
[
:message
]).
to
eq
(
'Too many users specified (limit is 100)'
)
expect
{
result
}.
not_to
change
(
ProjectMember
,
:count
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
][
'_bogus_'
]).
to
eq
(
"Invite email is invalid"
)
end
end
end
it
'does not invite an invalid email'
do
context
'when duplicate email addresses are passed'
do
params
=
{
email:
project_user
.
id
.
to_s
,
access_level:
Gitlab
::
Access
::
GUEST
}
let
(
:params
)
{
{
email:
'email@example.org,email@example.org'
}
}
result
=
described_class
.
new
(
user
,
params
).
execute
(
project
)
it
'only creates one member per unique address'
do
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
1
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
end
end
expect
(
result
[
:status
]).
to
eq
(
:error
)
context
'when observing email limits'
do
expect
(
result
[
:message
][
project_user
.
id
.
to_s
]).
to
eq
(
"Invite email is invalid"
)
let_it_be
(
:emails
)
{
Array
(
1
..
101
).
map
{
|
n
|
"email
#{
n
}
@example.com"
}
}
expect
(
project
.
users
).
not_to
include
project_user
context
'when over the allowed default limit of emails'
do
let
(
:params
)
{
{
email:
emails
}
}
it
'limits the number of emails to 100'
do
expect
{
result
}.
not_to
change
(
ProjectMember
,
:count
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
eq
(
'Too many users specified (limit is 100)'
)
end
end
context
'when over the allowed custom limit of emails'
do
let
(
:params
)
{
{
email:
'email@example.org,email2@example.org'
,
limit:
1
}
}
it
'limits the number of emails to the limit supplied'
do
expect
{
result
}.
not_to
change
(
ProjectMember
,
:count
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
eq
(
'Too many users specified (limit is 1)'
)
end
end
context
'when limit allowed is disabled via limit param'
do
let
(
:params
)
{
{
email:
emails
,
limit:
-
1
}
}
it
'does not limit number of emails'
do
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
101
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
end
end
end
end
it
'does not invite to an invalid access level'
do
context
'when email belongs to an existing user'
do
params
=
{
email:
project_user
.
email
,
access_level:
-
1
}
let
(
:params
)
{
{
email:
project_user
.
email
}
}
result
=
described_class
.
new
(
user
,
params
).
execute
(
project
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
it
'adds an existing user to members'
do
expect
(
result
[
:message
][
project_user
.
email
]).
to
eq
(
"Access level is not included in the list"
)
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
1
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
project
.
users
).
to
include
project_user
end
end
end
it
'does not add a member with an existing invite
'
do
context
'when access level is not valid
'
do
invited_member
=
create
(
:project_member
,
:invited
,
project:
project
)
let
(
:params
)
{
{
email:
project_user
.
email
,
access_level:
-
1
}
}
params
=
{
email:
invited_member
.
invite_email
,
it
'returns an error'
do
access_level:
Gitlab
::
Access
::
GUEST
}
expect
{
result
}.
not_to
change
(
ProjectMember
,
:count
)
result
=
described_class
.
new
(
user
,
params
).
execute
(
project
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
][
project_user
.
email
]).
to
eq
(
"Access level is not included in the list"
)
end
end
context
'when invite already exists for an included email'
do
let!
(
:invited_member
)
{
create
(
:project_member
,
:invited
,
project:
project
)
}
let
(
:params
)
{
{
email:
"
#{
invited_member
.
invite_email
}
,
#{
project_user
.
email
}
"
}
}
expect
(
result
[
:status
]).
to
eq
(
:error
)
it
'adds new email and returns an error for the already invited email'
do
expect
(
result
[
:message
][
invited_member
.
invite_email
]).
to
eq
(
"Member already invited to
#{
project
.
name
}
"
)
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
1
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
][
invited_member
.
invite_email
]).
to
eq
(
"Member already invited to
#{
project
.
name
}
"
)
expect
(
project
.
users
).
to
include
project_user
end
end
end
it
'does not add a member with an access_request'
do
context
'when access request already exists for an included email'
do
requested_member
=
create
(
:project_member
,
:access_request
,
project:
project
)
let!
(
:requested_member
)
{
create
(
:project_member
,
:access_request
,
project:
project
)
}
let
(
:params
)
{
{
email:
"
#{
requested_member
.
user
.
email
}
,
#{
project_user
.
email
}
"
}
}
it
'adds new email and returns an error for the already invited email'
do
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
1
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
][
requested_member
.
user
.
email
])
.
to
eq
(
"Member cannot be invited because they already requested to join
#{
project
.
name
}
"
)
expect
(
project
.
users
).
to
include
project_user
end
end
params
=
{
email:
requested_member
.
user
.
email
,
context
'when email is already a member on the project'
do
access_level:
Gitlab
::
Access
::
GUEST
}
let!
(
:existing_member
)
{
create
(
:project_member
,
:guest
,
project:
project
)
}
result
=
described_class
.
new
(
user
,
params
).
execute
(
project
)
let
(
:params
)
{
{
email:
"
#{
existing_member
.
user
.
email
}
,
#{
project_user
.
email
}
"
}
}
expect
(
result
[
:status
]).
to
eq
(
:error
)
it
'adds new email and returns an error for the already invited email'
do
expect
(
result
[
:message
][
requested_member
.
user
.
email
]).
to
eq
(
"Member cannot be invited because they already requested to join
#{
project
.
name
}
"
)
expect
{
result
}.
to
change
(
ProjectMember
,
:count
).
by
(
1
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
][
existing_member
.
user
.
email
]).
to
eq
(
"Already a member of
#{
project
.
name
}
"
)
expect
(
project
.
users
).
to
include
project_user
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment