Class: ProjectTeam

Inherits:
Object
  • Object
show all
Defined in:
app/models/project_team.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project) ⇒ ProjectTeam

Returns a new instance of ProjectTeam.



6
7
8
# File 'app/models/project_team.rb', line 6

def initialize(project)
  @project = project
end

Instance Attribute Details

#projectObject

Returns the value of attribute project.



4
5
6
# File 'app/models/project_team.rb', line 4

def project
  @project
end

Instance Method Details

#add_developer(user, current_user: nil) ⇒ Object



28
29
30
# File 'app/models/project_team.rb', line 28

def add_developer(user, current_user: nil)
  add_member(user, :developer, current_user: current_user)
end

#add_guest(user, current_user: nil) ⇒ Object



10
11
12
# File 'app/models/project_team.rb', line 10

def add_guest(user, current_user: nil)
  add_member(user, :guest, current_user: current_user)
end

#add_maintainer(user, current_user: nil) ⇒ Object



32
33
34
# File 'app/models/project_team.rb', line 32

def add_maintainer(user, current_user: nil)
  add_member(user, :maintainer, current_user: current_user)
end

#add_member(user, access_level, current_user: nil, expires_at: nil, immediately_sync_authorizations: false) ⇒ Object

NOTE: immediately_sync_authorizations can be expensive to run in the foreground. This should only be used in rare cases where asynchronous authorization does not work (e.g. user is created and used immediately in the same request).



69
70
71
72
73
74
75
76
77
78
# File 'app/models/project_team.rb', line 69

def add_member(user, access_level, current_user: nil, expires_at: nil, immediately_sync_authorizations: false)
  Members::Projects::CreatorService.add_member( # rubocop:disable CodeReuse/ServiceClass
    project,
    user,
    access_level,
    current_user: current_user,
    expires_at: expires_at,
    immediately_sync_authorizations: immediately_sync_authorizations
  )
end

#add_members(users, access_level, current_user: nil, expires_at: nil) ⇒ Object



56
57
58
59
60
61
62
63
64
# File 'app/models/project_team.rb', line 56

def add_members(users, access_level, current_user: nil, expires_at: nil)
  Members::Projects::CreatorService.add_members( # rubocop:disable CodeReuse/ServiceClass
    project,
    users,
    access_level,
    current_user: current_user,
    expires_at: expires_at
  )
end

#add_owner(user, current_user: nil) ⇒ Object



36
37
38
# File 'app/models/project_team.rb', line 36

def add_owner(user, current_user: nil)
  add_member(user, :owner, current_user: current_user)
end

#add_planner(user, current_user: nil) ⇒ Object



14
15
16
# File 'app/models/project_team.rb', line 14

def add_planner(user, current_user: nil)
  add_member(user, :planner, current_user: current_user)
end

#add_reporter(user, current_user: nil) ⇒ Object



18
19
20
# File 'app/models/project_team.rb', line 18

def add_reporter(user, current_user: nil)
  add_member(user, :reporter, current_user: current_user)
end

#add_role(user, role, current_user: nil) ⇒ Object



40
41
42
# File 'app/models/project_team.rb', line 40

def add_role(user, role, current_user: nil)
  public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
end

#add_security_manager(user, current_user: nil) ⇒ Object



22
23
24
25
26
# File 'app/models/project_team.rb', line 22

def add_security_manager(user, current_user: nil)
  return unless Gitlab::Security::SecurityManagerConfig.enabled?

  add_member(user, :security_manager, current_user: current_user)
end

#contribution_check_for_user_ids(user_ids) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'app/models/project_team.rb', line 254

def contribution_check_for_user_ids(user_ids)
  Gitlab::SafeRequestLoader.execute(
    resource_key: "contribution_check_for_users:#{project.id}",
    resource_ids: user_ids,
    default_value: false
  ) do |user_ids|
    project.merge_requests
           .merged
           .where(author_id: user_ids, target_branch: project.default_branch.to_s)
           .pluck(:author_id)
           .product([true]).to_h
  end
