summaryrefslogtreecommitdiff
path: root/app/models/active_session.rb
diff options
context:
space:
mode:
authorAlexis Reigel ( 🌴 may 2nd - may 9th 🌴 ) <mail@koffeinfrei.org>2018-05-02 08:08:16 +0000
committerDouwe Maan <douwe@gitlab.com>2018-05-02 08:08:16 +0000
commit9b33e3d36fcd46072b9fe83f1121fb0fd87c0fd7 (patch)
tree968009edb90046874d6c9d733239f77f42d19cdf /app/models/active_session.rb
parentd812ef0170ba2b482f096772d2307c64a7f6fc94 (diff)
downloadgitlab-ce-9b33e3d36fcd46072b9fe83f1121fb0fd87c0fd7.tar.gz
Display and revoke active sessions
Diffstat (limited to 'app/models/active_session.rb')
-rw-r--r--app/models/active_session.rb110
1 files changed, 110 insertions, 0 deletions
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
new file mode 100644
index 00000000000..b4a86dbb331
--- /dev/null
+++ b/app/models/active_session.rb
@@ -0,0 +1,110 @@
+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