Skip to content
Snippets Groups Projects
Commit d26f8123 authored by Rémy Coutable's avatar Rémy Coutable
Browse files

Add request access for groups


Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 17c22156
No related branches found
No related tags found
No related merge requests found
Showing
with 334 additions and 242 deletions
Loading
Loading
@@ -286,10 +286,6 @@
color: #555;
}
 
.project_member_row form {
margin: 0;
}
.transfer-project .select2-container {
min-width: 200px;
}
Loading
Loading
class Groups::GroupMembersController < Groups::ApplicationController
# Authorize
before_action :authorize_admin_group_member!, except: [:index, :leave]
before_action :authorize_admin_group_member!, except: [:index, :leave, :request_access]
 
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = @group.group_members
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.non_pending unless can?(current_user, :admin_group, @group)
 
if params[:search].present?
users = @group.users.search(params[:search]).to_a
Loading
Loading
@@ -36,7 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
 
return render_403 unless can?(current_user, :destroy_group_member, @group_member)
 
@group_member.destroy
@group_member.request? ? @group_member.decline_request : @group_member.destroy
 
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
Loading
Loading
@@ -59,12 +59,20 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
 
def leave
@group_member = @group.group_members.find_by(user_id: current_user)
@group_member =
@group.group_members.find_by(user_id: current_user.id) ||
@group.group_members.find_by(created_by_id: current_user.id)
 
if can?(current_user, :destroy_group_member, @group_member)
notice =
if @group_member.request?
'You withdrawn your access request to the group.'
else
"You left #{@group.name} group."
end
@group_member.destroy
 
redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.")
redirect_to dashboard_groups_path, notice: notice
else
if @group.last_owner?(current_user)
redirect_to(dashboard_groups_path, alert: "You can not leave #{group.name} group because you're the last owner. Transfer or delete the group.")
Loading
Loading
@@ -74,6 +82,22 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
end
 
def request_access
@group.request_access(current_user)
redirect_to group_path(@group), notice: 'Your request for access has been queued for review.'
end
def approve
@group_member = @group.group_members.request.find(params[:id])
return render_403 unless can?(current_user, :update_group_member, @group_member)
@group_member.accept_request
redirect_to group_group_members_path(@group)
end
protected
 
def member_params
Loading
Loading
Loading
Loading
@@ -14,9 +14,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project_members = @project_members.order('access_level DESC')
 
@group = @project.group
if @group
@group_members = @group.group_members
@group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
@group_members = @group_members.non_pending unless can?(current_user, :admin_group, @group)
 
if params[:search].present?
users = @group.users.search(params[:search]).to_a
Loading
Loading
@@ -49,7 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
 
return render_403 unless can?(current_user, :destroy_project_member, @project_member)
 
@project_member.destroy
@project_member.request? ? @project_member.decline_request : @project_member.destroy
 
respond_to do |format|
format.html do
Loading
Loading
@@ -74,15 +75,20 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
 
def leave
@project_member = @project.project_members.find_by(user_id: current_user)
@project_member =
@project.project_members.find_by(user_id: current_user.id) ||
@project.project_members.find_by(created_by_id: current_user.id)
 
if can?(current_user, :destroy_project_member, @project_member)
notice =
if @project_member.request?
'You withdrawn your access request to the project.'
else
'You left the project.'
end
@project_member.destroy
 
respond_to do |format|
format.html { redirect_to dashboard_projects_path, notice: "You left the project." }
format.js { head :ok }
end
redirect_to dashboard_projects_path, notice: notice
else
if current_user == @project.owner
message = 'You can not leave your own project. Transfer or delete the project.'
Loading
Loading
@@ -94,30 +100,20 @@ class Projects::ProjectMembersController < Projects::ApplicationController
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!
@project.request_access(current_user)
 
redirect_to redirect_path, notice: 'Your request for access has been queued for review.'
redirect_to namespace_project_path(@project.namespace, @project),
notice: 'Your request for access has been queued for review.'
end
 