end

#contributor?(user_id) ⇒ Boolean

Returns:

  • (Boolean)


268
269
270
271
272
# File 'app/models/project_team.rb', line 268

def contributor?(user_id)
  return false if max_member_access(user_id) >= Gitlab::Access::GUEST

  contribution_check_for_user_ids([user_id])[user_id]
end

#developer?(user) ⇒ Boolean

Returns:

  • (Boolean)


179
180
181
# File 'app/models/project_team.rb', line 179

def developer?(user)
  max_member_access(user.id) == Gitlab::Access::DEVELOPER
end

#developersObject



102
103
104
# File 'app/models/project_team.rb', line 102

def developers
  @developers ||= fetch_members(Gitlab::Access::DEVELOPER)
end

#find_member(user_id) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
# File 'app/models/project_team.rb', line 44

def find_member(user_id)
  member = project.members.find_by(user_id: user_id)

  # If user is not in project members
  # we should check for group membership
  if group && !member
    member = group.members.find_by(user_id: user_id)
  end

  member
end

#guest?(user) ⇒ Boolean

Returns:

  • (Boolean)


167
168
169
# File 'app/models/project_team.rb', line 167

def guest?(user)
  max_member_access(user.id) == Gitlab::Access::GUEST
end

#guestsObject



90
91
92
# File 'app/models/project_team.rb', line 90

def guests
  @guests ||= fetch_members(Gitlab::Access::GUEST)
end

#has_user?(user) ⇒ Boolean

Only for direct and not invited members

Returns:

  • (Boolean)


196
197
198
199
200
# File 'app/models/project_team.rb', line 196

def has_user?(user)
  return false unless user

  project.project_members.non_invite.exists?(user: user)
end

#human_max_access(user_id) ⇒ Object



202
203
204
# File 'app/models/project_team.rb', line 202

def human_max_access(user_id)
  Gitlab::Access.human_access(max_member_access(user_id))
end

#import(source_project, current_user) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/models/project_team.rb', line 125

def import(source_project, current_user)
  target_project = project

  source_members = source_members_for_import(source_project)
  target_user_ids = target_project.project_members.pluck_user_ids

  importer_access_level = max_member_access(current_user.id)

  source_members.reject! do |member|
    # Skip if user already present in team
    !member.invite? && target_user_ids.include?(member.user_id)
  end

  new_members = source_members.map do |member|
    new_member = member.dup
    new_member.id = nil
    new_member.source = target_project
    # So that a maintainer cannot import a member with owner access
    new_member.access_level = [new_member.access_level, importer_access_level].min
    new_member.created_by = current_user

    params = { access_level: new_member.access_level, member_role_id: member.member_role_id }
    if new_member.prevent_role_assignement?(current_user, params)
      new_member.errors.add(:base, _("Insufficient permissions to assign this member"))
    end

    new_member
  end

  ProjectMember.transaction do
    new_members.each do |member|
      next if member.errors.any?

      member.save
    end
  end

  new_members
rescue StandardError
  false
end

#maintainer?(user) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
# File 'app/models/project_team.rb', line 183

def maintainer?(user)
  max_member_access(user.id) == Gitlab::Access::MAINTAINER
end

#maintainersObject



106
107
108
# File 'app/models/project_team.rb', line 106

def maintainers
  @maintainers ||= fetch_members(Gitlab::Access::MAINTAINER)
end

#max_member_access(user_id) ⇒ Object



230
231
232
# File 'app/models/project_team.rb', line 230

def max_member_access(user_id)
  max_member_access_for_user_ids([user_id])[user_id]
end

#max_member_access_for_user(user, only_concrete_membership: false) ⇒ Object

Return the highest access level for a user

A special case is handled here when the user is a GitLab admin which implies it has “OWNER” access everywhere, but should not officially appear as a member of a project unless specifically added to it

