111 lines
3.1 KiB
Ruby
111 lines
3.1 KiB
Ruby
|
class ActiveSession
|
||
|
include ActiveModel::Model
|
||
|
|
||
|
attr_accessor :created_at, :updated_at,
|
||
|
:session_id, :ip_address,
|
||
|
:browser, :os, :device_name, :device_type
|
||
|
|
||
|
def current?(session)
|
||
|
return false if session_id.nil? || session.id.nil?
|
||
|
|
||
|
session_id == session.id
|
||
|
end
|
||
|
|
||
|
def human_device_type
|
||
|
device_type&.titleize
|
||
|
end
|
||
|
|
||
|
def self.set(user, request)
|
||
|
Gitlab::Redis::SharedState.with do |redis|
|
||
|
session_id = request.session.id
|
||
|
client = DeviceDetector.new(request.user_agent)
|
||
|
timestamp = Time.current
|
||
|
|
||
|
active_user_session = new(
|
||
|
ip_address: request.ip,
|
||
|
browser: client.name,
|
||
|
os: client.os_name,
|
||
|
device_name: client.device_name,
|
||
|
device_type: client.device_type,
|
||
|
created_at: user.current_sign_in_at || timestamp,
|
||
|
updated_at: timestamp,
|
||
|
session_id: session_id
|
||
|
)
|
||
|
|
||
|
redis.pipelined do
|
||
|
redis.setex(
|
||
|
key_name(user.id, session_id),
|
||
|
Settings.gitlab['session_expire_delay'] * 60,
|
||
|
Marshal.dump(active_user_session)
|
||
|
)
|
||
|
|
||
|
redis.sadd(
|
||
|
lookup_key_name(user.id),
|
||
|
session_id
|
||
|
)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def self.list(user)
|
||
|
Gitlab::Redis::SharedState.with do |redis|
|
||
|
cleaned_up_lookup_entries(redis, user.id).map do |entry|
|
||
|
# rubocop:disable Security/MarshalLoad
|
||
|
Marshal.load(entry)
|
||
|
# rubocop:enable Security/MarshalLoad
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def self.destroy(user, session_id)
|
||
|
Gitlab::Redis::SharedState.with do |redis|
|
||
|
redis.srem(lookup_key_name(user.id), session_id)
|
||
|
|
||
|
deleted_keys = redis.del(key_name(user.id, session_id))
|
||
|
|
||
|
# only allow deleting the devise session if we could actually find a
|
||
|
# related active session. this prevents another user from deleting
|
||
|
# someone else's session.
|
||
|
if deleted_keys > 0
|
||
|
redis.del("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def self.cleanup(user)
|
||
|
Gitlab::Redis::SharedState.with do |redis|
|
||
|
cleaned_up_lookup_entries(redis, user.id)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def self.key_name(user_id, session_id = '*')
|
||
|
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
|
||
|
end
|
||
|
|
||
|
def self.lookup_key_name(user_id)
|
||
|
"#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}"
|
||
|
end
|
||
|
|
||
|
def self.cleaned_up_lookup_entries(redis, user_id)
|
||
|
lookup_key = lookup_key_name(user_id)
|
||
|
|
||
|
session_ids = redis.smembers(lookup_key)
|
||
|
|
||
|
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
|
||
|
return [] if entry_keys.empty?
|
||
|
|
||
|
entries = redis.mget(entry_keys)
|
||
|
|
||
|
session_ids_and_entries = session_ids.zip(entries)
|
||
|
|
||
|
# remove expired keys.
|
||
|
# only the single key entries are automatically expired by redis, the
|
||
|
# lookup entries in the set need to be removed manually.
|
||
|
session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
|
||
|
redis.srem(lookup_key, session_id)
|
||
|
end
|
||
|
|
||
|
session_ids_and_entries.select { |_session_id, entry| entry }.map { |_session_id, entry| entry }
|
||
|
end
|
||
|
end
|