Class: Member

Direct Known Subclasses

GroupMember, ProjectMember

Constant Summary

Constants included from UpdateHighestRole

UpdateHighestRole::HIGHEST_ROLE_JOB_DELAY, UpdateHighestRole::HIGHEST_ROLE_LEASE_TIMEOUT

Constants included from Gitlab::Access

Gitlab::Access::AccessDeniedError, Gitlab::Access::DEVELOPER, Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS, Gitlab::Access::GUEST, Gitlab::Access::MAINTAINER, Gitlab::Access::MAINTAINER_PROJECT_ACCESS, Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS, Gitlab::Access::MINIMAL_ACCESS, Gitlab::Access::NO_ACCESS, Gitlab::Access::NO_ONE_PROJECT_ACCESS, Gitlab::Access::OWNER, Gitlab::Access::OWNER_SUBGROUP_ACCESS, Gitlab::Access::PROTECTION_DEV_CAN_MERGE, Gitlab::Access::PROTECTION_DEV_CAN_PUSH, Gitlab::Access::PROTECTION_FULL, Gitlab::Access::PROTECTION_NONE, Gitlab::Access::REPORTER

Constants included from Expirable

Expirable::DAYS_TO_EXPIRE

Instance Attribute Summary collapse

Attributes included from Importable

#imported, #importing

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from Presentable

#present

Methods included from Gitlab::Access

all_values, human_access, #human_access, #human_access_with_none, human_access_with_none, options, options_with_none, options_with_owner, #owner?, project_creation_level_name, project_creation_options, project_creation_string_options, project_creation_string_values, project_creation_values, protection_options, protection_values, subgroup_creation_options, subgroup_creation_string_options, subgroup_creation_string_values, subgroup_creation_values, sym_options, sym_options_with_owner

Methods included from Expirable

#expired?, #expires?, #expires_soon?

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, where_exists, with_fast_statement_timeout, without_order

Instance Attribute Details

#raw_invite_tokenObject

Returns the value of attribute raw_invite_token


16
17
18
# File 'app/models/member.rb', line 16

def raw_invite_token
  @raw_invite_token
end

Class Method Details

.access_for_user_ids(user_ids) ⇒ Object


170
171
172
# File 'app/models/member.rb', line 170

def access_for_user_ids(user_ids)
  where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
end

.access_levelsObject


243
244
245
# File 'app/models/member.rb', line 243

def access_levels
  Gitlab::Access.sym_options
end

.add_user(source, user, access_level, existing_members: nil, current_user: nil, expires_at: nil, ldap: false) ⇒ Object


179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'app/models/member.rb', line 179

def add_user(source, user, access_level, existing_members: nil, current_user: nil, expires_at: nil, ldap: false)
  # rubocop: disable CodeReuse/ServiceClass
  # `user` can be either a User object, User ID or an email to be invited
  member = retrieve_member(source, user, existing_members)
  access_level = retrieve_access_level(access_level)

  return member unless can_update_member?(current_user, member)

  set_member_attributes(
    member,
    access_level,
    current_user: current_user,
    expires_at: expires_at,
    ldap: ldap
  )

  if member.request?
    ::Members::ApproveAccessRequestService.new(
      current_user,
      access_level: access_level
    ).execute(
      member,
      skip_authorization: ldap,
      skip_log_audit_event: ldap
    )
  else
    member.save
  end

  member
  # rubocop: enable CodeReuse/ServiceClass
end

.add_users(source, users, access_level, current_user: nil, expires_at: nil) ⇒ Object


224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/member.rb', line 224

def add_users(source, users, access_level, current_user: nil, expires_at: nil)
  return [] unless users.present?

  emails, users, existing_members = parse_users_list(source, users)

  self.transaction do
    (emails + users).map! do |user|
      add_user(
        source,
        user,
        access_level,
        existing_members: existing_members,
        current_user: current_user,
        expires_at: expires_at
      )
    end
  end
end

.filter_by_2fa(value) ⇒ Object


135
136
137
138
139
140
141
142
143
144
# File 'app/models/member.rb', line 135

def filter_by_2fa(value)
  case value
  when 'enabled'
    left_join_users.merge(User.with_two_factor)
  when 'disabled'
    left_join_users.merge(User.without_two_factor)
  else
    all
  end
end

.find_by_invite_token(raw_invite_token) ⇒ Object


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

def find_by_invite_token(raw_invite_token)
  invite_token = Devise.token_generator.digest(self, :invite_token, raw_invite_token)
  find_by(invite_token: invite_token)
end

.left_join_usersObject


159
160
161
162
163
164
165
166
167
168
# File 'app/models/member.rb', line 159

def left_join_users
  users = User.arel_table
  members = Member.arel_table

  member_users = members.join(users, Arel::Nodes::OuterJoin)
                         .on(members[:user_id].eq(users[:id]))
                         .join_sources

  joins(member_users)
end

.search(query) ⇒ Object


127
128
129
# File 'app/models/member.rb', line 127

def search(query)
  joins(:user).merge(User.search(query))
end

.search_invite_email(query) ⇒ Object


131
132
133
# File 'app/models/member.rb', line 131

