diff --git a/app/models/user.rb b/app/models/user.rb index 31ca63bf89f..de7e335c64e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,6 +26,7 @@ class User < ApplicationRecord has_many :unseen_notification_recipients, -> { unseen }, :class_name => 'NotificationRecipient' has_many :unseen_notifications, :through => :unseen_notification_recipients, :source => :notification has_many :authentications, :foreign_key => :evm_owner_id, :dependent => :nullify, :inverse_of => :evm_owner + has_many :sessions, :dependent => :destroy belongs_to :current_group, :class_name => "MiqGroup" has_and_belongs_to_many :miq_groups scope :superadmins, lambda { @@ -206,7 +207,42 @@ def self.authenticator(username = nil) end def self.authenticate(username, password, request = nil, options = {}) - authenticator(username).authenticate(username, password, request, options) + user = authenticator(username).authenticate(username, password, request, options) + user.try(:link_to_session, request) + + user + end + + def link_to_session(request) + return unless request + return unless (session_id = request.session_options[:id]) + + sessions << Session.find_or_create_by(:session_id => session_id) + end + + def broadcast_revoke_sessions + if Settings.server.session_store == "cache" + MiqQueue.broadcast( + :class_name => self.class.name, + :instance_id => id, + :method_name => :revoke_sessions + ) + else + # If using SQL or Memory, the sessions don't need to (or can't) be + # revoked via a broadcast since the session/token stores are not server + # specific, so execute it inline. + revoke_sessions + end + end + + def revoke_sessions + current_sessions = Session.where(:user_id => id) + ManageIQ::Session.revoke(current_sessions.map(&:session_id)) + current_sessions.destroy_all + + TokenStore.token_caches.each do |_, token_store| + token_store.delete_all_for_user(userid) + end end def self.authenticate_with_http_basic(username, password, request = nil, options = {}) diff --git a/lib/token_manager.rb b/lib/token_manager.rb index 9d6970c65df..e463157846d 100644 --- a/lib/token_manager.rb +++ b/lib/token_manager.rb @@ -17,9 +17,9 @@ def gen_token(token_options = {}) ttl = token_options.delete(:token_ttl_override) || token_ttl token_data = {:token_ttl => ttl, :expires_on => Time.now.utc + ttl} - token_store.write(token, - token_data.merge!(prune_token_options(token_options)), - :expires_in => token_ttl) + token_store.create_user_token(token, + token_data.merge!(prune_token_options(token_options)), + :expires_in => token_ttl) token end diff --git a/lib/token_store.rb b/lib/token_store.rb index 9927116880b..ed1f6fcb4cb 100644 --- a/lib/token_store.rb +++ b/lib/token_store.rb @@ -1,20 +1,47 @@ class TokenStore - @token_caches = {} # Hash of Memory/Dalli Store Caches, Keyed by namespace + module KeyValueHelpers + def delete_all_for_user(userid) + Array(read("tokens_for_#{userid}")).each do |token| + delete(token) + end + + delete("tokens_for_#{userid}") + end + + def create_user_token(token, data, options) + write(token, data, options) + ensure + if data[:userid] + user_tokens_cache_key = "tokens_for_#{data[:userid]}" + user_tokens_cache = read(user_tokens_cache_key) || [] + user_tokens_cache << token + write(user_tokens_cache_key, user_tokens_cache) + end + end + end + + def self.token_caches + @token_caches ||= {} # Hash of Memory/Dalli Store Caches, Keyed by namespace + end # only used by TokenManager.token_store # @return a token store for users def self.acquire(namespace, token_ttl) - @token_caches[namespace] ||= begin + token_caches[namespace] ||= begin options = cache_store_options(namespace, token_ttl) case ::Settings.server.session_store when "sql" SqlStore.new(options) when "memory" require 'active_support/cache/memory_store' - ActiveSupport::Cache::MemoryStore.new(options) + ActiveSupport::Cache::MemoryStore.new(options).tap do |store| + store.extend KeyValueHelpers + end when "cache" require 'active_support/cache/dalli_store' - ActiveSupport::Cache::DalliStore.new(MiqMemcached.server_address, options) + ActiveSupport::Cache::DalliStore.new(MiqMemcached.server_address, options).tap do |store| + store.extend KeyValueHelpers + end else raise "unsupported session store type: #{::Settings.server.session_store}" end diff --git a/lib/token_store/sql_store.rb b/lib/token_store/sql_store.rb index 55812d0f9e8..511f4a9e4fe 100644 --- a/lib/token_store/sql_store.rb +++ b/lib/token_store/sql_store.rb @@ -4,9 +4,14 @@ def initialize(options) @namespace = options.fetch(:namespace) end + def create_user_token(token, data, options) + write(token, data, options) + end + def write(token, data, _options = nil) record = Session.find_or_create_by(:session_id => session_key(token)) record.raw_data = data + record.user_id = find_user_by_userid(data[:userid]).try(:id) if data[:userid] record.save! end @@ -28,6 +33,11 @@ def delete(token) record.destroy! end + def delete_all_for_user(userid) + user = find_user_by_userid(userid) + user.sessions.where(Session.arel_table[:session_id].matches("#{@namespace}%", nil, true)).destroy_all + end + private attr_reader :namespace @@ -35,5 +45,13 @@ def delete(token) def session_key(token) "#{namespace}:#{token}" end + + def find_user_by_userid(userid) + User.in_my_region.where('lower(userid) = ?', userid.downcase).first + end + + def find_user_by_id(id) + User.in_my_region.where(:id => id).first + end end end