Permalink
Browse files

implements expirable module (and adds a bunch of documentation); closes

#20

Squashed commit of the following:

commit e25c3d1a8b6a5d5fa89e2c6e398d4d3403840030
Author: Alexander Dreher <alexdreher@lxmedia.net>
Date:   Wed Dec 28 17:35:53 2011 +0100

    documentation and methods for cron jobs

commit 7d2383b88171c51cc94b2cbaa01e6f0d30fc2462
Author: Alexander Dreher <alexdreher@lxmedia.net>
Date:   Wed Dec 28 15:17:11 2011 +0100

    documentation and finishing of expirable module

commit 7ec2babaab2870efb7a882c7f50d78be9309e6e4
Author: Alexander Dreher <alexdreher@lxmedia.net>
Date:   Tue Dec 27 17:50:39 2011 +0100

    expiry methods

commit 85b2684bce2f2b969c89f05686eb9047acd6211b
Author: Alexander Dreher <alexdreher@lxmedia.net>
Date:   Tue Dec 27 14:23:55 2011 +0100

    adds config stuff for account expiry

commit 218dc13eafa43489e286fbdb4529eb6e6098ec9e
Author: Alexander Dreher <alexdreher@lxmedia.net>
Date:   Tue Dec 27 14:03:29 2011 +0100

    adds tracking part of expirable module
  • Loading branch information...