Parameters:

  • user (User)
  • only_concrete_membership (Bool) (defaults to: false)

    whether require admin concrete membership status



242
243
244
245
246
247
248
249
250
251
252
# File 'app/models/project_team.rb', line 242

def max_member_access_for_user(user, only_concrete_membership: false)
  return ProjectMember::NO_ACCESS unless user
  return ProjectMember::OWNER if project.owner == user

  unless only_concrete_membership
    return ProjectMember::OWNER if user.can_admin_all_resources?
    return ProjectMember::OWNER if user.can_admin_organization?(project.organization)
  end

  max_member_access(user.id)
end

#max_member_access_for_user_ids(user_ids) ⇒ Object

Determine the maximum access level for a group of users in bulk.

Returns a Hash mapping user ID -> maximum access level.



209
210
211
212
213
214
215
216
217
218
219
220
# File 'app/models/project_team.rb', line 209

def max_member_access_for_user_ids(user_ids)
  Gitlab::SafeRequestLoader.execute(
    resource_key: project.max_member_access_for_resource_key(User),
    resource_ids: user_ids,
    default_value: Gitlab::Access::NO_ACCESS
  ) do |user_ids|
    project.project_authorizations
           .where(user: user_ids)
           .group(:user_id)
           .maximum(:access_level)
  end
end

#member?(user, min_access_level = Gitlab::Access::GUEST) ⇒ Boolean

Checks if user is authorized for this project, with at least the min_access_level (if given).

Returns:

  • (Boolean)


189
190
191
192
193
# File 'app/models/project_team.rb', line 189

def member?(user, min_access_level = Gitlab::Access::GUEST)
  return false unless user

  max_member_access(user.id) >= min_access_level
end

#membersObject Also known as: users



85
86
87
# File 'app/models/project_team.rb', line 85

def members
  @members ||= fetch_members
end

#owner?(user) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
# File 'app/models/project_team.rb', line 121

def owner?(user)
  owners.include?(user)
end

#ownersObject



110
111
112
113
114
115
116
117
118
119
# File 'app/models/project_team.rb', line 110

def owners
  @owners ||=
    if group
      group.owners.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/432606")
    else
      # workaround until we migrate Project#owners to have membership with
      # OWNER access level
      Array.wrap(fetch_members(Gitlab::Access::OWNER)) | Array.wrap(project.owner)
    end
end

#planner?(user) ⇒ Boolean

Returns:

  • (Boolean)


171
172
173
# File 'app/models/project_team.rb', line 171

def planner?(user)
  max_member_access(user.id) == Gitlab::Access::PLANNER
end

#plannersObject



94
95
96
# File 'app/models/project_team.rb', line 94

def planners
  @planners ||= fetch_members(Gitlab::Access::PLANNER)
end

#purge_member_access_cache_for_user_id(user_id) ⇒ Object



226
227
228
# File 'app/models/project_team.rb', line 226

def purge_member_access_cache_for_user_id(user_id)
  project.purge_resource_id_from_request_store(User, user_id)
end

#reporter?(user) ⇒ Boolean

Returns:

  • (Boolean)


175
176
177
# File 'app/models/project_team.rb', line 175

def reporter?(user)
  max_member_access(user.id) == Gitlab::Access::REPORTER
end

#reportersObject



98
99
100
# File 'app/models/project_team.rb', line 98

def reporters
  @reporters ||= fetch_members(Gitlab::Access::REPORTER)
end

#truncateObject

Remove all users from project team



81
82
83
# File 'app/models/project_team.rb', line 81

def truncate
  ProjectMember.truncate_team(project)
end

#write_member_access_for_user_id(user_id, project_access_level) ⇒ Object



222
223
224
# File 'app/models/project_team.rb', line 222

def write_member_access_for_user_id(user_id, project_access_level)
  project.merge_value_to_request_store(User, user_id, project_access_level)
end