authengine 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|