Class: Users::RefreshAuthorizedProjectsService

Inherits:
Object
  • Object
show all
Defined in:
app/services/users/refresh_authorized_projects_service.rb

Overview

Service for refreshing the authorized projects of a user.

This particular service class can not be used to update data for the same user concurrently. Doing so could lead to an incorrect state. To ensure this doesn’t happen a caller must synchronize access (e.g. using Gitlab::ExclusiveLease).

Usage:

user = User.find_by(username: 'alice')
service = Users::RefreshAuthorizedProjectsService.new(some_user)
service.execute

Constant Summary collapse

LEASE_TIMEOUT =
1.minute.to_i

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user, source: nil, incorrect_auth_found_callback: nil, missing_auth_found_callback: nil) ⇒ RefreshAuthorizedProjectsService

user - The User for which to refresh the authorized projects.



22
23
24
25
26
27
28
29
30
# File 'app/services/users/refresh_authorized_projects_service.rb', line 22

def initialize(user, source: nil, incorrect_auth_found_callback: nil, missing_auth_found_callback: nil)
  @user = user
  @source = source
  @incorrect_auth_found_callback = incorrect_auth_found_callback
  @missing_auth_found_callback = missing_auth_found_callback

  @start_time = current_monotonic_time
  @duration_statistics = {}
end

Instance Attribute Details

#sourceObject (readonly)

Returns the value of attribute source.



17
18
19
# File 'app/services/users/refresh_authorized_projects_service.rb', line 17

def source
  @source
end

#userObject (readonly)

Returns the value of attribute user.



17
18
19
# File 'app/services/users/refresh_authorized_projects_service.rb', line 17

def user
  @user
end

Instance Method Details

#executeObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'app/services/users/refresh_authorized_projects_service.rb', line 32

def execute
  lease_key = "refresh_authorized_projects:#{user.id}"
  lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)

  until uuid = lease.try_obtain
    # Keep trying until we obtain the lease. If we don't do so we may end up
    # not updating the list of authorized projects properly. To prevent
    # hammering Redis too much we'll wait for a bit between retries.
    sleep(0.1)
  end

  reset_timer_and_store_duration(:obtain_redis_lease)

  begin
    # We need an up to date User object that has access to all relations that
    # may have been created earlier. The only way to ensure this is to reload
    # the User object.
    user.reset
    execute_without_lease
  ensure
    Gitlab::ExclusiveLease.cancel(lease_key, uuid)
  end
end

#execute_without_leaseObject

This method returns the updated User object.



57
58
59
60
61
62
63
64
65
66
67
68
# File 'app/services/users/refresh_authorized_projects_service.rb', line 57

def execute_without_lease
  remove, add = AuthorizedProjectUpdate::FindRecordsDueForRefreshService.new(
    user,
    source: source,
    incorrect_auth_found_callback: incorrect_auth_found_callback,
    missing_auth_found_callback: missing_auth_found_callback
  ).execute

  reset_timer_and_store_duration(:find_records_due_for_refresh)

  update_authorizations(remove, add)
end

#update_authorizations(remove = [], add = []) ⇒ Object

Updates the list of authorizations for the current user.

remove - The project IDs of the authorization rows to remove. add - Rows to insert in the form ‘[{ user_id: user_id, project_id: project_id, access_level: access_level}, …]`



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/services/users/refresh_authorized_projects_service.rb', line 74

def update_authorizations(remove = [], add = [])
  ProjectAuthorizations::Changes.new do |changes|
    changes.add(add)
    changes.remove_projects_for_user(user, remove)
  end.apply!

  user.update!(project_authorizations_recalculated_at: Time.zone.now) if remove.any? || add.any?

  reset_timer_and_store_duration(:update_authorizations)

  log_refresh_details(remove, add)

  # Since we batch insert authorization rows, Rails' associations may get
  # out of sync. As such we force a reload of the User object.
  user.reset
end