def approval
@project_member = @project.project_members.find(params[:id])
def approve
@project_member = @project.project_members.request.find(params[:id])
 
return render_403 unless can?(current_user, :update_project_member, @project_member)
 
@project_member.requested = nil
@project_member.save!
@project_member.accept_request
 
respond_to do |format|
format.html do
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
format.js { render nothing: true }
end
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
 
def apply_import
Loading
Loading
module GroupsHelper
def remove_user_from_group_message(group, member)
if member.user
"Are you sure you want to remove \"#{member.user.name}\" from \"#{group.name}\"?"
else
"Are you sure you want to revoke the invitation for \"#{member.invite_email}\" to join \"#{group.name}\"?"
end
end
def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?"
end
def should_user_see_group_roles?(user, group)
if user
user.is_admin? || group.members.exists?(user_id: user.id)
else
false
end
end
def can_change_group_visibility_level?(group)
can?(current_user, :change_visibility_level, group)
end
Loading
Loading
module MembersHelper
def member_class(member)
"#{member.source.class.to_s}Member".constantize
end
def members_association(entity)
"#{entity.class.to_s.underscore}_members".to_sym
end
def action_member_permission(action, member)
"#{action}_#{member.source.class.to_s.underscore}_member".to_sym
end
def can_see_entity_roles?(user, entity)
return false unless user
user.is_admin? || entity.send(members_association(entity)).exists?(user_id: user.id)
end
def member_path(member)
case member.source
when Project
namespace_project_project_member_path(member.source.namespace, member.source, member)
when Group
group_group_member_path(member.source, member)
else
raise ArgumentError.new('Unknown object class')
end
end
def resend_invite_member_path(member)
case member.source
when Project
resend_invite_namespace_project_project_member_path(member.source.namespace, member.source, member)
when Group
resend_invite_group_group_member_path(member.source, member)
else
raise ArgumentError.new('Unknown object class')
end
end
def request_access_path(entity)
case entity
when Project
request_access_namespace_project_project_members_path(entity.namespace, entity)
when Group
request_access_group_group_members_path(entity)
else
raise ArgumentError.new('Unknown object class')
end
end
def approve_request_member_path(member)
case member.source
when Project
approve_namespace_project_project_member_path(member.source.namespace, member.source, member)
when Group
approve_group_group_member_path(member.source, member)
else
raise ArgumentError.new('Unknown object class')
end
end
def leave_path(entity)
case entity
when Project
leave_namespace_project_project_members_path(entity.namespace, entity)
when Group
leave_group_group_members_path(entity)
else
raise ArgumentError.new('Unknown object class')
end
end
def withdraw_request_message(entity)
"Are you sure you want to withdraw your access request for the \"#{entity_name(entity)}\" #{entity_type(entity)}?"
end
def remove_member_message(member)
entity = member.source
entity_type = entity_type(entity)
entity_name = entity_name(entity)
if member.request?
"You are going to deny #{member.created_by.name}'s request to join the #{entity_name} #{entity_type}. Are you sure?"
elsif member.invite?
"You are going to revoke the invitation for #{member.invite_email} to join the #{entity_name} #{entity_type}. Are you sure?"
else
"You are going to remove #{member.user.name} from the #{entity_name} #{entity_type}. Are you sure?"
end
end
def remove_member_title(member)
member.request? ? 'Deny access request' : 'Remove user'
end
def leave_confirmation_message(entity)
"Are you sure you want to leave \"#{entity_name(entity)}\" #{entity_type(entity)}?"
end
private
def entity_type(entity)
entity.class.to_s.underscore
end
def entity_name(entity)
case entity
when Project
entity.name_with_namespace
when Group
entity.name
else
raise ArgumentError.new('Unknown object class')
end
end
end
module ProjectsHelper
def remove_from_project_team_message(project, member)
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?"
def max_access_level(project, user)
Gitlab::Access.options_with_owner.key(project.team.max_member_access(user.id))
end
 
