Class: Gitlab::Auth::CurrentUserMode

Inherits:
Object
  • Object
show all
Includes:
Utils::StrongMemoize
Defined in:
lib/gitlab/auth/current_user_mode.rb

Overview

Keeps track of the current session user mode

In order to perform administrative tasks over some interfaces, an administrator must have explicitly enabled admin-mode e.g. on web access require re-authentication

Constant Summary collapse

NotRequestedError =
Class.new(StandardError)
NonSidekiqEnvironmentError =
Class.new(StandardError)
CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY =

RequestStore entries

{ res: :current_user_mode, data: :bypass_session_admin_id }.freeze
CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY =
{ res: :current_user_mode, data: :current_admin }.freeze
SESSION_STORE_KEY =

SessionStore entries

:current_user_mode
ADMIN_MODE_START_TIME_KEY =
:admin_mode
ADMIN_MODE_REQUESTED_TIME_KEY =
:admin_mode_requested
MAX_ADMIN_MODE_TIME =
6.hours
ADMIN_MODE_REQUESTED_GRACE_PERIOD =
5.minutes

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user, session = Gitlab::Session.current) ⇒ CurrentUserMode

Returns a new instance of CurrentUserMode.



104
105
106
107
# File 'lib/gitlab/auth/current_user_mode.rb', line 104

def initialize(user, session = Gitlab::Session.current)
  @user = user
  @session = session
end

Class Method Details

.bypass_session!(admin_id) ⇒ Object

Admin mode activation requires storing a flag in the user session. Using this method when scheduling jobs in sessionless environments (e.g. Sidekiq, API) will bypass the session check for a user that was already in admin mode

If passed a block, it will surround the block execution and reset the session bypass at the end; otherwise you must remember to call ‘.reset_bypass_session!’



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/gitlab/auth/current_user_mode.rb', line 33

def bypass_session!(admin_id)
  Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY] = admin_id
  # Bypassing the session invalidates the cached value of admin_mode?
  # Any new calls need to be re-computed.
  uncache_admin_mode_state(admin_id)

  Gitlab::AppLogger.debug("Bypassing session in admin mode for: #{admin_id}")

  return unless block_given?

  begin
    yield
  ensure
    reset_bypass_session!(admin_id)
  end
end

.bypass_session_admin_idObject



56
57
58
# File 'lib/gitlab/auth/current_user_mode.rb', line 56

def bypass_session_admin_id
  Gitlab::SafeRequestStore[CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY]
end

.current_adminObject



85
86
87
# File 'lib/gitlab/auth/current_user_mode.rb', line 85

def current_admin
  Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY]
end

.optionally_run_in_admin_mode(user) ⇒ Object

Execute the given block with admin privileges if the user is an admin and admin mode is enabled. Otherwise, execute the block with regular user permissions.



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/gitlab/auth/current_user_mode.rb', line 91

def optionally_run_in_admin_mode(user)
  raise NonSidekiqEnvironmentError unless Gitlab::Runtime.sidekiq?

  return yield unless Gitlab::CurrentSettings.admin_mode && user.admin?

  bypass_session!(user.id) do
    with_current_admin(user) do
      yield
    end
  end
end

.reset_bypass_session!(admin_id = nil) ⇒ Object



50
51
52
53
54
# File 'lib/gitlab/auth/current_user_mode.rb', line 50

def reset_bypass_session!(admin_id = nil)
  # Restoring the session bypass invalidates the cached value of admin_mode?
  uncache_admin_mode_state(admin_id)
  Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_BYPASS_SESSION_ADMIN_ID_RS_KEY)
end

.uncache_admin_mode_state(admin_id = nil) ⇒ Object



60
61
62
63
64
65
66
67
68
69
# File 'lib/gitlab/auth/current_user_mode.rb', line 60

def uncache_admin_mode_state(admin_id = nil)
  if admin_id
    key = { res: :current_user_mode, user: admin_id, method: :admin_mode? }
    Gitlab::SafeRequestStore.delete(key)
  else
    Gitlab::SafeRequestStore.delete_if do |key|
      key.is_a?(Hash) && key[:res] == :current_user_mode && key[:method] == :admin_mode?
    end
  end
end

.with_current_admin(admin) ⇒ Object

Store in the current request the provided user model (only if in admin mode) and yield



73
74
75
76
77
78
79
80
81
82
83
# File 'lib/gitlab/auth/current_user_mode.rb', line 73

def with_current_admin(admin)
  return yield unless new(admin).admin_mode?

  Gitlab::SafeRequestStore[CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY] = admin

  Gitlab::AppLogger.debug("Admin mode active for: #{admin.username}")

  yield
ensure
  Gitlab::SafeRequestStore.delete(CURRENT_REQUEST_ADMIN_MODE_USER_RS_KEY)
end

Instance Method Details

#admin_mode?Boolean

Returns:

  • (Boolean)


109
110
111
112
113
114
115
# File 'lib/gitlab/auth/current_user_mode.rb', line 109

def admin_mode?
  return false unless user

  Gitlab::SafeRequestStore.fetch(admin_mode_rs_key) do
    user.admin? && (privileged_runtime? || session_with_admin_mode?)
  end
end

#admin_mode_requested?Boolean

Returns:

  • (Boolean)


117
118
119
120
121
122
123
# File 'lib/gitlab/auth/current_user_mode.rb', line 117

def admin_mode_requested?
  return false unless user

  Gitlab::SafeRequestStore.fetch(admin_mode_requested_rs_key) do
    user.admin? && admin_mode_requested_in_grace_period?
  end
end

#current_session_dataObject



158
159
160
# File 'lib/gitlab/auth/current_user_mode.rb', line 158

def current_session_data
  Gitlab::NamespacedSessionStore.new(SESSION_STORE_KEY, @session)
end

#disable_admin_mode!Object



141
142
143
144
145
146
147
148
# File 'lib/gitlab/auth/current_user_mode.rb', line 141

def disable_admin_mode!
  return unless user&.admin?

  reset_request_store_cache_entries

  current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
  current_session_data[ADMIN_MODE_START_TIME_KEY] = nil
end

#enable_admin_mode!(password: nil, skip_password_validation: false) ⇒ Object

Raises:



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/gitlab/auth/current_user_mode.rb', line 125

def enable_admin_mode!(password: nil, skip_password_validation: false)
  return false unless user&.admin?
  return false unless skip_password_validation || user&.valid_password?(password)

  raise NotRequestedError unless admin_mode_requested?

  reset_request_store_cache_entries

  current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
  current_session_data[ADMIN_MODE_START_TIME_KEY] = Time.now

  audit_user_enable_admin_mode

  true
end

#request_admin_mode!Object



150
151
152
153
154
155
156
# File 'lib/gitlab/auth/current_user_mode.rb', line 150

def request_admin_mode!
  return unless user&.admin?

  reset_request_store_cache_entries

  current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = Time.now
end