Commit 5d559f6
Changed files (8)
app
controllers
concerns
models
views
scim
v2
users
db
spec
factories
models
app/controllers/concerns/authentication.rb
@@ -29,10 +29,7 @@ module Authentication
redirect_to new_mfa_path unless mfa.valid_session?(session[:mfa])
end
- def apply_current_request_details(uuid: session[:user_id])
- Current.request_id = request.uuid
- Current.user_agent = request.user_agent
- Current.ip_address = request.ip
- Current.user = User.find_by(uuid: uuid) if uuid.present?
+ def apply_current_request_details
+ Current.access(request, session)
end
end
app/models/current.rb
@@ -12,4 +12,12 @@ class Current < ActiveSupport::CurrentAttributes
super
self.user = token&.subject
end
+
+ def access(request, session)
+ self.request_id = request.uuid
+ self.user_agent = request.user_agent
+ self.ip_address = request.ip
+ uuid = session[:user_id]
+ self.user = User.find_by(uuid: uuid) if uuid.present?
+ end
end
app/models/user_session.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class UserSession < ApplicationRecord
+ belongs_to :user
+ before_validation :set_unique_key
+
+ scope :active, -> { where("accessed_at > ?", 30.minutes.ago).where('created_at > ?', 24.hours.ago).where(revoked_at: nil) }
+
+ def self.authenticate(key)
+ active.find_by(key: key)
+ end
+
+ def revoke!
+ update!(revoked_at: Time.now)
+ end
+
+ def sudo?
+ sudo_enabled_at.present? && sudo_enabled_at > 1.hour.ago
+ end
+
+ def sudo!
+ update!(sudo_enabled_at: Time.now)
+ end
+
+ def access(request)
+ update(
+ accessed_at: Time.now,
+ ip: request.ip,
+ user_agent: request.user_agent,
+ )
+ end
+
+ private
+
+ def set_unique_key
+ self.key = SecureRandom.urlsafe_base64(32)
+ end
+end
app/views/scim/v2/users/show.scim.jbuilder
@@ -20,4 +20,4 @@ json.locale @user.try(:locale)
json.timezone @user.try(:timezone)
json.active true
json.emails [{ value: @user.email, primary: true }]
-json.groups [ ]
+json.groups []
db/migrate/20180922153546_create_user_sessions.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateUserSessions < ActiveRecord::Migration[5.2]
+ def change
+ create_table :user_sessions do |t|
+ t.references :user, foreign_key: true
+ t.string :key
+ t.string :ip
+ t.text :user_agent
+ t.datetime :sudo_enabled_at
+ t.datetime :accessed_at
+ t.datetime :revoked_at
+
+ t.timestamps
+ end
+ add_index :user_sessions, :key, unique: true
+ end
+end
db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2018_09_09_173139) do
+ActiveRecord::Schema.define(version: 2018_09_22_153546) do
create_table "authorizations", force: :cascade do |t|
t.integer "user_id"
@@ -62,6 +62,20 @@ ActiveRecord::Schema.define(version: 2018_09_09_173139) do
t.index ["uuid"], name: "index_tokens_on_uuid", unique: true
end
+ create_table "user_sessions", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "key"
+ t.string "ip"
+ t.text "user_agent"
+ t.datetime "sudo_enabled_at"
+ t.datetime "accessed_at"
+ t.datetime "revoked_at"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["key"], name: "index_user_sessions_on_key", unique: true
+ t.index ["user_id"], name: "index_user_sessions_on_user_id"
+ end
+
create_table "users", force: :cascade do |t|
t.string "email"
t.string "uuid", null: false
spec/factories/user_session.rb
@@ -0,0 +1,24 @@
+FactoryBot.define do
+ factory :user_session do
+ user
+ ip { FFaker::Internet.ip_v4_address }
+ user_agent { "Googlebot/2.1 (+http://www.google.com/bot.html)" }
+ accessed_at { 1.minute.ago }
+
+ trait :idle_timeout_expired do
+ accessed_at { 31.minutes.ago }
+ end
+
+ trait :absolute_timeout_expired do
+ created_at { (24.hours + 1.second).ago }
+ end
+
+ trait :revoked do
+ revoked_at { 1.minute.ago }
+ end
+
+ trait :sudo do
+ sudo_enabled_at { 1.minute.ago }
+ end
+ end
+end
spec/models/user_session_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+RSpec.describe UserSession do
+ subject { build(:user_session) }
+
+ describe "#revoke!" do
+ before { subject.revoke! }
+
+ specify { expect(subject.revoked_at).to be_present }
+ end
+
+ describe "#access" do
+ let(:request) { double(ip: "192.168.1.1", user_agent: "blah") }
+
+ before { freeze_time }
+ before { subject.access(request) }
+
+ specify { expect(subject.accessed_at).to eql(Time.now) }
+ specify { expect(subject.ip).to eql(request.ip) }
+ specify { expect(subject.user_agent).to eql(request.user_agent) }
+ specify { expect(subject).to be_persisted }
+ end
+
+ describe ".active" do
+ let!(:active_session) { create(:user_session) }
+ let!(:inactive_session) { create(:user_session, :idle_timeout_expired) }
+ let!(:expired_session) { create(:user_session, :absolute_timeout_expired) }
+ let!(:revoked_session) { create(:user_session, :revoked) }
+
+ specify { expect(UserSession.active).to match_array([active_session]) }
+ end
+
+ describe ".authenticate" do
+ let!(:active_session) { create(:user_session) }
+ let!(:inactive_session) { create(:user_session, :idle_timeout_expired) }
+ let!(:expired_session) { create(:user_session, :absolute_timeout_expired) }
+ let!(:revoked_session) { create(:user_session, :revoked) }
+
+ specify { expect(UserSession.authenticate(active_session.key)).to eql(active_session) }
+ specify { expect(UserSession.authenticate("blah")).to be_nil }
+ specify { expect(UserSession.authenticate(inactive_session.key)).to be_nil }
+ specify { expect(UserSession.authenticate(expired_session.key)).to be_nil }
+ specify { expect(UserSession.authenticate(revoked_session.key)).to be_nil }
+ end
+
+ describe ".sudo?" do
+ let!(:sudo_session) { create(:user_session, :sudo) }
+ let!(:non_sudo_session) { create(:user_session) }
+
+ specify { expect(sudo_session).to be_sudo }
+ specify { expect(non_sudo_session).not_to be_sudo }
+ end
+end