def link_to_project(project)
Loading
Loading
@@ -121,14 +111,6 @@ module ProjectsHelper
end
end
 
def user_max_access_in_project(user_id, project)
level = project.team.max_member_access(user_id)
if level
Gitlab::Access.options_with_owner.key(level)
end
end
def license_short_name(project)
return 'LICENSE' if project.repository.license_key.nil?
 
Loading
Loading
@@ -292,10 +274,6 @@ module ProjectsHelper
end
end
 
def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?"
end
def new_readme_path
ref = @repository.root_ref if @repository
ref ||= 'master'
Loading
Loading
module Emails
module Groups
def group_access_requested_email(group_member_id)
setup_group_member_mail(group_member_id)
@requester = @group_member.created_by
group_admins = User.where(id: @group.group_members.admins.pluck(:user_id)).pluck(:notification_email)
mail(to: group_admins,
subject: subject("Request to join #{@group.name} group"))
end
def group_access_granted_email(group_member_id)
@group_member = GroupMember.find(group_member_id)
@group = @group_member.group
setup_group_member_mail(group_member_id)
 
@target_url = group_url(@group)
@current_user = @group_member.user
 
mail(to: @group_member.user.notification_email,
subject: subject("Access to group was granted"))
mail(to: @current_user.notification_email,
subject: subject("Access to #{@group.name} group was granted"))
end
def group_access_denied_email(group_id, user_id)
@group = Group.find(group_id)
@current_user = User.find(user_id)
@target_url = group_url(@group)
mail(to: @current_user.notification_email,
subject: subject("Access to #{@group.name} group was denied"))
end
 
def group_member_invited_email(group_member_id, token)
@group_member = GroupMember.find group_member_id
@group = @group_member.group
@token = token
setup_group_member_mail(group_member_id)
 
@target_url = group_url(@group)
@token = token
@current_user = @group_member.user
 