def search_invite_email(query)
  invite.where(['invite_email ILIKE ?', "%#{query}%"])
end

.set_member_attributes(member, access_level, current_user: nil, expires_at: nil, ldap: false) ⇒ Object

Populates the attributes of a member.

This logic resides in a separate method so that EE can extend this logic, without having to patch the `add_user` method directly.


216
217
218
219
220
221
222
# File 'app/models/member.rb', line 216

def set_member_attributes(member, access_level, current_user: nil, expires_at: nil, ldap: false)
  member.attributes = {
    created_by: member.created_by || current_user,
    access_level: access_level,
    expires_at: expires_at
  }
end

.sort_by_attribute(method) ⇒ Object


146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/models/member.rb', line 146

def sort_by_attribute(method)
  case method.to_s
  when 'access_level_asc' then reorder(access_level: :asc)
  when 'access_level_desc' then reorder(access_level: :desc)
  when 'recent_sign_in' then 
  when 'oldest_sign_in' then 
  when 'last_joined' then order_created_desc
  when 'oldest_joined' then order_created_asc
  else
    order_by(method)
  end
end

Instance Method Details

#accept_invite!(new_user) ⇒ Object


337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'app/models/member.rb', line 337

def accept_invite!(new_user)
  return false unless invite?

  self.invite_token = nil
  self.invite_accepted_at = Time.current.utc

  self.user = new_user

  saved = self.save

  after_accept_invite if saved

  saved
end

#accept_requestObject


328
329
330
331
332
333
334
335
# File 'app/models/member.rb', line 328

def accept_request
  return false unless request?

  updated = self.update(requested_at: nil)
  after_accept_request if updated

  updated
end

#access_fieldObject


312
313
314
# File 'app/models/member.rb', line 312

def access_field
  access_level
end

#create_notification_settingObject


388
389
390
# File 'app/models/member.rb', line 388

def create_notification_setting
  user.notification_settings.find_or_create_for(source)
end

#created_by_nameObject


422
423
424
# File 'app/models/member.rb', line 422

def created_by_name
  created_by&.name
end

#decline_invite!Object


352
353
354
355
356
357
358
359
360
# File 'app/models/member.rb', line 352

def decline_invite!
  return false unless invite?

  destroyed = self.destroy

  after_decline_invite if destroyed

  destroyed
end

#destroy_notification_settingObject


392
393
394
# File 'app/models/member.rb', line 392

def destroy_notification_setting
  notification_setting&.destroy
end

#generate_invite_tokenObject


362
363
364
365
366
# File 'app/models/member.rb', line 362

def generate_invite_token
  raw, enc = Devise.token_generator.generate(self.class, :invite_token)
  @raw_invite_token = raw
  self.invite_token = enc
end

#generate_invite_token!Object


368
369
370
# File 'app/models/member.rb', line 368

def generate_invite_token!
  generate_invite_token && save(validate: false)
end

#highest_group_memberObject

Find the user's group member with a highest access level


410
411
412
413
414
415
416
# File 'app/models/member.rb', line 410

def highest_group_member
  strong_memoize(:highest_group_member) do
    next unless user_id && source&.ancestors&.any?

    GroupMember.where(source: source.ancestors, user_id: user_id).order(:access_level).last
  end
end

#invite?Boolean

Returns:

  • (Boolean)

316
317
318
# File 'app/models/member.rb', line 316

def invite?
  self.invite_token.present?
end

#invite_to_unknown_user?Boolean

Returns:

  • (Boolean)

418
419
420
# File 'app/models/member.rb', line 418

def invite_to_unknown_user?
  invite? && user_id.nil?
end

#notifiable?(type, opts = {}) ⇒ Boolean

rubocop: disable CodeReuse/ServiceClass

Returns:

  • (Boolean)

401
402
403
404
405
406
# File 'app/models/member.rb', line 401

def notifiable?(type, opts = {})
  # always notify when there isn't a user yet
  return true if user.blank?

  NotificationRecipients::BuildService.notifiable?(user, type, notifiable_options.merge(opts))
end

#notification_settingObject


396
397
398
# File 'app/models/member.rb', line 396

def notification_setting
  @notification_setting ||= user&.notification_settings_for(source)
end

#pending?Boolean

Returns:

  • (Boolean)

324
325
326
# File 'app/models/member.rb', line 324

def pending?
  invite? || request?
end

#real_source_typeObject


308
309
310
# File 'app/models/member.rb', line 308

def real_source_type
  source_type
end

#request?Boolean

Returns:

  • (Boolean)

320
321
322
# File 'app/models/member.rb', line 320

def request?
  requested_at.present?
end

#resend_inviteObject


372
373
374
375
376
377
378
# File 'app/models/member.rb', line 372

def resend_invite
  return unless invite?

  generate_invite_token! unless @raw_invite_token

  send_invite
end

#send_invitation_reminder(reminder_index) ⇒ Object


380
381
382
383
384
385
386
# File 'app/models/member.rb', line 380

def send_invitation_reminder(reminder_index)
  return unless invite?

  generate_invite_token! unless @raw_invite_token

  run_after_commit_or_now { notification_service.invite_member_reminder(self, @raw_invite_token, reminder_index) }
end