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
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gitlab-ce
Commits
17c22156
Commit
17c22156
authored
Mar 14, 2016
by
David Alexander
Committed by
Rémy Coutable
Jun 14, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial implementation of user access request to projects
parent
0c0ef7df
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
248 additions
and
14 deletions
+248
-14
app/controllers/projects/project_members_controller.rb
app/controllers/projects/project_members_controller.rb
+29
-2
app/helpers/projects_helper.rb
app/helpers/projects_helper.rb
+9
-3
app/mailers/emails/projects.rb
app/mailers/emails/projects.rb
+42
-0
app/models/ability.rb
app/models/ability.rb
+1
-1
app/models/member.rb
app/models/member.rb
+55
-5
app/models/members/project_member.rb
app/models/members/project_member.rb
+18
-0
app/models/project_team.rb
app/models/project_team.rb
+6
-0
app/services/notification_service.rb
app/services/notification_service.rb
+12
-0
app/views/layouts/nav/_project.html.haml
app/views/layouts/nav/_project.html.haml
+18
-0
app/views/notify/project_request_access_accepted_email.html.haml
...ws/notify/project_request_access_accepted_email.html.haml
+4
-0
app/views/notify/project_request_access_accepted_email.text.erb
...ews/notify/project_request_access_accepted_email.text.erb
+3
-0
app/views/notify/project_request_access_denied_email.html.haml
...iews/notify/project_request_access_denied_email.html.haml
+4
-0
app/views/notify/project_request_access_denied_email.text.erb
...views/notify/project_request_access_denied_email.text.erb
+3
-0
app/views/projects/project_members/_pending.html.haml
app/views/projects/project_members/_pending.html.haml
+21
-0
app/views/projects/project_members/_project_member.html.haml
app/views/projects/project_members/_project_member.html.haml
+13
-2
app/views/projects/project_members/index.html.haml
app/views/projects/project_members/index.html.haml
+2
-1
config/routes.rb
config/routes.rb
+2
-0
db/migrate/20160314114439_add_membership_request.rb
db/migrate/20160314114439_add_membership_request.rb
+5
-0
db/schema.rb
db/schema.rb
+1
-0
No files found.
app/controllers/projects/project_members_controller.rb
View file @
17c22156
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
@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 @@ class Projects::ProjectMembersController < Projects::ApplicationController
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 @
17c22156
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?"
else
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
"You are going to remove
#{
member
.
user
.
name
}
from
#{
project
.
name
}
project team. Are you sure?"
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 @
17c22156
...
...
@@ -11,6 +11,48 @@ module Emails
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 @
17c22156
...
...
@@ -153,7 +153,7 @@ class Ability
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 @
17c22156
...
...
@@ -27,7 +27,12 @@ class Member < ActiveRecord::Base
}
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,11 +40,16 @@ class Member < ActiveRecord::Base
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?
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,10 +106,38 @@ class Member < ActiveRecord::Base
end
end
def
pending?
request?
||
invite?
end
def
request?
self
.
requested
end
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 @@ class Member < ActiveRecord::Base
private
def
send_request_access
# override in subclass
end
def
send_invite
# override in subclass
end
...
...
@@ -169,6 +211,14 @@ class Member < ActiveRecord::Base
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 @
17c22156
...
...
@@ -107,6 +107,12 @@ class ProjectMember < Member
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 @@ class ProjectMember < Member
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 @
17c22156
...
...
@@ -115,6 +115,12 @@ class ProjectTeam
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 @
17c22156
...
...
@@ -173,6 +173,18 @@ class NotificationService
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 @
17c22156
...
...
@@ -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 @
17c22156
%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 @
17c22156
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 @
17c22156
%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 @
17c22156
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 @
17c22156
.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 @
17c22156
...
...
@@ -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,10 +37,19 @@
=
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
)
-
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 @
17c22156
...
...
@@ -12,8 +12,9 @@
%p
.light
Users with access to this project are listed below.
=
render
"new_project_member"
=
render
"pending"
,
members:
@project_members
.
request
=
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 @
17c22156
...
...
@@ -768,6 +768,7 @@ Rails.application.routes.draw do
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 @@ Rails.application.routes.draw do
member
do
post
:resend_invite
post
:approval
end
end
...
...
db/migrate/20160314114439_add_membership_request.rb
0 → 100644
View file @
17c22156
class
AddMembershipRequest
<
ActiveRecord
::
Migration
def
change
add_column
:members
,
:requested
,
:boolean
end
end
db/schema.rb
View file @
17c22156
...
...
@@ -536,6 +536,7 @@ ActiveRecord::Schema.define(version: 20160610301627) do
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
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