mail(to: @group_member.invite_email,
Loading
Loading
@@ -24,15 +40,12 @@ module Emails
end
 
def group_invite_accepted_email(group_member_id)
@group_member = GroupMember.find group_member_id
setup_group_member_mail(group_member_id)
return if @group_member.created_by.nil?
 
@group = @group_member.group
@target_url = group_url(@group)
@current_user = @group_member.created_by
 
mail(to: @group_member.created_by.notification_email,
mail(to: @current_user.notification_email,
subject: subject("Invitation accepted"))
end
 
Loading
Loading
@@ -43,10 +56,18 @@ module Emails
@current_user = @created_by = User.find(created_by_id)
@access_level = access_level
@invite_email = invite_email
@target_url = group_url(@group)
mail(to: @created_by.notification_email,
subject: subject("Invitation declined"))
end
private
def setup_group_member_mail(group_member_id)
@group_member = GroupMember.find(group_member_id)
@group = @group_member.group
@target_url = group_url(@group)
end
end
end
module Emails
module Projects
def project_access_granted_email(project_member_id)
@project_member = ProjectMember.find project_member_id
@project = @project_member.project
@target_url = namespace_project_url(@project.namespace, @project)
@current_user = @project_member.user
def project_access_requested_email(project_member_id)
setup_project_member_mail(project_member_id)
 
mail(to: @project_member.user.notification_email,
subject: subject("Access to project was granted"))
end
@requester = @project_member.created_by
 
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 = User.where(id: @project.project_members.admins.pluck(:user_id)).pluck(:notification_email)
 
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
mail(to: project_admins,
subject: subject("Request to join #{@project.name_with_namespace} project"))
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
def project_access_granted_email(project_member_id)
setup_project_member_mail(project_member_id)
 
@target_url = namespace_project_url(@project.namespace, @project)
@current_user = @project_member.created_by
@current_user = @project_member.user
 
mail(to: @project_member.created_by.notification_email,
subject: subject('Request for access granted'))
mail(to: @current_user.notification_email,
subject: subject("Access to #{@project.name_with_namespace} project was 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
def project_access_denied_email(project_id, user_id)
@project = Project.find(project_id)
@current_user = User.find(user_id)
@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'))
mail(to: @current_user.notification_email,
subject: subject("Access to #{@project.name_with_namespace} project was denied"))
end
 
def project_member_invited_email(project_member_id, token)
@project_member = ProjectMember.find project_member_id
@project = @project_member.project
@token = token
setup_project_member_mail(project_member_id)
 
@target_url = namespace_project_url(@project.namespace, @project)
@token = token
@current_user = @project_member.user
 
mail(to: @project_member.invite_email,
Loading
Loading
@@ -66,12 +40,9 @@ module Emails
end
 
def project_invite_accepted_email(project_member_id)
@project_member = ProjectMember.find project_member_id
setup_project_member_mail(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,
Loading
Loading
@@ -117,5 +88,13 @@ module Emails
reply_to: @message.reply_to,
subject: @message.subject)
end
private
def setup_project_member_mail(project_member_id)
@project_member = ProjectMember.find(project_member_id)
@project = @project_member.project
@target_url = namespace_project_url(@project.namespace, @project)
end
end
end
Loading
Loading
@@ -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)) unless project.team.pending?(user)
rules.push(*project_team_rules(project.team, user))
 
if project.owner == user ||
(project.group && project.group.has_owner?(user)) ||
Loading
Loading
@@ -187,6 +187,8 @@ class Ability
project_report_rules
elsif team.guest?(user)
project_guest_rules
else
[]
end
end
 
Loading
Loading
@@ -458,6 +460,8 @@ class Ability
rules << :destroy_group_member
elsif user == target_user
rules << :destroy_group_member
elsif subject.request? && user == subject.created_by
rules << :destroy_group_member
end
end
 
Loading
Loading
@@ -477,6 +481,8 @@ class Ability
rules << :destroy_project_member
elsif user == target_user
rules << :destroy_project_member
elsif subject.request? && user == subject.created_by
rules << :destroy_project_member
end
end
 
Loading
Loading
# == AccessRequestable concern
#
# Contains functionality related to objects that can receive request for access.
#
# Used by Project, and Group.
#
module AccessRequestable
extend ActiveSupport::Concern
def request_access(user)
members.create(
access_level: Gitlab::Access::DEVELOPER,
created_by: user,
requested_at: Time.now.utc)
end
def access_requested?(user)
members.where(created_by_id: user.id).where.not(requested_at: nil).any?
end
private
# Returns a `<entities>_members` association, e.g.: project_members, group_members
def members
@members ||= send("#{self.class.to_s.underscore}_members".to_sym)
end
end
Loading
Loading
@@ -3,6 +3,7 @@ require 'carrierwave/orm/activerecord'
class Group < Namespace
include Gitlab::ConfigHelper
include Gitlab::VisibilityLevel
include AccessRequestable
include Referable
 
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
Loading
Loading
Loading
Loading
@@ -8,7 +8,7 @@ class Member < ActiveRecord::Base
belongs_to :user
belongs_to :source, polymorphic: true
 
validates :user, presence: true, unless: :invite?
validates :user, presence: true, unless: :pending?
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source",
Loading
Loading
@@ -26,29 +26,25 @@ class Member < ActiveRecord::Base
allow_nil: true
}
 
scope :invite, -> { where(user_id: nil) }
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 :invite, -> { where.not(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) }
scope :non_pending, -> { where.not(user_id: nil) }
 
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
scope :developers, -> { where(access_level: DEVELOPER) }
scope :masters, -> { where(access_level: MASTER) }
scope :owners, -> { where(access_level: OWNER) }
scope :admins, -> { where(access_level: [OWNER, MASTER]) }
 
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
 
after_create :send_invite, if: :invite?
after_create :send_request_access, if: :request?
after_create :send_request, 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
Loading
Loading
@@ -111,31 +107,29 @@ class Member < ActiveRecord::Base
end
 
def request?
self.requested
user.nil? && created_by.present? && requested_at.present?
end
 
def invite?
self.invite_token.present?
end
 
def accept_request_access!
def accept_request
return false unless request?
 
self.request = false
saved = self.save
updated = self.update(user: created_by, requested_at: nil)
after_accept_request if updated
 
after_accept_request_access if saved
saved
updated
end
 
def decline_request_access!
def decline_request
return false unless request?
 
destroyed = self.destroy
after_decline_request_access if destroyed
self.destroy
after_decline_request if destroyed?
 
destroyed
destroyed?
end
 
def accept_invite!(new_user)
Loading
Loading
@@ -191,11 +185,11 @@ class Member < ActiveRecord::Base
 
private
 
def send_request_access
def send_invite
# override in subclass
end
 
def send_invite
def send_request
# override in subclass
end
 
Loading
Loading
@@ -211,19 +205,19 @@ class Member < ActiveRecord::Base
system_hook_service.execute_hooks_for(self, :destroy)
end
 
def after_accept_request_access
def after_accept_invite
post_create_hook
end
 
def after_decline_request_access
def after_decline_invite
# override in subclass
end
 
def after_accept_invite
def after_accept_request
post_create_hook
end
 
def after_decline_invite
def after_decline_request
# override in subclass
end
 
Loading
Loading
Loading
Loading
@@ -8,9 +8,6 @@ class GroupMember < Member
validates_format_of :source_type, with: /\ANamespace\z/
default_scope { where(source_type: SOURCE_TYPE) }
 
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
def self.access_level_roles
Gitlab::Access.options_with_owner
end
Loading
Loading
@@ -31,6 +28,12 @@ class GroupMember < Member
super
end
 
def send_request
notification_service.new_group_access_request(self)
super
end
def post_create_hook
notification_service.new_group_member(self)
 
Loading
Loading
@@ -56,4 +59,10 @@ class GroupMember < Member
 
super
end
def after_decline_request
notification_service.decline_group_access_request(group, created_by)
super
end
end
Loading
Loading
@@ -11,8 +11,6 @@ class ProjectMember < Member
default_scope { where(source_type: SOURCE_TYPE) }
 
scope :in_project, ->(project) { where(source_id: project.id) }
scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) }
scope :with_user, ->(user) { where(user_id: user.id) }
 