1 parent 27756c6 commit 8082b778826d4a1c1f4dde35725909948b6154a4 @alexdreher alexdreher committed Dec 28, 2011
View
@@ -9,12 +9,13 @@ An enterprise security extension for devise, trying to meet industrial standard
=== Model modules
* :password_expirable - passwords will expire after a configured time (and will need an update)
-* :secure_validatable - better way to validate model (email, stronger password validation). don't use with :validatable!
-* :password_archivable - save used password in an old_passwords table for history checks (don't be able to use a formerly used password)
-* :session_limitable - ensures, that there is only one session usable per account at once.
+* :secure_validatable - better way to validate a model (email, stronger password validation). Don't use with :validatable!
+* :password_archivable - save used passwords in an old_passwords table for history checks (don't be able to use a formerly used password)
+* :session_limitable - ensures, that there is only one session usable per account at once
+* :expirable - expires a user account after x days of inactivity (default 90 days)
== Installation
-add to Gemfile
+Add to Gemfile
gem 'devise_security_extension'
after bundle install
@@ -49,6 +50,10 @@ for :secure_validatable you need to add
# captcha integration for unlock form
# config.captcha_for_unlock = true
+
+ # ==> Configuration for :expirable
+ # Time period for account expiry from last_activity_at
+ config.expire_after = 90.days
end
== Captcha-Support
@@ -58,7 +63,7 @@ for :secure_validatable you need to add
1. add to Gemfile "gem 'easy_captcha'"
2. install easy_captcha "rails g easy_captcha:install"
3. enable captcha - see "Configuration"
-4. add captcha source in views of devise for each controller you have activate
+4. add captcha source in the devise views for each controller you have activated
<p><%= captcha_tag %></p>
<p><%= text_field_tag :captcha %></p>
@@ -87,6 +92,24 @@ That's it!
t.session_limitable
end
+=== Expirable
+Devise 2.0 style migrations on new resource:
+
+ create_table :the_resources do |t|
+ ...
+ t.datetime :last_activity_at
+ t.datetime :expired_at
+ ...
+ end
+
+Add module to existing resource with
+
+ change_table :the_resources do |t|
+ t.datetime :last_activity_at
+ t.datetime :expired_at
+ end
+
+
== Requirements
* devise (https://github.com/plataformatec/devise)
@@ -95,14 +118,15 @@ That's it!
== Todo
-* disable inactive users after time
+* see the github issues (feature requests)
== History
* 0.1 expire passwords
* 0.2 strong password validation
* 0.3 password archivable with validation
* 0.4 captcha support for sign_up, sign_in, recover and unlock
* 0.5 session_limitable module
+* 0.6 expirable module
== Maintainers
View
@@ -10,3 +10,4 @@ de:
change_required: "Ihr Passwort ist abgelaufen. Bitte vergeben sie ein neues Passwort!"
failure:
session_limited: 'Ihre Anmeldedaten wurden in einem anderen Browser genutzt. Bitte melden Sie sich erneut an, um in diesem Browser fortzufahren.'
+ expired: 'Ihr Account ist aufgrund zu langer Inaktiviät abgelaufen. Bitte kontaktieren Sie den Administrator.'
View
@@ -10,3 +10,4 @@ en:
change_required: "Your password is expired. Please renew your password!"
failure:
session_limited: 'Your login credentials were used in another browser. Please sign in again to continue in this browser.'
+ expired: 'Your account has expired due to inactivity. Please contact the site administrator.'
@@ -43,15 +43,20 @@ module Devise # :nodoc:
# captcha integration for unlock form
mattr_accessor :captcha_for_unlock
@@captcha_for_unlock = false
-
+
+ # Time period for account expiry from last_activity_at
+ mattr_accessor :expire_after
+ @@expire_after = 90.days
+ mattr_accessor :delete_expired_after
+ @@delete_expired_after = 90.days
end
# an security extension for devise
module DeviseSecurityExtension
autoload :Schema, 'devise_security_extension/schema'
autoload :Patches, 'devise_security_extension/patches'
- module Controllers # :nodoc:
+ module Controllers
autoload :Helpers, 'devise_security_extension/controllers/helpers'
end
end
@@ -61,10 +66,10 @@ module Controllers # :nodoc:
Devise.add_module :secure_validatable, :model => 'devise_security_extension/models/secure_validatable'
Devise.add_module :password_archivable, :model => 'devise_security_extension/models/password_archivable'
Devise.add_module :session_limitable, :model => 'devise_security_extension/models/session_limitable'
+Devise.add_module :expirable, :model => 'devise_security_extension/models/expirable'
# requires
require 'devise_security_extension/routes'
require 'devise_security_extension/rails'
require 'devise_security_extension/orm/active_record'
require 'devise_security_extension/models/old_password'
-
@@ -0,0 +1,10 @@
+# Updates the last_activity_at fields from the record. Only when the user is active
+# for authentication and authenticated.
+# An expiry of the account is only checked on sign in OR on manually setting the
+# expired_at to the past (see Devise::Models::Expirable for this)
+Warden::Manager.after_set_user do |record, warden, options|
+ if record && record.respond_to?(:active_for_authentication?) && record.active_for_authentication? &&
+ warden.authenticated?(options[:scope]) && record.respond_to?(:update_last_activitiy!)
+ record.update_last_activitiy!
+ end
+end
@@ -0,0 +1,124 @@
+require 'devise_security_extension/hooks/expirable'
+
+module Devise
+ module Models
+ # Deactivate the account after a configurable amount of time. To be able to
+ # tell, it tracks activity about your account with the following columns:
+ #
+ # * last_activity_at - A timestamp updated when the user requests a page (only signed in)
+ #
+ # == Options
+ # +:expire_after+ - Time interval to expire accounts after
+ #
+ # == Additions
+ # Best used with two cron jobs. One for expiring accounts after inactivity,
+ # and another, that deletes accounts, which have expired for a given amount
+ # of time (for example 90 days).
+ #
+ module Expirable
+ extend ActiveSupport::Concern
+
+ module InstanceMethods
+ # Updates +last_activity_at+, called from a Warden::Manager.after_set_user hook.
+ def update_last_activitiy!
+ self.last_activity_at = Time.now.utc
+ save(:validate => false)
+ end
+
+ # Tells if the account has expired
+ #
+ # @return [bool]
+ def expired?
+ # expired_at set (manually, via cron, etc.)
+ return self.expired_at < Time.now.utc unless self.expired_at.nil?
+ # if it is not set, check the last activity against configured expire_after time range
+ return self.last_activity_at < self.class.expire_after.ago unless self.last_activity_at.nil?
+ # if last_activity_at is nil as well, the user has to be 'fresh' and is therefore not expired
+ false
+ end
+
+ # Expire an account. This is for cron jobs and manually expiring of accounts.
+ #
+ # @example
+ # User.expire!
+ # User.expire! 1.week.from_now
+ # @note +expired_at+ can be in the future as well
+ def expire!(at = Time.now.utc)
+ self.expired_at = at
+ save(:validate => false)
+ end
+
+ # Overwrites active_for_authentication? from Devise::Models::Activatable
+ # for verifying whether a user is active to sign in or not. If the account
+ # is expired, it should never be allowed.
+ #
+ # @return [bool]
+ def active_for_authentication?
+ super && !self.expired?
+ end
+
+ # The message sym, if {#active_for_authentication?} returns +false+. E.g. needed
+ # for i18n.
+ def inactive_message
+ !self.expired? ? super : :expired
+ end
+
+ end
+
+ module ClassMethods
+ ::Devise::Models.config(self, :expire_after, :delete_expired_after)
+
+ # Sample method for daily cron to mark expired entries.
+ #
+ # @example You can overide this in your +resource+ model
+ # def self.mark_expired
+ # puts 'overwritten mark_expired'
+ # end
+ def mark_expired
+ all.each do |u|
+ u.expire! if u.expired? && u.expired_at.nil?
+ end
+ return
+ end
+
+ # Scope method to collect all expired users since +time+ ago
+ def expired_for(time = delete_expired_after)
+ where('expired_at < ?', time.ago)
+ end
+
+ # Sample method for daily cron to delete all expired entries after a
+ # given amount of +time+.
+ #
+ # In your overwritten method you can "blank out" the object instead of
+ # deleting it.
+ #
+ # *Word of warning*: You have to handle the dependent method
+ # on the +resource+ relations (+:destroy+ or +:nullify+) and catch this
+ # behavior (see http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Deleting+from+associations).
+ #
+ # @example
+ # Resource.delete_all_expired_for 90.days
+ # @example You can overide this in your +resource+ model
+ # def self.delete_all_expired_for(time = 90.days)
+ # puts 'overwritten delete call'
+ # end
+ # @example Overwritten version to blank out the object.
+ # def self.delete_all_expired_for(time = 90.days)
+ # expired_for(time).each do |u|
+ # u.update_attributes first_name: nil, last_name: nil
+ # end
+ # end
+ def delete_all_expired_for(time)
+ expired_for(time).delete_all
+ end
+
+ # Version of {#delete_all_expired_for} without arguments (uses
+ # configured +delete_expired_after+ default value).
+ # @see #delete_all_expired_for
+ def delete_all_expired
+ delete_all_expired_for(delete_expired_after)
+ end
+ end
+ end
+ end
+end
@@ -26,8 +26,11 @@ def add_configs
" # captcha integration for sign in form\n" +
" # config.captcha_for_sign_in = true\n\n" +
" # captcha integration for unlock form\n" +
- " # config.captcha_for_unlock = true" +
- "\n", :before => /end[ |\n|]+\Z/
+ " # config.captcha_for_unlock = true\n\n" +
+ " # ==> Configuration for :expirable\n" +
+ " # Time period for account expiry from last_activity_at\n" +
+ " config.expire_after = 90.days\n" +
+ "", :before => /end[ |\n|]+\Z/
end
def copy_locale

0 comments on commit 8082b77

Please sign in to comment.