Allow accounts to specify session timeout time
fixes #11388 This should work for single and multiple accounts. You can now enable a plugin that lets you set how long (in minutes) before users on your account are automatically logged of because of inactivity. You are required to set this to at least 20 minutes or more. Test Plan Steps: 1. log in as a site admin 2. [plugins] 3. [Sessions] 4. on the account drop down menu, select all accounts, then enter a time in the text field in minutes. At least 20 minutes 5. [Apply] 6. log out 7. go to /login and make sure the "stay signed in" checkbox is checked 8. log in with any user that can get on the account you enabled the plugin to work for 9. wait for a little longer than the amount of time you set the plugin for 10. try to complete an action, like clicking on course or the canvas home page logo You should be logged out Thanks Adam for writing this test plan. Change-Id: If7dc772e4a1a59e646645c698d732308d3e0a19f Reviewed-on: https://gerrit.instructure.com/15231 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
parent
c10ea6f852
commit
c0809345a9
2
Gemfile
2
Gemfile
|
@ -13,7 +13,7 @@ gem 'bcrypt-ruby', '3.0.1'
|
|||
gem 'builder', '2.1.2'
|
||||
gem 'daemons', '1.1.0'
|
||||
gem 'diff-lcs', '1.1.2', :require => 'diff/lcs'
|
||||
gem 'encrypted_cookie_store-instructure', '1.0.1', :require => 'encrypted_cookie_store'
|
||||
gem 'encrypted_cookie_store-instructure', '1.0.2', :require => 'encrypted_cookie_store'
|
||||
gem 'erubis', '2.7.0'
|
||||
gem 'ffi', '1.1.5'
|
||||
gem 'hairtrigger', '0.1.14'
|
||||
|
|
|
@ -17,4 +17,7 @@
|
|||
#
|
||||
|
||||
module PseudonymSessionsHelper
|
||||
def session_timeout_enabled
|
||||
PluginSetting.settings_for_plugin 'sessions'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
class SessionsTimeout
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
# When loading an account, set the expire_after key if they have set up session
|
||||
# timeouts in the plugin settings. :expire_after is relative to Time.now and
|
||||
# should be a Fixnum. This will work it's way up to encrypted_cookie_store.rb
|
||||
# where the session's expire time is determined. EncryptedCookieStore is in a gem.
|
||||
def call(env)
|
||||
session_option_key = ActionController::Session::CookieStore::ENV_SESSION_OPTIONS_KEY
|
||||
sessions_settings = Canvas::Plugin.find('sessions').settings
|
||||
sessions_timeout = 1.day # defaults to 1 day (in seconds)
|
||||
|
||||
# Grab settings, convert them to seconds.(everything is converted down to seconds)
|
||||
if sessions_settings && sessions_settings["session_timeout"].present?
|
||||
sessions_timeout = sessions_settings["session_timeout"].to_f.minutes
|
||||
end
|
||||
|
||||
options = env[session_option_key]
|
||||
options[:expire_after] = sessions_timeout
|
||||
env[session_option_key] = options
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
<% fields_for :settings, OpenObject.new(settings) do |f| %>
|
||||
<table style="width: 500px;" class="formtable">
|
||||
<tr>
|
||||
<td><%= f.blabel :session_timeout, :en => "Time before session expires in minutes (20 minimum)" %></td>
|
||||
<td><%= f.text_field :session_timeout %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
|
@ -102,7 +102,11 @@ $(document).ready(function() {
|
|||
</div>
|
||||
<div>
|
||||
<div class="login-options">
|
||||
<%= f.check_box :remember_me, :checked => session[:used_remember_me_token] %> <%= f.label :remember_me, :en => "Stay signed in" %>
|
||||
<% unless session_timeout_enabled %>
|
||||
<%= f.check_box :remember_me, :checked => session[:used_remember_me_token] %>
|
||||
<%= f.label :remember_me, :en => "Stay signed in" %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% url = (params[:canvas_login] != '1' && @domain_root_account.try(:forgot_password_external_url)) || "#" %><br />
|
||||
<%= link_to t('dont_know_password', "Don't know your password?"), url, :class => (url != '#' ? "not_external" : "forgot_password_link"), :id => "login_forgot_password" %>
|
||||
|
|
|
@ -72,6 +72,7 @@ Rails::Initializer.run do |config|
|
|||
|
||||
config.autoload_paths += %W( #{RAILS_ROOT}/app/middleware #{RAILS_ROOT}/app/observers )
|
||||
|
||||
config.middleware.insert_after(ActionController::Base.session_store, 'SessionsTimeout')
|
||||
config.middleware.insert_before('ActionController::ParamsParser', 'LoadAccount')
|
||||
config.middleware.insert_before('ActionController::ParamsParser', 'StatsTiming')
|
||||
config.middleware.insert_before('ActionController::ParamsParser', 'PreventNonMultipartParse')
|
||||
|
|
|
@ -190,6 +190,19 @@ if Attachment.s3_storage?
|
|||
:settings => Attachment.s3_config
|
||||
})
|
||||
end
|
||||
|
||||
Canvas::Plugin.register('sessions', nil, {
|
||||
:name => lambda{ t :name, 'Sessions' },
|
||||
:description => lambda{ t :description, 'Manage session timeouts' },
|
||||
:website => 'http://www.instructure.com',
|
||||
:author => 'Instructure',
|
||||
:author_website => 'http://www.instructure.com',
|
||||
:version => '1.0.0',
|
||||
:settings_partial => 'plugins/sessions_timeout',
|
||||
:validator => 'SessionsValidator',
|
||||
:settings => nil
|
||||
})
|
||||
|
||||
Canvas::Plugin.register('assignment_freezer', nil, {
|
||||
:name => lambda{ t :name, 'Assignment Property Freezer' },
|
||||
:description => lambda{ t :description, 'Freeze Assignment Properties on Copy' },
|
||||
|
@ -200,6 +213,7 @@ Canvas::Plugin.register('assignment_freezer', nil, {
|
|||
:settings_partial => 'plugins/assignment_freezer_settings',
|
||||
:settings => nil
|
||||
})
|
||||
|
||||
Canvas::Plugin.register('embedly', nil, {
|
||||
:name => lambda{ t :name, 'Embedly Integration' },
|
||||
:description => lambda{ t :description, 'Pull Embedly info for Collections' },
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# Copyright (C) 2011 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
module Canvas::Plugins::Validators::SessionsValidator
|
||||
def self.validate(settings, plugin_setting)
|
||||
timeout = settings["session_timeout"].to_f.minutes
|
||||
if timeout < 20.minutes
|
||||
plugin_setting.errors.add_to_base(I18n.t('canvas.plugins.errors.login_expiration_minimum', 'Session expiration must be 20 minutes or greater'))
|
||||
else
|
||||
settings
|
||||
end
|
||||
end
|
||||
end
|
|
@ -26,4 +26,25 @@ describe PseudonymSessionsHelper do
|
|||
included_modules.should be_include(PseudonymSessionsHelper)
|
||||
end
|
||||
|
||||
describe "#session_timeout_enabled" do
|
||||
context "when the sessions plugin is enabled" do
|
||||
before do
|
||||
PluginSetting.expects(:settings_for_plugin).with('sessions').returns({"session_timeout" => 123})
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
helper.session_timeout_enabled.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context "when the sessions plugin is disabled" do
|
||||
before do
|
||||
PluginSetting.expects(:settings_for_plugin).with('sessions').returns(nil)
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
helper.session_timeout_enabled.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,11 +19,14 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||
|
||||
describe CollectionItemsController do
|
||||
before(:all) { Bundler.require :embedly }
|
||||
before(:all) {Bundler.require :embedly}
|
||||
|
||||
context "#link_data" do
|
||||
before do
|
||||
PluginSetting.expects(:settings_for_plugin)
|
||||
end
|
||||
|
||||
it "should error if the user isn't logged in" do
|
||||
PluginSetting.expects(:settings_for_plugin).never
|
||||
post "/collection_items/link_data", :url => "http://www.example.com/"
|
||||
response.status.to_i.should == 401
|
||||
end
|
||||
|
@ -45,14 +48,18 @@ describe CollectionItemsController do
|
|||
it "should return basic data for free embedly accounts" do
|
||||
user_session(user)
|
||||
data = OpenObject.new(:title => "t1", :description => "d1", :thumbnail_url => "/1.jpg")
|
||||
|
||||
PluginSetting.expects(:settings_for_plugin).with(:embedly).returns({ :api_key => 'test', :plan_type => 'free'})
|
||||
|
||||
Embedly::API.any_instance.expects(:oembed).with(
|
||||
:url => "http://www.example.com/",
|
||||
:autoplay => true,
|
||||
:maxwidth => Canvas::Embedly::MAXWIDTH
|
||||
).returns([data])
|
||||
|
||||
post "/collection_items/link_data", :url => "http://www.example.com/"
|
||||
response.should be_success
|
||||
|
||||
json_parse.should == {
|
||||
'title' => data.title,
|
||||
'description' => data.description,
|
||||
|
@ -65,6 +72,7 @@ describe CollectionItemsController do
|
|||
it "should return extended data for paid embedly accounts" do
|
||||
user_session(user)
|
||||
data = OpenObject.new(:title => "t1", :description => "d1", :images => [{'url' => 'u1'},{'url' => 'u2'}], :object => OpenObject.new(:html => "<iframe src='test'></iframe>"))
|
||||
|
||||
PluginSetting.expects(:settings_for_plugin).with(:embedly).returns({ :api_key => 'test', :plan_type => 'paid'})
|
||||
Embedly::API.any_instance.expects(:preview).with(
|
||||
:url => "http://www.example.com/",
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# Copyright (C) 2011 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||
|
||||
describe "Session Timeout" do
|
||||
context " when sessions timeout is set to 30 minutes" do
|
||||
before do
|
||||
plugin_setting = PluginSetting.new(:name => "sessions", :settings => {"session_timeout" => "30"})
|
||||
plugin_setting.save!
|
||||
end
|
||||
|
||||
context "when a user logs in" do
|
||||
before do
|
||||
course_with_student(:active_all => true, :user => user_with_pseudonym)
|
||||
login_as
|
||||
end
|
||||
|
||||
it "should time out after 40 minutes of inactivity" do
|
||||
now = Time.now
|
||||
get "/"
|
||||
response.should be_success
|
||||
|
||||
Time.stubs(:now).returns(now + 40.minutes)
|
||||
get "/"
|
||||
response.should redirect_to "http://www.example.com/login"
|
||||
end
|
||||
|
||||
it "should not time out if the user remains active" do
|
||||
now = Time.now
|
||||
get "/"
|
||||
response.should be_success
|
||||
|
||||
Time.stubs(:now).returns(now + 20.minutes)
|
||||
get "/"
|
||||
response.should be_success
|
||||
|
||||
Time.stubs(:now).returns(now + 40.minutes)
|
||||
get "/"
|
||||
response.should be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
require File.expand_path('spec/selenium/common')
|
||||
|
||||
describe "Sessions Timeout" do
|
||||
it_should_behave_like "in-process server selenium tests"
|
||||
|
||||
describe "Validations" do
|
||||
context "when you are logged in as an admin" do
|
||||
before do
|
||||
user_logged_in
|
||||
Account.site_admin.add_user(@user)
|
||||
end
|
||||
|
||||
it "requires session expiration to be at least 20 minutes" do
|
||||
get "/plugins/sessions"
|
||||
f("#plugin_setting_disabled").click
|
||||
f('#settings_session_timeout').send_keys('19')
|
||||
expect_new_page_load{ f('.save_button').click }
|
||||
assert_flash_error_message /There was an error saving the plugin settings/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when sessions timeout is set to .04 minutes" do
|
||||
before do
|
||||
plugin_setting = PluginSetting.new(:name => "sessions", :settings => {"session_timeout" => ".04"})
|
||||
plugin_setting.save!
|
||||
end
|
||||
|
||||
context "when a user is logged in" do
|
||||
before do
|
||||
user_with_pseudonym({:active_user => true})
|
||||
login_as
|
||||
f('.user_name').text.should == @user.primary_pseudonym.unique_id
|
||||
end
|
||||
|
||||
it "logs the user out after 3 seconds" do
|
||||
sleep 3
|
||||
|
||||
get "/courses"
|
||||
|
||||
assert_flash_warning_message /You must be logged in to access this page/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue