authengine 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +86 -0
- data/Rakefile +31 -0
- data/app/assets/images/message_block/back.gif +0 -0
- data/app/assets/images/message_block/back_m.gif +0 -0
- data/app/assets/images/message_block/confirmation.gif +0 -0
- data/app/assets/images/message_block/confirmation_m.gif +0 -0
- data/app/assets/images/message_block/error.gif +0 -0
- data/app/assets/images/message_block/error_m.gif +0 -0
- data/app/assets/images/message_block/info.gif +0 -0
- data/app/assets/images/message_block/info_m.gif +0 -0
- data/app/assets/images/message_block/notice.gif +0 -0
- data/app/assets/images/message_block/notice_m.gif +0 -0
- data/app/assets/images/message_block/warn.gif +0 -0
- data/app/assets/images/message_block/warn_m.gif +0 -0
- data/app/assets/stylesheets/authengine.css +3 -0
- data/app/assets/stylesheets/message_block.css +45 -0
- data/app/controllers/authengine/accounts_controller.rb +56 -0
- data/app/controllers/authengine/action_roles_controller.rb +22 -0
- data/app/controllers/authengine/actions_controller.rb +17 -0
- data/app/controllers/authengine/roles_controller.rb +35 -0
- data/app/controllers/authengine/sessions_controller.rb +75 -0
- data/app/controllers/authengine/user_roles_controller.rb +55 -0
- data/app/controllers/authengine/useractions_controller.rb +17 -0
- data/app/controllers/authengine/users_controller.rb +137 -0
- data/app/helpers/application_helper.rb +2 -0
- data/app/helpers/authengine/users_helper.rb +11 -0
- data/app/helpers/roles_helper.rb +2 -0
- data/app/mailers/authengine/user_mailer.rb +53 -0
- data/app/models/action.rb +54 -0
- data/app/models/action_role.rb +29 -0
- data/app/models/authenticated_system.rb +179 -0
- data/app/models/authorized_system.rb +41 -0
- data/app/models/controller.rb +124 -0
- data/app/models/role.rb +71 -0
- data/app/models/session.rb +3 -0
- data/app/models/session_role.rb +17 -0
- data/app/models/user.rb +191 -0
- data/app/models/user_observer.rb +14 -0
- data/app/models/user_role.rb +4 -0
- data/app/models/useraction.rb +56 -0
- data/app/views/authengine/accounts/edit.html.erb +19 -0
- data/app/views/authengine/actions/create.html.erb +2 -0
- data/app/views/authengine/actions/destroy.html.erb +2 -0
- data/app/views/authengine/actions/edit.html.erb +80 -0
- data/app/views/authengine/actions/index.html.haml +26 -0
- data/app/views/authengine/actions/new.html.erb +2 -0
- data/app/views/authengine/actions/show.html.erb +8 -0
- data/app/views/authengine/actions/update.html.erb +11 -0
- data/app/views/authengine/admin/_show.html.haml +5 -0
- data/app/views/authengine/layouts/authengine.html.haml +9 -0
- data/app/views/authengine/roles/index.html.haml +12 -0
- data/app/views/authengine/roles/new.html.haml +15 -0
- data/app/views/authengine/roles/show.html.erb +8 -0
- data/app/views/authengine/sessions/new.html.haml +18 -0
- data/app/views/authengine/user_mailer/activation.html.erb +5 -0
- data/app/views/authengine/user_mailer/forgot_password.html.erb +3 -0
- data/app/views/authengine/user_mailer/message_to_admin.html.erb +2 -0
- data/app/views/authengine/user_mailer/reset_password.html.erb +1 -0
- data/app/views/authengine/user_mailer/signup_notification.html.erb +5 -0
- data/app/views/authengine/user_roles/edit.html.haml +10 -0
- data/app/views/authengine/user_roles/index.html.haml +14 -0
- data/app/views/authengine/user_roles/new.html.haml +8 -0
- data/app/views/authengine/useractions/_useraction.html.erb +6 -0
- data/app/views/authengine/useractions/index.html.erb +13 -0
- data/app/views/authengine/useractions/show.html.haml +14 -0
- data/app/views/authengine/useractions/update.html.erb +2 -0
- data/app/views/authengine/users/_no_privacy_policy.html.haml +1 -0
- data/app/views/authengine/users/_privacy_policy_example.html.haml +36 -0
- data/app/views/authengine/users/_user.html.haml +19 -0
- data/app/views/authengine/users/edit.html.haml +24 -0
- data/app/views/authengine/users/index.html.haml +10 -0
- data/app/views/authengine/users/new.html.haml +31 -0
- data/app/views/authengine/users/show.html.haml +19 -0
- data/app/views/authengine/users/signup.html.haml +52 -0
- data/authengine.gemspec +44 -0
- data/config/application.rb +1 -0
- data/config/routes.rb +43 -0
- data/db/migrate/20110320171029_create_authengine_tables.rb +90 -0
- data/db/migrate/20110924165900_add_parent_id_to_roles_table.rb +5 -0
- data/db/migrate/20110925202800_add_type_field_to_user_roles_table.rb +5 -0
- data/db/migrate/20111003074700_add_indexes_to_several_tables.rb +7 -0
- data/db/seeds.rb +7 -0
- data/lib/application_helper.rb +19 -0
- data/lib/authengine.rb +5 -0
- data/lib/authengine/engine.rb +44 -0
- data/lib/authengine/testing_support/factories/user_factory.rb +13 -0
- data/lib/authengine/version.rb +3 -0
- data/lib/rails/generators/authengine/authengine_generator.rb +160 -0
- data/lib/rails/generators/authengine/templates/initializer.rb +3 -0
- data/lib/rails/generators/authengine/templates/migration.rb +16 -0
- data/lib/rails/generators/authengine/templates/pre_populate_database.rb +20 -0
- data/lib/rails/generators/authengine/templates/schema.rb +69 -0
- data/lib/tasks/bootstrap.rake +29 -0
- data/spec/authengine_spec.rb +7 -0
- data/spec/dummy/.rspec +1 -0
- data/spec/dummy/Gemfile +3 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/assets/javascripts/jasmine_examples/Player.js +22 -0
- data/spec/dummy/app/assets/javascripts/jasmine_examples/Song.js +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +50 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/application.rb +1 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +87 -0
- data/spec/dummy/lib/constants.rb +5 -0
- data/spec/dummy/log/development.log +117 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +191 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/javascripts/helpers/.gitkeep +0 -0
- data/spec/dummy/spec/javascripts/helpers/SpecHelper.js +9 -0
- data/spec/dummy/spec/javascripts/jasmine_examples/PlayerSpec.js +58 -0
- data/spec/dummy/spec/javascripts/support/jasmine.yml +76 -0
- data/spec/generators/authengine_generator_spec.rb +11 -0
- data/spec/integration/navigation_spec.rb +9 -0
- data/spec/javascripts/spec.css +3 -0
- data/spec/javascripts/spec.js.coffee +2 -0
- data/spec/models/action_role_spec.rb +59 -0
- data/spec/models/authenticated_system_spec.rb +109 -0
- data/spec/models/role_spec.rb +38 -0
- data/spec/models/user_factory_spec.rb +7 -0
- data/spec/models/user_spec.rb +16 -0
- data/spec/requests/sessions_spec.rb +11 -0
- data/spec/spec_helper.rb +57 -0
- metadata +405 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# AuthorizedSystem is 'include'd in ActionController by the authengine engine
|
2
|
+
# see lib/authengine/engine.rb
|
3
|
+
module AuthorizedSystem
|
4
|
+
# established for the session when the user logs in
|
5
|
+
# may be modified later if user's roles are modified
|
6
|
+
# or if session is downgraded
|
7
|
+
def current_role_ids=(ids)
|
8
|
+
session[:role].current_role_ids = ids
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_role_ids
|
12
|
+
session[:role].current_role_ids
|
13
|
+
end
|
14
|
+
|
15
|
+
def action_permitted?(controller, action)
|
16
|
+
ActionRole.permits_access_for(controller, action, current_role_ids)
|
17
|
+
end
|
18
|
+
|
19
|
+
def permitted?(controller, action)
|
20
|
+
action_permitted?(controller, action) && logged_in?
|
21
|
+
end
|
22
|
+
|
23
|
+
# for each and every action, we check the configured permission
|
24
|
+
# for the role(s) assigned to the logged-in user
|
25
|
+
# The controller and action can be passed as parameters, to check whether or not to display a link/button
|
26
|
+
# or else the current request controller/action are used to check whether or not to display a page
|
27
|
+
def check_permissions(controller = request.parameters["controller"], action = request.parameters["action"])
|
28
|
+
permission = false
|
29
|
+
if !logged_in?
|
30
|
+
logger.info "access denied: not logged in"
|
31
|
+
access_denied
|
32
|
+
elsif permitted?(controller, action)
|
33
|
+
permission = true
|
34
|
+
else
|
35
|
+
logger.info "permission denied, #{controller}, #{action}"
|
36
|
+
permission_denied
|
37
|
+
end
|
38
|
+
permission
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# the table is managed so as to make it mirror the file system
|
2
|
+
# the only reason this is necessary is to be able to store in the
|
3
|
+
# database a last_modified attribute for a file, in order to
|
4
|
+
# know whether the actions table is up to date for this controller
|
5
|
+
# or should be regenerated from the file contents
|
6
|
+
# The class represents both filesystem objects and database objects
|
7
|
+
# hmmm... is that really the best way to design it, shouldn't it be two classes?
|
8
|
+
class Controller < ActiveRecord::Base
|
9
|
+
has_many :actions, :dependent=>:delete_all
|
10
|
+
CONTROLLERS_DIR = "#{Rails.root.to_s}/app/controllers"
|
11
|
+
|
12
|
+
cattr_accessor :controllers
|
13
|
+
|
14
|
+
# returns an array of strings, each one is a controller name
|
15
|
+
def self.all_controller_names
|
16
|
+
@@controllers ||= all_files.map { |file| file.camelize.gsub(".rb","") }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.all_files
|
20
|
+
application_files + engine_files
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.engine_files
|
24
|
+
engine_controller_paths.inject([]) do |array, path|
|
25
|
+
# entries may be controller files, but if there are namespaced controllers
|
26
|
+
# then entries are directories
|
27
|
+
directories, files = Dir.new(path).entries.reject{|c|c.match(/^\./)}.partition{|c| File.directory?(File.new(File.join(path,c)))}
|
28
|
+
array += files
|
29
|
+
directories.each do |directory|
|
30
|
+
files = Dir.new(File.join(path,directory)).reject{|c|c.match(/^\./)}.entries.map{|file| File.join(directory,file)}
|
31
|
+
array += files
|
32
|
+
end
|
33
|
+
array
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.engine_controller_paths
|
38
|
+
Rails::Engine.subclasses.collect { |engine|
|
39
|
+
engine.config.eager_load_paths.detect{|p| p=~/controller/}
|
40
|
+
}.flatten.compact
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.application_files
|
44
|
+
Dir.new(CONTROLLERS_DIR).entries.reject{|c| c.match(/^\./)}.reject{|c| c == 'application_controller.rb'}
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.all_modified_files
|
48
|
+
all_controller_names.select(&:modified?)
|
49
|
+
end
|
50
|
+
|
51
|
+
# a file is declared to be modified if it's either older or newer than the last_modified date
|
52
|
+
# this triggers re-parsing the actions in the file whether it's older or newer
|
53
|
+
# and so responds both to the file being edited and also the database being restored
|
54
|
+
# from an older version.
|
55
|
+
# Only by converting to string could I persuade two apparently equal DateTime objects to match!
|
56
|
+
def modified?
|
57
|
+
file_modification_time.to_s != last_modified.getutc.to_datetime.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def file_modification_time
|
61
|
+
file.mtime.getutc.to_datetime
|
62
|
+
end
|
63
|
+
|
64
|
+
def file
|
65
|
+
all_paths = Controller.engine_controller_paths << CONTROLLERS_DIR
|
66
|
+
controller_path = all_paths.detect do |path|
|
67
|
+
full_path = File.join(path, "#{controller_name}_controller.rb")
|
68
|
+
File.exists?(full_path)
|
69
|
+
end
|
70
|
+
File.new(File.join(controller_path, "#{controller_name}_controller.rb"))
|
71
|
+
end
|
72
|
+
|
73
|
+
# updates the controllers table so that it contains a record for each controller file
|
74
|
+
# in the /app/controllers directory
|
75
|
+
def self.update_table
|
76
|
+
cc = Controller.all.inject({}){ |hash,controller| hash[controller.controller_name]=controller; hash } # from the database
|
77
|
+
all_controller_names.each do |f| # f is of the form "ItemsController"
|
78
|
+
cont = f.tableize.gsub!("_controllers","") # cont is of the form "items"
|
79
|
+
admin_name = Role.find_by_name("administrator") ? "administrator" : "admin"
|
80
|
+
if !cc.keys.include?(cont) # it's not in the db
|
81
|
+
new_controller = new(:controller_name=>f.underscore.gsub!("_controller", ""), :last_modified=>Date.today) # add controller to controllers table as there's not a record corresponding with the file
|
82
|
+
new_controller.actions << new_controller.action_list.map { |a| Action.new(:action_name=>a[1]) }# when a new controller is added, its actions must be added to the actions.file
|
83
|
+
new_controller.save
|
84
|
+
elsif cc[cont].modified? # file was modified since db was updated, so read the actions from the file, and add/delete as necessary
|
85
|
+
action_names = cc[cont].actions_from_file # action_names is of the form ["index", "new", "edit", "create", "update"]
|
86
|
+
Action.update_table_for(cc[cont],action_names)
|
87
|
+
# finally modify the last_modified date of the controller record to match the actual file
|
88
|
+
cc[cont].update_attribute(:last_modified,cc[cont].file_modification_time)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
ActionRole.assign_developer_access
|
92
|
+
# delete any records in the controllers table for which there's no xx_controller.rb file... it must've been deleted
|
93
|
+
cc.each { |name,controller| controller.destroy if !all_controller_names.map{|cn| cn.tableize.gsub!("_controllers","")}.include?(name) }
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.all_actions_from_files
|
98
|
+
all_controller_names.inject([]){ |ar,c| ar+=c.action_list; ar }
|
99
|
+
end
|
100
|
+
|
101
|
+
def model_name
|
102
|
+
controller_name.gsub("Controller","").underscore
|
103
|
+
end
|
104
|
+
|
105
|
+
# the actions returned are those that were in the file when it was loaded
|
106
|
+
# this is reasonable only because the actions are changed by the developer
|
107
|
+
# and never get changed "on the fly". However this fact should be considered when testing!
|
108
|
+
def actions_from_file
|
109
|
+
# there's a workaround here for some strangeness that appeared in Rails 3
|
110
|
+
# where public_instance_methods returns some spurious methods with the format
|
111
|
+
# _one_time_conditions_valid_nnn?
|
112
|
+
controller.public_instance_methods(false).reject{|m| m.match(/one_time_conditions_valid/)}.map(&:to_s)
|
113
|
+
end
|
114
|
+
|
115
|
+
def controller
|
116
|
+
(controller_name+"_controller").classify.constantize
|
117
|
+
end
|
118
|
+
|
119
|
+
# returns an array of arrays, each contains the controller controller_name and action name
|
120
|
+
def action_list
|
121
|
+
actions_from_file.collect { |m| [model_name,m] }
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
data/app/models/role.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Roles are hierarchically organized, so that the current role
|
2
|
+
# for a session can be downgraded to a lower role.
|
3
|
+
# The hierarchy gives meaning to "lower role".
|
4
|
+
class Role < ActiveRecord::Base
|
5
|
+
has_many :user_roles
|
6
|
+
has_many :users, :through=>:user_roles
|
7
|
+
|
8
|
+
has_many :action_roles
|
9
|
+
has_many :actions, :through => :action_roles
|
10
|
+
|
11
|
+
belongs_to :parent, :class_name => 'Role'
|
12
|
+
|
13
|
+
|
14
|
+
validates_presence_of :name
|
15
|
+
validates_uniqueness_of :name
|
16
|
+
|
17
|
+
before_destroy do
|
18
|
+
if users.empty?
|
19
|
+
action_roles.clear
|
20
|
+
else
|
21
|
+
false # don't delete a role if there are users assigned
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# takes either an array of roles or a single role object
|
26
|
+
def self.equal_or_lower_than(role)
|
27
|
+
roles = role.is_a?(Array) ? role : [role]
|
28
|
+
(lower_than(role) + roles).uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
# takes either an array of roles or a single role object
|
32
|
+
def self.lower_than(role)
|
33
|
+
roles = role.is_a?(Array) ? role : [role]
|
34
|
+
collection = roles.inject([]) do |ar, r|
|
35
|
+
ar + with_ancestor(r)
|
36
|
+
end
|
37
|
+
(collection).uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
# returns an array of roles that have the passed-in role as an
|
41
|
+
# ancestor
|
42
|
+
def self.with_ancestor(role)
|
43
|
+
all.select{|r| r.has_ancestor?(role)}
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_ancestor?(role)
|
47
|
+
ancestors.include?(role)
|
48
|
+
end
|
49
|
+
|
50
|
+
def ancestors
|
51
|
+
node, nodes = self, []
|
52
|
+
nodes << node = node.parent while node.parent
|
53
|
+
nodes
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_developer?
|
57
|
+
name == 'developer'
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.developer
|
61
|
+
where('name = "developer"').first
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.developer_id
|
65
|
+
developer && developer.id
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
name
|
70
|
+
end
|
71
|
+
end
|
data/app/models/user.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
class User < ActiveRecord::Base
|
3
|
+
# Virtual attribute for the unencrypted password
|
4
|
+
attr_accessor :password
|
5
|
+
|
6
|
+
# when user account is first created, only firstname, lastname and email are required
|
7
|
+
validates_presence_of :firstName, :lastName, :email
|
8
|
+
validates_length_of :email, :within => 6..100
|
9
|
+
validates_uniqueness_of :email, :case_sensitive=>false, :if=>Proc.new { |user| all_active=true; User.find_all_by_email(user.email).each { |u| all_active=false if u.status=='inactive' }; all_active }, :message=>'There is already an active user with that email address.'
|
10
|
+
#validates_format_of :email, :with => EMAIL_REGEX
|
11
|
+
|
12
|
+
# the next action on the user's record is account activation
|
13
|
+
# at this time, login and password must be present and valid
|
14
|
+
validates_presence_of :login, :on => :update
|
15
|
+
validates_presence_of :password, :if => :password_required?, :on=>:update
|
16
|
+
validates_presence_of :password_confirmation, :if => :password_required?, :on=>:update
|
17
|
+
validates_length_of :password, :within => 4..40, :if => :password_required?, :on=>:update
|
18
|
+
validates_confirmation_of :password, :if => :password_required?, :on=>:update
|
19
|
+
validates_length_of :login, :within => 3..40, :on => :update
|
20
|
+
validates_uniqueness_of :login, :case_sensitive => false, :on => :update
|
21
|
+
|
22
|
+
has_many :user_roles, :dependent=>:delete_all
|
23
|
+
accepts_nested_attributes_for :user_roles
|
24
|
+
has_many :roles, :through=>:user_roles
|
25
|
+
|
26
|
+
has_many :useractions, :dependent=>:delete_all
|
27
|
+
has_many :actions, :through=>:useractions
|
28
|
+
|
29
|
+
before_save :encrypt_password
|
30
|
+
before_create :make_activation_code
|
31
|
+
|
32
|
+
|
33
|
+
# prevents a user from submitting a crafted form that bypasses activation
|
34
|
+
# anything else you want your user to change should be added here.
|
35
|
+
# If the ability to add users was extended, may wish to change the attr_accessible to make sure a user cannot assign
|
36
|
+
# themselves to a higher-privileged role
|
37
|
+
# TODO in this application, users are trusted, but see how this should be implemented if users are not trusted
|
38
|
+
attr_accessible :login, :email, :password, :password_confirmation, :firstName, :lastName, :user_roles_attributes
|
39
|
+
|
40
|
+
class PermissionsNotConfigured < StandardError
|
41
|
+
attr_reader :message
|
42
|
+
def initialize(controller,action)
|
43
|
+
@message = "Permissions not yet configured for #{controller}/#{action}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
class ActivationCodeNotFound < StandardError; end
|
47
|
+
class ArgumentError < StandardError; end
|
48
|
+
class AlreadyActivated < StandardError
|
49
|
+
attr_reader :user, :message;
|
50
|
+
def initialize(user, message=nil)
|
51
|
+
@message, @user = message, user
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def first_last_name
|
56
|
+
firstName+' '+lastName
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Finds the user with the corresponding activation code, activates their account and returns the user.
|
61
|
+
#
|
62
|
+
# Raises:
|
63
|
+
# +User::ActivationCodeNotFound+ if there is no user with the corresponding activation code
|
64
|
+
# +User::AlreadyActivated+ if the user with the corresponding activation code has already activated their account
|
65
|
+
def self.find_and_activate!(activation_code)
|
66
|
+
raise ArgumentError if activation_code.nil?
|
67
|
+
user = find_by_activation_code(activation_code)
|
68
|
+
raise ActivationCodeNotFound if !user
|
69
|
+
raise AlreadyActivated.new(user) if user.active?
|
70
|
+
user.send(:activate!)
|
71
|
+
user
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.find_with_activation_code(activation_code)
|
75
|
+
raise ArgumentError if activation_code.nil?
|
76
|
+
user = find_by_activation_code(activation_code)
|
77
|
+
raise ActivationCodeNotFound if !user
|
78
|
+
raise AlreadyActivated.new(user) if user.active?
|
79
|
+
user
|
80
|
+
end
|
81
|
+
|
82
|
+
def active?
|
83
|
+
# the presence of an activation date means they have activated
|
84
|
+
!activated_at.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns true if the user has just been activated.
|
88
|
+
def pending?
|
89
|
+
@activated
|
90
|
+
end
|
91
|
+
|
92
|
+
# Authenticates a user by their login name and unencrypted password. Returns the user or nil.
|
93
|
+
def self.authenticate(login, password)
|
94
|
+
u = find :first, :conditions => ['login = ?', login]
|
95
|
+
u && u.authenticated?(password) ? u : nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Encrypts some data with the salt.
|
99
|
+
def self.encrypt(password, salt)
|
100
|
+
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
|
101
|
+
end
|
102
|
+
|
103
|
+
# Encrypts the password with the user salt
|
104
|
+
def encrypt(password)
|
105
|
+
self.class.encrypt(password, salt)
|
106
|
+
end
|
107
|
+
|
108
|
+
def authenticated?(password)
|
109
|
+
crypted_password == encrypt(password)
|
110
|
+
end
|
111
|
+
|
112
|
+
def remember_token?
|
113
|
+
remember_token_expires_at && Time.now.utc < remember_token_expires_at
|
114
|
+
end
|
115
|
+
|
116
|
+
def forgot_password
|
117
|
+
@forgotten_password = true
|
118
|
+
self.make_password_reset_code
|
119
|
+
end
|
120
|
+
|
121
|
+
def reset_password
|
122
|
+
# First update the password_reset_code before setting the
|
123
|
+
# reset_password flag to avoid duplicate email notifications.
|
124
|
+
update_attribute(:password_reset_code, nil)
|
125
|
+
@reset_password = true
|
126
|
+
end
|
127
|
+
|
128
|
+
#used in user_observer
|
129
|
+
def recently_forgot_password?
|
130
|
+
@forgotten_password
|
131
|
+
end
|
132
|
+
|
133
|
+
def recently_reset_password?
|
134
|
+
@reset_password
|
135
|
+
end
|
136
|
+
|
137
|
+
def forget_me
|
138
|
+
self.remember_token_expires_at = nil
|
139
|
+
self.remember_token = nil
|
140
|
+
save(:validate => false)
|
141
|
+
end
|
142
|
+
|
143
|
+
def has_role?(name)
|
144
|
+
self.roles.find_by_name(name) ? true : false
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.create_by_sql(attributes)
|
148
|
+
user = User.new(attributes)
|
149
|
+
user.send('encrypt_password')
|
150
|
+
user.send('make_activation_code')
|
151
|
+
now = DateTime.now.to_formatted_s(:db)
|
152
|
+
query = <<-SQL
|
153
|
+
INSERT INTO users
|
154
|
+
(activated_at, activation_code, created_at, crypted_password, email, enabled, firstName, lastName, login, password_reset_code, remember_token, remember_token_expires_at, salt, status, type, updated_at)
|
155
|
+
VALUES
|
156
|
+
( '#{now}','#{user.activation_code}','#{now}', '#{user.crypted_password}', NULL, 1, '#{user.firstName}', '#{user.lastName}', '#{user.login}', NULL, NULL, NULL, '#{user.salt}', NULL, NULL,'#{now}')
|
157
|
+
SQL
|
158
|
+
#can't use ActiveRecord#create here as it would trigger a notification email
|
159
|
+
ActiveRecord::Base.connection.insert_sql(query)
|
160
|
+
end
|
161
|
+
|
162
|
+
protected
|
163
|
+
|
164
|
+
# before filter
|
165
|
+
def encrypt_password
|
166
|
+
return if password.blank?
|
167
|
+
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if salt.blank?
|
168
|
+
self.crypted_password = encrypt(password)
|
169
|
+
end
|
170
|
+
|
171
|
+
def password_required?
|
172
|
+
crypted_password.blank? || !password.blank?
|
173
|
+
end
|
174
|
+
|
175
|
+
def make_activation_code
|
176
|
+
self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
|
177
|
+
end
|
178
|
+
|
179
|
+
def make_password_reset_code
|
180
|
+
self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def activate!
|
186
|
+
@activated = true
|
187
|
+
self.update_attribute(:activated_at, Time.now.utc)
|
188
|
+
@activated = false
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|