before_destroy :delete_member_todos
 
Loading
Loading
@@ -84,7 +82,7 @@ class ProjectMember < Member
Gitlab::Access.sym_options
end
 
def access_roles
def access_level_roles
Gitlab::Access.options
end
end
Loading
Loading
@@ -107,14 +105,14 @@ 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)
def send_invite
notification_service.invite_project_member(self, @raw_invite_token)
 
super
end
 
def send_invite
notification_service.invite_project_member(self, @raw_invite_token)
def send_request
notification_service.new_project_access_request(self)
 
super
end
Loading
Loading
@@ -142,18 +140,6 @@ 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)
 
Loading
Loading
@@ -166,6 +152,12 @@ class ProjectMember < Member
super
end
 
def after_decline_request
notification_service.decline_project_access_request(project, created_by)
super
end
def event_service
EventCreateService.new
end
Loading
Loading
Loading
Loading
@@ -5,6 +5,7 @@ class Project < ActiveRecord::Base
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
include Gitlab::CurrentSettings
include AccessRequestable
include Referable
include Sortable
include AfterCommitQueue
Loading
Loading
@@ -102,7 +103,7 @@ class Project < ActiveRecord::Base
has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet'
has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy
has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :users, through: :project_members
has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects
Loading
Loading
@@ -680,16 +681,6 @@ class Project < ActiveRecord::Base
end
end
 
