Commit 79f05f8a authored by James Edwards-Jones's avatar James Edwards-Jones

Sessions can be listed for a given user

Adds ActiveSession#session_ids_for_user for listing session IDs for a
given user, and adds ActiveSession#list_sessions for listing session
data directly.
parent b575b303
...@@ -53,7 +53,7 @@ class ActiveSession ...@@ -53,7 +53,7 @@ class ActiveSession
def self.list(user) def self.list(user)
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
cleaned_up_lookup_entries(redis, user.id).map do |entry| cleaned_up_lookup_entries(redis, user).map do |entry|
# rubocop:disable Security/MarshalLoad # rubocop:disable Security/MarshalLoad
Marshal.load(entry) Marshal.load(entry)
# rubocop:enable Security/MarshalLoad # rubocop:enable Security/MarshalLoad
...@@ -78,7 +78,7 @@ class ActiveSession ...@@ -78,7 +78,7 @@ class ActiveSession
def self.cleanup(user) def self.cleanup(user)
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
cleaned_up_lookup_entries(redis, user.id) cleaned_up_lookup_entries(redis, user)
end end
end end
...@@ -90,25 +90,52 @@ class ActiveSession ...@@ -90,25 +90,52 @@ class ActiveSession
"#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}" "#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}"
end end
def self.cleaned_up_lookup_entries(redis, user_id) def self.list_sessions(user)
lookup_key = lookup_key_name(user_id) sessions_from_ids(session_ids_for_user(user))
end
session_ids = redis.smembers(lookup_key) def self.session_ids_for_user(user)
Gitlab::Redis::SharedState.with do |redis|
redis.smembers(lookup_key_name(user.id))
end
end
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) } def self.sessions_from_ids(session_ids)
return [] if entry_keys.empty? return [] if session_ids.empty?
entries = redis.mget(entry_keys) Gitlab::Redis::SharedState.with do |redis|
session_keys = session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" }
session_ids_and_entries = session_ids.zip(entries) redis.mget(session_keys).compact.map do |raw_session|
# rubocop:disable Security/MarshalLoad
Marshal.load(raw_session)
# rubocop:enable Security/MarshalLoad
end
end
end
def self.raw_active_session_entries(session_ids, user_id)
return [] if session_ids.empty?
Gitlab::Redis::SharedState.with do |redis|
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
redis.mget(entry_keys)
end
end
def self.cleaned_up_lookup_entries(redis, user)
session_ids = session_ids_for_user(user)
entries = raw_active_session_entries(session_ids, user.id)
# remove expired keys. # remove expired keys.
# only the single key entries are automatically expired by redis, the # only the single key entries are automatically expired by redis, the
# lookup entries in the set need to be removed manually. # lookup entries in the set need to be removed manually.
session_ids_and_entries = session_ids.zip(entries)
session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry| session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
redis.srem(lookup_key, session_id) redis.srem(lookup_key_name(user.id), session_id)
end end
session_ids_and_entries.select { |_session_id, entry| entry }.map { |_session_id, entry| entry } entries.compact
end end
end end
...@@ -88,6 +88,52 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do ...@@ -88,6 +88,52 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
end end
end end
describe '.list_sessions' do
it 'uses the ActiveSession lookup to return original sessions' do
Gitlab::Redis::SharedState.with do |redis|
redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' }))
redis.sadd(
"session:lookup:user:gitlab:#{user.id}",
%w[
6919a6f1bb119dd7396fadc38fd18d0d
59822c7d9fcdfa03725eff41782ad97d
]
)
end
expect(ActiveSession.list_sessions(user)).to eq [{ _csrf_token: 'abcd' }]
end
end
describe '.session_ids_for_user' do
it 'uses the user lookup table to return session ids' do
session_ids = ['59822c7d9fcdfa03725eff41782ad97d']
Gitlab::Redis::SharedState.with do |redis|
redis.sadd("session:lookup:user:gitlab:#{user.id}", session_ids)
end
expect(ActiveSession.session_ids_for_user(user)).to eq(session_ids)
end
end
describe '.sessions_from_ids' do
it 'uses the ActiveSession lookup to return original sessions' do
Gitlab::Redis::SharedState.with do |redis|
redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' }))
end
expect(ActiveSession.sessions_from_ids(['6919a6f1bb119dd7396fadc38fd18d0d'])).to eq [{ _csrf_token: 'abcd' }]
end
it 'avoids a redis lookup for an empty array' do
expect(Gitlab::Redis::SharedState).not_to receive(:with)
expect(ActiveSession.sessions_from_ids([])).to eq([])
end
end
describe '.set' do describe '.set' do
it 'sets a new redis entry for the user session and a lookup entry' do it 'sets a new redis entry for the user session and a lookup entry' do
ActiveSession.set(user, request) ActiveSession.set(user, request)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment