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, without_order

Instance Attribute Details

#raw_invite_tokenObject

Returns the value of attribute raw_invite_token


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

def raw_invite_token
  @raw_invite_token
end

Class Method Details

.access_for_user_ids(user_ids) ⇒ Object


163
164
165
# File 'app/models/member.rb', line 163

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

.access_levelsObject


236
237
238
# File 'app/models/member.rb', line 236

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


172
173
174
175
176
177
178
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
# File 'app/models/member.rb', line 172

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


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'app/models/member.rb', line 217

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


128
129
130
131
132
133
134
135
136
137
# File 'app/models/member.rb', line 128

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


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

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


152
153
154
155
156
157
158
159
160
161
# File 'app/models/member.rb', line 152

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


120
121
122
# File 'app/models/member.rb', line 120

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

.search_invite_email(query) ⇒ Object


124
125
126
# File 'app/models/member.rb', line 124

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.


209
210
211
212
213
214
215
# File 'app/models/member.rb', line 209

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


139
140
141
142
143
144
145
146
147
148
149
150
# File 'app/models/member.rb', line 139

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


330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'app/models/member.rb', line 330

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


321
322
323
324
325
326
327
328
# File 'app/models/member.rb', line 321

def accept_request
  return false unless request?

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

  updated
end

#access_fieldObject


305
306
307
# File 'app/models/member.rb', line 305

def access_field
  access_level
end

#create_notification_settingObject


373
374
375
# File 'app/models/member.rb', line 373

def create_notification_setting
  user.notification_settings.find_or_create_for(source)
end

#decline_invite!Object


345
346
347
348
349
350
351
352
353
# File 'app/models/member.rb', line 345

def decline_invite!
  return false unless invite?

  destroyed = self.destroy

  after_decline_invite if destroyed

  destroyed
end

#destroy_notification_settingObject


377
378
379
# File 'app/models/member.rb', line 377

def destroy_notification_setting
  notification_setting&.destroy
end

#generate_invite_tokenObject


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

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


361
362
363
# File 'app/models/member.rb', line 361

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


395
396
397
398
399
400
401
# File 'app/models/member.rb', line 395

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)

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

def invite?
  self.invite_token.present?
end

#invite_to_unknown_user?Boolean

Returns:

  • (Boolean)

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

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

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

rubocop: disable CodeReuse/ServiceClass

Returns:

  • (Boolean)

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

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


381
382
383
# File 'app/models/member.rb', line 381

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

#pending?Boolean

Returns:

  • (Boolean)

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

def pending?
  invite? || request?
end

#real_source_typeObject


301
302
303
# File 'app/models/member.rb', line 301

def real_source_type
  source_type
end

#request?Boolean

Returns:

  • (Boolean)

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

def request?
  requested_at.present?
end

#resend_inviteObject


365
366
367
368
369
370
371
# File 'app/models/member.rb', line 365

def resend_invite
  return unless invite?

  generate_invite_token! unless @raw_invite_token

  send_invite
end