def project_member_by_name_or_email(name = nil, email = nil)
user = users.find_by('name like ? or email like ?', name, email)
project_members.where(user: user) if user
end
# Get Team Member record by user id
def project_member_by_id(user_id)
project_members.find_by(user_id: user_id)
end
def name_with_namespace
@name_with_namespace ||= begin
if namespace
Loading
Loading
Loading
Loading
@@ -21,16 +21,6 @@ class ProjectTeam
end
end
 
def find(user_id)
user = project.users.find_by(id: user_id)
if group
user ||= group.users.find_by(id: user_id)
end
user
end
def find_member(user_id)
member = project.project_members.find_by(user_id: user_id)
 
Loading
Loading
@@ -61,13 +51,10 @@ class ProjectTeam
ProjectMember.truncate_team(project)
end
 
def users
members
end
def members
@members ||= fetch_members
end
alias_method :users, :members
 
def guests
@guests ||= fetch_members(:guests)
Loading
Loading
@@ -115,12 +102,6 @@ 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
Loading
Loading
@@ -147,10 +128,6 @@ class ProjectTeam
end
end
 
def human_max_access(user_id)
Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end
# This method assumes project and group members are eager loaded for optimal
# performance.
def max_member_access(user_id)
Loading
Loading
@@ -179,6 +156,7 @@ class ProjectTeam
access.compact.max
end
 
private
 
def max_invited_level(user_id)
project.project_group_links.map do |group_link|
Loading
Loading
@@ -195,8 +173,6 @@ class ProjectTeam
end.compact.max
end
 
private
def fetch_members(level = nil)
project_members = project.project_members
group_members = group ? group.group_members : []
Loading
Loading
Loading
Loading
@@ -56,8 +56,7 @@ class User < ActiveRecord::Base
 
# Groups
has_many :members, dependent: :destroy
has_many :project_members, source: 'ProjectMember'
has_many :group_members, source: 'GroupMember'
has_many :group_members, dependent: :destroy, source: 'GroupMember'
has_many :groups, through: :group_members
has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
Loading
Loading
@@ -65,13 +64,13 @@ class User < ActiveRecord::Base
# Projects
has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects
has_many :project_members, dependent: :destroy, class_name: 'ProjectMember'
has_many :projects, through: :project_members
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy
has_many :starred_projects, through: :users_star_projects, source: :project
 
has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
has_many :project_members, dependent: :destroy, class_name: 'ProjectMember'
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
Loading
Loading
Loading
Loading
@@ -173,16 +173,13 @@ class NotificationService
end
end
 
def request_access_project_member(project_member)
mailer.project_member_requested_access(project_member.id).deliver_later
# Project access request
def new_project_access_request(project_member)
mailer.project_access_requested_email(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
def decline_project_access_request(project, user)
mailer.project_access_denied_email(project.id, user.id).deliver_later
end
 
def invite_project_member(project_member, token)
Loading
Loading
@@ -210,6 +207,15 @@ class NotificationService
mailer.project_access_granted_email(project_member.id).deliver_later
end
 
# Group access request
def new_group_access_request(group_member)
mailer.group_access_requested_email(group_member.id).deliver_later
end
def decline_group_access_request(group, user)
mailer.group_access_denied_email(group.id, user.id).deliver_later
end
def invite_group_member(group_member, token)
mailer.group_member_invited_email(group_member.id, token).deliver_later
end
Loading
Loading
Loading
Loading
@@ -109,7 +109,7 @@
%span.pull-right.light
= member.human_access
- if can?(current_user, :destroy_group_member, member)
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
= link_to group_group_member_path(@group, member), data: { confirm: remove_member_message(member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
Loading
Loading
@@ -142,7 +142,7 @@
%i.fa.fa-pencil-square-o
%ul.well-list
- @group_members.each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false
= render 'shared/members/member', member: member, show_controls: false
.panel-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
 
Loading
Loading
@@ -172,7 +172,7 @@
%span.light Owner
- else
%span.light= project_member.human_access
= link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_from_project_team_message(@project, project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
= link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_member_message(project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
%i.fa.fa-times
.panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment