From cbca117f72b19e1908b00279ec46ee983caffce2 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 26 Jul 2013 23:45:16 +0200 Subject: [PATCH] Added admin session management. --- .../app/controllers/session.js.coffee | 55 +++++++++++++++++++ .../javascripts/app/views/session.jst.eco | 28 ++++++++++ app/controllers/application_controller.rb | 23 +++++++- app/controllers/sessions_controller.rb | 26 +++++++++ app/models/observer/session.rb | 25 +++++++++ config/application.rb | 1 + config/routes/auth.rb | 14 +++-- db/migrate/20130726000001_update_session.rb | 9 +++ lib/geoip.rb | 26 +++++++++ 9 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/session.js.coffee create mode 100644 app/assets/javascripts/app/views/session.jst.eco create mode 100644 app/models/observer/session.rb create mode 100644 db/migrate/20130726000001_update_session.rb create mode 100644 lib/geoip.rb diff --git a/app/assets/javascripts/app/controllers/session.js.coffee b/app/assets/javascripts/app/controllers/session.js.coffee new file mode 100644 index 000000000..a9e28fec8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/session.js.coffee @@ -0,0 +1,55 @@ +class Session extends App.ControllerContent + events: + 'click [data-type="delete"]': 'destroy' + + constructor: -> + super + # check authentication + return if !@authenticate() + + @load() + @interval( + => + @load + 10000 + ) + + # fetch data, render view + load: -> + App.Com.ajax( + id: 'sessions' + type: 'GET' + url: 'api/sessions' + success: (data) => + @render(data) + ) + + render: (data) -> + App.Collection.load( type: 'User', data: data.users ) + + # fill users + for session in data.sessions + if session.data && session.data.user_id + session.data.user = App.User.find( session.data.user_id ) + + @html App.view('session')( + sessions: data.sessions + ) + + # show frontend times + @frontendTimeUpdate() + + destroy: (e) -> + e.preventDefault() + sessionId = $( e.target ).data('session-id') + App.Com.ajax( + id: 'sessions/' + sessionId + type: 'DELETE' + url: 'api/sessions/' + sessionId + success: (data) => + @load() + ) + + +App.Config.set( 'session', Session, 'Routes' ) +App.Config.set( 'session', { prio: 3700, parent: '#admin', name: 'Sessions', target: '#session', role: ['Admin'] }, 'NavBar' ) diff --git a/app/assets/javascripts/app/views/session.jst.eco b/app/assets/javascripts/app/views/session.jst.eco new file mode 100644 index 000000000..7c3f426cb --- /dev/null +++ b/app/assets/javascripts/app/views/session.jst.eco @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + <% for session in @sessions: %> + + + + + + + + + <% end %> + +
<%- @T('User') %><%- @T('Browser') %><%- @T('Location') %><%- @T('Age') %><%- @T('Update') %>
<% if session.data.user: %><%= session.data.user.displayName() %><% end %><% if session.data.user_agent: %><%= session.data.user_agent %><% end %><% if session.data.geo: %><%= session.data.geo.country_code %> <%= session.data.geo.city %><% end %>??
+ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c6632ee0f..8b6f07e51 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base :mode_show_rendeder, :model_index_render - before_filter :set_user + before_filter :set_user, :session_update before_filter :cors_preflight_check after_filter :set_access_control_headers @@ -73,9 +73,26 @@ class ApplicationController < ActionController::Base UserInfo.current_user_id = current_user.id end + # update session updated_at + def session_update + session[:ping] = Time.now.utc.iso8601 + + # check if remote ip need to be updated + if !session[:remote_id] || session[:remote_id] != request.remote_ip + session[:remote_id] = request.remote_ip + session[:geo] = Geoip.location( request.remote_ip ) + end + + # fill user agent + if !session[:user_agent] + session[:user_agent] = request.env['HTTP_USER_AGENT'] + end + end + def authentication_check_only puts 'authentication_check' + session[:request_type] = 1 #puts params.inspect #puts session.inspect #puts cookies.inspect @@ -83,6 +100,8 @@ class ApplicationController < ActionController::Base # check http basic auth authenticate_with_http_basic do |username, password| puts 'http basic auth check' + session[:request_type] = 2 + userdata = User.authenticate( username, password ) message = '' if !userdata @@ -113,6 +132,8 @@ class ApplicationController < ActionController::Base userdata = User.find( logon_session.data[:user_id] ) end + session[:request_type] = 3 + # set logon session user to current user current_user_set(userdata) return { diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 9ddb62075..e3946f14e 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -148,4 +148,30 @@ class SessionsController < ApplicationController redirect_to '/#' end + def list + return if deny_if_not_role('Admin') + sessions = ActiveRecord::SessionStore::Session.order('created_at DESC').limit(10000) + users = {} + sessions_clean = [] + sessions.each {|session| + next if !session.data['user_id'] + sessions_clean.push session + if session.data['user_id'] + if !users[ session.data['user_id'] ] + users[ session.data['user_id'] ] = User.user_data_full( session.data['user_id'] ) + end + end + } + render :json => { + :sessions => sessions_clean, + :users => users, + } + end + + def delete + return if deny_if_not_role('Admin') + session = ActiveRecord::SessionStore::Session.find(params[:id]) + session.destroy + render :json => {} + end end diff --git a/app/models/observer/session.rb b/app/models/observer/session.rb new file mode 100644 index 000000000..7847a20a9 --- /dev/null +++ b/app/models/observer/session.rb @@ -0,0 +1,25 @@ +# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/ + +require 'history' + +class Observer::Session < ActiveRecord::Observer + observe 'active_record::_session_store::_session' + + def before_create(record) + check(record) + end + def before_update(record) + check(record) + end + + def check(record) + return if !record.data + + # remember request type + if record.data['request_type'] + record[:request_type] = record.data['request_type'] + record.data.delete('request_type') + end + end + +end diff --git a/config/application.rb b/config/application.rb index 5591236e0..83aacad44 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,6 +26,7 @@ module Zammad # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer config.active_record.observers = + 'observer::_session', 'observer::_history', 'observer::_ticket::_first_response', 'observer::_ticket::_last_contact', diff --git a/config/routes/auth.rb b/config/routes/auth.rb index 02130506d..576a0378f 100644 --- a/config/routes/auth.rb +++ b/config/routes/auth.rb @@ -2,16 +2,18 @@ module ExtraRoutes def add(map) # omniauth - map.match '/auth/:provider/callback', :to => 'sessions#create_omniauth' + map.match '/auth/:provider/callback', :to => 'sessions#create_omniauth',:via => [:post, :get, :puts, :delete] # sso - map.match '/auth/sso', :to => 'sessions#create_sso' + map.match '/auth/sso', :to => 'sessions#create_sso', :via => [:post, :get] # sessions - map.match '/signin', :to => 'sessions#create' - map.match '/signshow', :to => 'sessions#show' - map.match '/signout', :to => 'sessions#destroy' + map.match '/signin', :to => 'sessions#create', :via => :post + map.match '/signshow', :to => 'sessions#show', :via => :get + map.match '/signout', :to => 'sessions#destroy', :via => [:get, :delete] + map.match '/api/sessions', :to => 'sessions#list', :via => :get + map.match '/api/sessions/:id', :to => 'sessions#delete', :via => :delete end module_function :add -end \ No newline at end of file +end diff --git a/db/migrate/20130726000001_update_session.rb b/db/migrate/20130726000001_update_session.rb new file mode 100644 index 000000000..2c0fcca00 --- /dev/null +++ b/db/migrate/20130726000001_update_session.rb @@ -0,0 +1,9 @@ +class UpdateSession < ActiveRecord::Migration + def up + add_column :sessions, :request_type, :integer, :null => true + add_index :sessions, :request_type + end + def down + end +end + diff --git a/lib/geoip.rb b/lib/geoip.rb new file mode 100644 index 000000000..319072439 --- /dev/null +++ b/lib/geoip.rb @@ -0,0 +1,26 @@ +require 'faraday' +require 'cache' + +module Geoip + def self.location(address) + + # check cache + cache_key = "geoip::#{address}" + cache = Cache.get( cache_key ) + return cache if cache + + # do lookup + host = "http://freegeoip.net" + url = "/json/#{CGI::escape address}" + data = {} + begin + conn = Faraday.new( :url => host ) + response = conn.get url + data = JSON.parse( response.body ) + Cache.write( cache_key, data, { :expires_in => 90.days } ) + rescue + Cache.write( cache_key, data, { :expires_in => 60.minutes } ) + end + data + end +end