Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
58f08aa9e328
Commit
58f08aa9
authored
Mar 14, 2016
by
David Alexander
Browse files
Initial implementation of user access request to projects
parent
94943c091c89
Changes
19
Hide whitespace changes
Inline
Side-by-side
app/controllers/projects/project_members_controller.rb
View file @
58f08aa9
class
Projects::ProjectMembersController
<
Projects
::
ApplicationController
# Authorize
before_action
:authorize_admin_project_member!
,
except:
[
:
leave
,
:index
]
before_action
:authorize_admin_project_member!
,
except:
[
:
index
,
:leave
,
:request_access
]
def
index
@project_members
=
@project
.
project_members
...
...
@@ -4,7 +4,7 @@
def
index
@project_members
=
@project
.
project_members
@project_members
=
@project_members
.
non_
invite
unless
can?
(
current_user
,
:admin_project
,
@project
)
@project_members
=
@project_members
.
non_
pending
unless
can?
(
current_user
,
:admin_project
,
@project
)
if
params
[
:search
].
present?
users
=
@project
.
users
.
search
(
params
[
:search
]).
to_a
...
...
@@ -93,6 +93,33 @@
end
end
def
request_access
redirect_path
=
namespace_project_path
(
@project
.
namespace
,
@project
)
# current_user
# @project
@project_member
=
ProjectMember
.
new
(
source:
@project
,
access_level:
ProjectMember
::
DEVELOPER
,
user_id:
current_user
.
id
,
created_by_id:
current_user
.
id
,
requested:
true
)
@project_member
.
save!
redirect_to
redirect_path
,
notice:
'Your request for access has been queued for review.'
end
def
approval
@project_member
=
@project
.
project_members
.
find
(
params
[
:id
])
return
render_403
unless
can?
(
current_user
,
:update_project_member
,
@project_member
)
@project_member
.
requested
=
nil
@project_member
.
save!
respond_to
do
|
format
|
format
.
html
do
redirect_to
namespace_project_project_members_path
(
@project
.
namespace
,
@project
)
end
format
.
js
{
render
nothing:
true
}
end
end
def
apply_import
source_project
=
Project
.
find
(
params
[
:source_project_id
])
...
...
app/helpers/projects_helper.rb
View file @
58f08aa9
module
ProjectsHelper
def
remove_from_project_team_message
(
project
,
member
)
if
member
.
user
"You are going to remove
#{
member
.
user
.
name
}
from
#{
project
.
name
}
project team. Are you sure?"
if
!
member
.
user
"You are going to revoke the invitation for
#{
member
.
invite_email
}
to join
#{
project
.
name
}
project team. Are you sure?"
elsif
member
.
request?
"You are going to deny
#{
member
.
user
.
name
}
's request to join
#{
project
.
name
}
project team. Are you sure?"
else
...
...
@@ -5,5 +7,5 @@
else
"You are going to re
voke the invitation for
#{
member
.
invite_email
}
to join
#{
project
.
name
}
project team. Are you sure?"
"You are going to re
move
#{
member
.
user
.
name
}
from
#{
project
.
name
}
project team. Are you sure?"
end
end
...
...
@@ -7,6 +9,10 @@
end
end
def
approve_for_project_team_message
(
project
,
member
)
"You are going to approve
#{
member
.
user
.
name
}
's request for
#{
member
.
human_access
}
access to the
#{
project
.
name
}
project team. Are you sure?"
end
def
link_to_project
(
project
)
link_to
[
project
.
namespace
.
becomes
(
Namespace
),
project
],
title:
h
(
project
.
name
)
do
title
=
content_tag
(
:span
,
project
.
name
,
class:
'project-name'
)
...
...
app/mailers/emails/projects.rb
View file @
58f08aa9
...
...
@@ -11,6 +11,48 @@
subject:
subject
(
"Access to project was granted"
))
end
def
project_member_requested_access
(
project_member_id
)
@project_member
=
ProjectMember
.
find
project_member_id
@project
=
@project_member
.
project
@target_url
=
namespace_project_url
(
@project
.
namespace
,
@project
)
project_admins
=
ProjectMember
.
in_project
(
@project
)
.
where
(
access_level:
[
Gitlab
::
Access
::
OWNER
,
Gitlab
::
Access
::
MASTER
])
.
pluck
(
:notification_email
)
project_admins
.
each
do
|
address
|
mail
(
to:
address
,
subject:
subject
(
"Request to join project:
#{
@project
.
name_with_namespace
}
"
))
end
end
def
project_request_access_accepted_email
(
project_member_id
)
@project_member
=
ProjectMember
.
find
project_member_id
return
if
@project_member
.
created_by
.
nil?
@project
=
@project_member
.
project
@target_url
=
namespace_project_url
(
@project
.
namespace
,
@project
)
@current_user
=
@project_member
.
created_by
mail
(
to:
@project_member
.
created_by
.
notification_email
,
subject:
subject
(
'Request for access granted'
))
end
def
project_request_access_declined_email
(
project_member_id
)
@project_member
=
ProjectMember
.
find
project_member_id
return
if
@project_member
.
created_by
.
nil?
@project
=
@project_member
.
project
@target_url
=
namespace_project_url
(
@project
.
namespace
,
@project
)
@current_user
=
@project_member
.
created_by
mail
(
to:
@project_member
.
created_by
.
notification_email
,
subject:
subject
(
'Request for access declined'
))
end
def
project_member_invited_email
(
project_member_id
,
token
)
@project_member
=
ProjectMember
.
find
project_member_id
@project
=
@project_member
.
project
...
...
app/models/ability.rb
View file @
58f08aa9
...
...
@@ -153,7 +153,7 @@
RequestStore
.
store
[
key
]
||=
begin
# Push abilities on the users team role
rules
.
push
(
*
project_team_rules
(
project
.
team
,
user
))
rules
.
push
(
*
project_team_rules
(
project
.
team
,
user
))
unless
project
.
team
.
pending?
(
user
)
if
project
.
owner
==
user
||
(
project
.
group
&&
project
.
group
.
has_owner?
(
user
))
||
...
...
app/models/member.rb
View file @
58f08aa9
...
...
@@ -27,7 +27,12 @@
}
scope
:invite
,
->
{
where
(
user_id:
nil
)
}
scope
:non_invite
,
->
{
where
(
"user_id IS NOT NULL"
)
}
scope
:non_invite
,
->
{
where
(
'user_id IS NOT NULL'
)
}
scope
:request
,
->
{
where
(
requested:
true
)
}
scope
:non_request
,
->
{
where
(
requested:
nil
)
}
scope
:pending
,
->
{
where
(
"user_id IS NULL OR requested"
)
}
scope
:non_pending
,
->
{
self
.
non_invite
.
non_request
}
scope
:guests
,
->
{
where
(
access_level:
GUEST
)
}
scope
:reporters
,
->
{
where
(
access_level:
REPORTER
)
}
scope
:developers
,
->
{
where
(
access_level:
DEVELOPER
)
}
...
...
@@ -35,4 +40,5 @@
scope
:owners
,
->
{
where
(
access_level:
OWNER
)
}
before_validation
:generate_invite_token
,
on: :create
,
if:
->
(
member
)
{
member
.
invite_email
.
present?
}
after_create
:send_invite
,
if: :invite?
...
...
@@ -38,8 +44,12 @@
after_create
:send_invite
,
if: :invite?
after_create
:create_notification_setting
,
unless: :invite?
after_create
:post_create_hook
,
unless: :invite?
after_update
:post_update_hook
,
unless: :invite?
after_destroy
:post_destroy_hook
,
unless: :invite?
after_create
:send_request_access
,
if: :request?
after_create
:create_notification_setting
,
unless: :pending?
after_create
:post_create_hook
,
unless: :pending?
after_update
:post_update_hook
,
unless: :pending?
after_destroy
:post_destroy_hook
,
unless: :pending?
delegate
:name
,
:username
,
:email
,
to: :user
,
prefix:
true
...
...
@@ -96,7 +106,15 @@
end
end
def
pending?
request?
||
invite?
end
def
request?
self
.
requested
end
def
invite?
self
.
invite_token
.
present?
end
...
...
@@ -99,7 +117,27 @@
def
invite?
self
.
invite_token
.
present?
end
def
accept_request_access!
return
false
unless
request?
self
.
request
=
false
saved
=
self
.
save
after_accept_request_access
if
saved
saved
end
def
decline_request_access!
return
false
unless
request?
destroyed
=
self
.
destroy
after_decline_request_access
if
destroyed
destroyed
end
def
accept_invite!
(
new_user
)
return
false
unless
invite?
...
...
@@ -153,6 +191,10 @@
private
def
send_request_access
# override in subclass
end
def
send_invite
# override in subclass
end
...
...
@@ -169,6 +211,14 @@
system_hook_service
.
execute_hooks_for
(
self
,
:destroy
)
end
def
after_accept_request_access
post_create_hook
end
def
after_decline_request_access
# override in subclass
end
def
after_accept_invite
post_create_hook
end
...
...
app/models/members/project_member.rb
View file @
58f08aa9
...
...
@@ -107,6 +107,12 @@
user
.
todos
.
where
(
project_id:
source_id
).
destroy_all
if
user
end
def
send_request_access
notification_service
.
request_access_project_member
(
self
)
super
end
def
send_invite
notification_service
.
invite_project_member
(
self
,
@raw_invite_token
)
...
...
@@ -136,6 +142,18 @@
super
end
def
after_accept_request_access
notification_service
.
accept_project_request_access
(
self
)
super
end
def
after_decline_request_access
notification_service
.
decline_project_request_access
(
self
)
super
end
def
after_accept_invite
notification_service
.
accept_project_invite
(
self
)
...
...
app/models/project_team.rb
View file @
58f08aa9
...
...
@@ -115,6 +115,12 @@
false
end
def
pending?
(
user
)
project
.
project_members
.
each
do
|
member
|
return
member
.
pending?
if
member
.
user_id
==
user
.
id
end
end
def
guest?
(
user
)
max_member_access
(
user
.
id
)
==
Gitlab
::
Access
::
GUEST
end
...
...
app/services/notification_service.rb
View file @
58f08aa9
...
...
@@ -173,6 +173,18 @@
end
end
def
request_access_project_member
(
project_member
)
mailer
.
project_member_requested_access
(
project_member
.
id
).
deliver_later
end
def
accept_project_request_access
(
project_member
)
mailer
.
project_request_access_accepted_email
(
project_member
.
id
).
deliver_later
end
def
decline_project_request_access
(
project_member
)
mailer
.
project_request_access_declined_email
(
project_member
.
id
).
deliver_later
end
def
invite_project_member
(
project_member
,
token
)
mailer
.
project_member_invited_email
(
project_member
.
id
,
token
).
deliver_later
end
...
...
app/views/layouts/nav/_project.html.haml
View file @
58f08aa9
...
...
@@ -8,6 +8,19 @@
=
icon
(
'caret-down'
)
%ul
.dropdown-menu.dropdown-menu-align-right
=
render
'layouts/nav/project_settings'
-
if
access
%li
=
link_to
leave_namespace_project_project_members_path
(
@project
.
namespace
,
@project
),
data:
{
confirm:
leave_project_message
(
@project
)
},
method: :delete
,
title:
'Leave project'
do
Leave Project
-
else
=
link_to
request_access_namespace_project_project_members_path
(
@project
.
namespace
,
@project
),
class:
'btn btn-gray'
,
style:
'margin-left: 10px'
,
method: :post
,
title:
'Request access'
do
Request Access
%li
.divider
-
if
can_edit
%li
...
...
@@ -18,6 +31,11 @@
=
link_to
leave_namespace_project_project_members_path
(
@project
.
namespace
,
@project
),
data:
{
confirm:
leave_project_message
(
@project
)
},
method: :delete
,
title:
'Leave project'
do
Leave Project
-
else
%li
=
link_to
request_access_namespace_project_project_members_path
(
@project
.
namespace
,
@project
),
class:
'btn btn-gray'
,
style:
'margin-left: 10px'
,
method: :post
,
title:
'Request access'
do
Request Access
%div
{
class:
nav_control_class
}
%ul
.nav-links.scrolling-tabs
...
...
app/views/notify/project_request_access_accepted_email.html.haml
0 → 100644
View file @
58f08aa9
%p
Your request to join project
#{
link_to
@project
.
name_with_namespace
,
namespace_project_url
(
@project
.
namespace
,
@project
)
}
has been granted with
#{
@project_member
.
human_access
}
access.
app/views/notify/project_request_access_accepted_email.text.erb
0 → 100644
View file @
58f08aa9
Your request to join project
<%=
@project
.
name_with_namespace
%>
has been granted with
<%=
@project_member
.
human_access
%>
access.
<%=
namespace_project_url
(
@project
.
namespace
,
@project
)
%>
app/views/notify/project_request_access_denied_email.html.haml
0 → 100644
View file @
58f08aa9
%p
Your request to join project
#{
link_to
@project
.
name_with_namespace
,
namespace_project_url
(
@project
.
namespace
,
@project
)
}
has been denied.
app/views/notify/project_request_access_denied_email.text.erb
0 → 100644
View file @
58f08aa9
Your request to join project
<%=
@project
.
name_with_namespace
%>
has been denied.
<%=
namespace_project_url
(
@project
.
namespace
,
@project
)
%>
app/views/projects/project_members/_pending.html.haml
0 → 100644
View file @
58f08aa9
.panel.panel-default
.panel-heading
%strong
#{
@project
.
name
}
candidates
%small
(
#{
members
.
count
}
)
.controls
=
form_tag
namespace_project_project_members_path
(
@project
.
namespace
,
@project
),
method: :get
,
class:
'form-inline member-search-form'
do
.form-group
=
search_field_tag
:search
,
params
[
:search
],
{
placeholder:
'Find existing member by name'
,
class:
'form-control'
,
spellcheck:
false
}
=
button_tag
class:
'btn'
,
title:
'Search'
do
=
icon
(
"search"
)
%ul
.content-list
-
members
.
each
do
|
project_member
|
=
render
'project_member'
,
member:
project_member
:javascript
$
(
'
form.member-search-form
'
).
on
(
'
submit
'
,
function
(
event
)
{
event
.
preventDefault
();
Turbolinks
.
visit
(
this
.
action
+
'
?
'
+
$
(
this
).
serialize
());
});
app/views/projects/project_members/_project_member.html.haml
View file @
58f08aa9
...
...
@@ -13,6 +13,9 @@
-
if
user
.
blocked?
%label
.label.label-danger
%strong
Blocked
-
if
member
.
request?
%span
.label.label-info
Pending Approval
-
else
=
image_tag
avatar_icon
(
member
.
invite_email
,
24
),
class:
"avatar s24"
,
alt:
''
%strong
...
...
@@ -27,7 +30,6 @@
-
if
can?
(
current_user
,
:admin_project_member
,
@project
)
=
link_to
resend_invite_namespace_project_project_member_path
(
@project
.
namespace
,
@project
,
member
),
method: :post
,
class:
"btn-xs btn"
,
title:
'Resend invite'
do
Resend invite
-
if
can?
(
current_user
,
:admin_project_member
,
@project
)
.pull-right
%strong
=
member
.
human_access
...
...
@@ -35,6 +37,12 @@
=
button_tag
class:
"btn-xs btn-grouped inline btn js-toggle-button"
,
title:
'Edit access level'
,
type:
'button'
do
=
icon
(
'pencil'
)
-
if
member
.
request?
=
link_to
approval_namespace_project_project_member_path
(
@project
.
namespace
,
@project
,
member
),
class:
"btn-xs btn btn-success"
,
title:
'Grant access'
,
type:
'button'
do
%i
.fa.fa-check.fa-inverse
-
if
can?
(
current_user
,
:destroy_project_member
,
member
)
...
...
@@ -38,7 +46,10 @@
-
if
can?
(
current_user
,
:destroy_project_member
,
member
)
-
if
current_user
==
user
-
if
member
.
request?
=
link_to
namespace_project_project_member_path
(
@project
.
namespace
,
@project
,
member
),
data:
{
confirm:
remove_from_project_team_message
(
@project
,
member
)
},
method: :delete
,
class:
"btn-xs btn btn-remove"
,
title:
'Deny access'
do
%i
.fa.fa-times.fa-inverse
-
elsif
current_user
==
user
=
link_to
leave_namespace_project_project_members_path
(
@project
.
namespace
,
@project
),
data:
{
confirm:
leave_project_message
(
@project
)
},
method: :delete
,
class:
"btn-xs btn btn-remove"
,
title:
'Leave project'
do
=
icon
(
"sign-out"
)
Leave
...
...
app/views/projects/project_members/index.html.haml
View file @
58f08aa9
...
...
@@ -12,4 +12,5 @@
%p
.light
Users with access to this project are listed below.
=
render
"new_project_member"
=
render
"pending"
,
members:
@project_members
.
request
...
...
@@ -15,5 +16,5 @@
=
render
"team"
,
members:
@project_members
=
render
"team"
,
members:
@project_members
.
non_request
-
if
@group
=
render
"group_members"
,
members:
@group_members
...
...
config/routes.rb
View file @
58f08aa9
...
...
@@ -768,6 +768,7 @@
resources
:project_members
,
except:
[
:new
,
:edit
],
constraints:
{
id:
/[a-zA-Z.\/0-9_\-#%+]+/
}
do
collection
do
delete
:leave
post
:request_access
# Used for import team
# from another project
...
...
@@ -777,6 +778,7 @@
member
do
post
:resend_invite
post
:approval
end
end
...
...
db/migrate/20160314114439_add_membership_request.rb
0 → 100644
View file @
58f08aa9
class
AddMembershipRequest
<
ActiveRecord
::
Migration
def
change
add_column
:members
,
:requested
,
:boolean
end
end
db/schema.rb
View file @
58f08aa9
...
...
@@ -536,6 +536,7 @@
t
.
string
"invite_email"
t
.
string
"invite_token"
t
.
datetime
"invite_accepted_at"
t
.
boolean
"requested"
end
add_index
"members"
,
[
"access_level"
],
name:
"index_members_on_access_level"
,
using: :btree
...
...
Write
Preview
Supports
Markdown
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