ioquatix-account_engine 0.1.0
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/README +0 -0
- data/lib/account_engine/configuration.rb +101 -0
- data/lib/account_engine/controller.rb +246 -0
- data/lib/account_engine/helper.rb +104 -0
- data/lib/account_engine/password.rb +432 -0
- data/lib/account_engine/support.rb +12 -0
- data/lib/account_engine/user_account/class_methods.rb +63 -0
- data/lib/account_engine/user_account.rb +184 -0
- data/lib/account_engine.rb +63 -0
- data/rails/app/controllers/account_controller.rb +162 -0
- data/rails/app/controllers/permissions_controller.rb +90 -0
- data/rails/app/controllers/roles_controller.rb +133 -0
- data/rails/app/controllers/users_controller.rb +144 -0
- data/rails/app/helpers/account_helper.rb +3 -0
- data/rails/app/helpers/permissions_helper.rb +3 -0
- data/rails/app/helpers/roles_helper.rb +3 -0
- data/rails/app/helpers/users_helper.rb +3 -0
- data/rails/app/models/permission.rb +129 -0
- data/rails/app/models/role.rb +60 -0
- data/rails/app/models/user.rb +5 -0
- data/rails/app/models/user_notify.rb +75 -0
- data/rails/app/views/account/_form.rhtml +8 -0
- data/rails/app/views/account/change_password.rhtml +17 -0
- data/rails/app/views/account/edit.rhtml +5 -0
- data/rails/app/views/account/forgot_password.rhtml +12 -0
- data/rails/app/views/account/home.rhtml +3 -0
- data/rails/app/views/account/login.rhtml +27 -0
- data/rails/app/views/account/logout.rhtml +8 -0
- data/rails/app/views/account/signup.rhtml +28 -0
- data/rails/app/views/permissions/_form.rhtml +14 -0
- data/rails/app/views/permissions/_list.rhtml +38 -0
- data/rails/app/views/permissions/edit.rhtml +5 -0
- data/rails/app/views/permissions/index.rhtml +3 -0
- data/rails/app/views/permissions/new.rhtml +5 -0
- data/rails/app/views/roles/_form.rhtml +8 -0
- data/rails/app/views/roles/_permissions.rhtml +25 -0
- data/rails/app/views/roles/edit.rhtml +5 -0
- data/rails/app/views/roles/index.rhtml +34 -0
- data/rails/app/views/roles/new.rhtml +5 -0
- data/rails/app/views/roles/show.rhtml +20 -0
- data/rails/app/views/user_notify/change_password.rhtml +10 -0
- data/rails/app/views/user_notify/delete.rhtml +5 -0
- data/rails/app/views/user_notify/forgot_password.rhtml +11 -0
- data/rails/app/views/user_notify/pending_delete.rhtml +9 -0
- data/rails/app/views/user_notify/signup.rhtml +12 -0
- data/rails/app/views/users/_form.rhtml +12 -0
- data/rails/app/views/users/edit.rhtml +5 -0
- data/rails/app/views/users/index.rhtml +38 -0
- data/rails/app/views/users/new.rhtml +5 -0
- data/rails/app/views/users/roles.rhtml +42 -0
- data/rails/app/views/users/show.rhtml +36 -0
- data/rails/assets/images/default/omnipotent.png +0 -0
- data/rails/assets/images/default/system.png +0 -0
- data/rails/assets/images/permissions/create.png +0 -0
- data/rails/assets/images/permissions/sync.png +0 -0
- data/rails/assets/images/roles/add_permission.png +0 -0
- data/rails/assets/images/roles/create.png +0 -0
- data/rails/assets/images/roles/edit.png +0 -0
- data/rails/assets/images/roles/remove_permission.png +0 -0
- data/rails/assets/images/roles/user.png +0 -0
- data/rails/assets/images/table_background.png +0 -0
- data/rails/assets/images/users/create.png +0 -0
- data/rails/assets/images/users/destroy.png +0 -0
- data/rails/assets/images/users/edit.png +0 -0
- data/rails/assets/images/users/show.png +0 -0
- data/rails/assets/javascripts/account_engine.js +166 -0
- data/rails/assets/stylesheets/account_engine.css +7 -0
- data/rails/assets/stylesheets/check_password.css +10 -0
- data/rails/assets/stylesheets/simple.css +168 -0
- data/rails/db/migrate/001_initial_schema.rb +49 -0
- data/rails/init.rb +21 -0
- data/rails/routes.rb +5 -0
- data/rails/tasks/account_engine.rake +123 -0
- metadata +165 -0
data/README
ADDED
File without changes
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Configuration
|
2
|
+
module AccountEngine
|
3
|
+
# These are the default constants, used if nothing else is specified
|
4
|
+
mattr_accessor :email_charset, :site_email, :admin_email, :app_url, :app_name
|
5
|
+
mattr_accessor :generate_password, :validate_email, :salt, :security_token_life_hours
|
6
|
+
mattr_accessor :confirm_account, :use_email_notification
|
7
|
+
mattr_reader :registration
|
8
|
+
|
9
|
+
def self.registration=(r)
|
10
|
+
if [:open, :closed].include? r
|
11
|
+
@@registration = r
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
mattr_accessor :guest_role_name, :user_role_name
|
16
|
+
mattr_accessor :admin_role_name, :admin_login, :admin_password, :admin_email
|
17
|
+
|
18
|
+
mattr_accessor :login_page, :logout_page, :stealth
|
19
|
+
|
20
|
+
mattr_accessor :users_table, :roles_table, :permissions_table
|
21
|
+
|
22
|
+
# The names of the new Role and Permission tables
|
23
|
+
if ActiveRecord::Base.pluralize_table_names
|
24
|
+
@@users_table = "users"
|
25
|
+
@@roles_table = "roles"
|
26
|
+
@@permissions_table = "permissions"
|
27
|
+
else
|
28
|
+
@@users_table = "user"
|
29
|
+
@@roles_table = "role"
|
30
|
+
@@permissions_table = "permission"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Join tables for users <-> roles, and roles <-> permissions
|
34
|
+
def self.users_roles_table
|
35
|
+
"#{AccountEngine.users_table}_#{AccountEngine.roles_table}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.permissions_roles_table
|
39
|
+
"#{AccountEngine.permissions_table}_#{AccountEngine.roles_table}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.allow_registration?
|
43
|
+
return (@@registration == :open ? true : false)
|
44
|
+
end
|
45
|
+
|
46
|
+
#Generate random passwords
|
47
|
+
@@generate_password = true
|
48
|
+
|
49
|
+
# Source address for user emails
|
50
|
+
@@site_email = 'root@localhost'
|
51
|
+
|
52
|
+
# Destination email for system errors
|
53
|
+
@@admin_email = 'root@localhost'
|
54
|
+
|
55
|
+
# Sent in emails to users
|
56
|
+
@@app_url = 'http://localhost:3000/'
|
57
|
+
|
58
|
+
# Sent in emails to users
|
59
|
+
@@app_name = 'My Application'
|
60
|
+
|
61
|
+
# Whether the model will enforce email validation
|
62
|
+
@@validate_email = true
|
63
|
+
|
64
|
+
# Email charset
|
65
|
+
@@email_charset = 'utf-8'
|
66
|
+
|
67
|
+
# Security token lifetime in hours
|
68
|
+
@@security_token_life_hours = 48
|
69
|
+
|
70
|
+
# Registrations are open to the public
|
71
|
+
@@registration = :closed
|
72
|
+
|
73
|
+
# controls whether or not email is used
|
74
|
+
@@use_email_notification = true
|
75
|
+
|
76
|
+
# Controls whether accounts must be confirmed after signing up
|
77
|
+
# ONLY if this and use_email_notification are both true
|
78
|
+
@@confirm_account = false
|
79
|
+
|
80
|
+
# The names of the Guest and User roles
|
81
|
+
# The Guest role is automatically assigned to any visitor who is not logged in
|
82
|
+
@@guest_role_name = "Guest"
|
83
|
+
# The User role is given to every user
|
84
|
+
@@user_role_name = "User"
|
85
|
+
|
86
|
+
# The details for the Admin user and role
|
87
|
+
@@admin_role_name = "Administrator"
|
88
|
+
@@admin_login = "admin"
|
89
|
+
@@admin_password = "test123"
|
90
|
+
@@admin_email = "root@localhost"
|
91
|
+
|
92
|
+
# The controller/action
|
93
|
+
@@login_page = {:controller => 'account', :action => 'login'}
|
94
|
+
|
95
|
+
@@logout_page = {:controller => 'account', :action => 'logout'}
|
96
|
+
|
97
|
+
# If this is set to true, authorization failure messages won't volunteer
|
98
|
+
# any extra information, and missing actions will not be flagged as such.
|
99
|
+
@@stealth = false
|
100
|
+
end
|
101
|
+
|
@@ -0,0 +1,246 @@
|
|
1
|
+
|
2
|
+
require 'account_engine/support'
|
3
|
+
|
4
|
+
module AccountEngine
|
5
|
+
module Controller
|
6
|
+
# methods to be added to the ApplicationController
|
7
|
+
|
8
|
+
include AccountEngine::Support
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
# Returns an array containing all subclasses of ApplicationController
|
13
|
+
def all_controllers
|
14
|
+
#ObjectSpace.subclasses_of(ApplicationController)
|
15
|
+
subclasses_of(ApplicationController)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns an array of all action names for this controller
|
20
|
+
# (Actually returns the result of ApplicationController#action_methods, which is private)
|
21
|
+
def action_method_names
|
22
|
+
action_methods
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included(base)
|
26
|
+
base.extend(ClassMethods)
|
27
|
+
|
28
|
+
base.class_eval do
|
29
|
+
# We don't want these actions to be exposed to the Permission
|
30
|
+
# system synchronisation, so we hide them for all controllers.
|
31
|
+
hide_action :require_without_load_path_reloading, :process_test
|
32
|
+
hide_action :action_method_names, :wsdl, :deepcopy
|
33
|
+
hide_action :readable?, :writable?, :r?, :w?, :authorize_action
|
34
|
+
hide_action :store_location, :redirect_back_or_default
|
35
|
+
|
36
|
+
# methods from the AccountEngine module itself
|
37
|
+
hide_action :link_if_authorized, :authorized?, :user_name_if_logged_in
|
38
|
+
hide_action :user?, :current_user, :logged_in?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
# Use this method to log a user in to the system
|
44
|
+
def authenticate_user
|
45
|
+
# If we are already logged in, return true
|
46
|
+
return true if current_user
|
47
|
+
|
48
|
+
login = params[:login]
|
49
|
+
password = params[:password]
|
50
|
+
id = params[:user_id]
|
51
|
+
key = params[:key]
|
52
|
+
user = nil
|
53
|
+
|
54
|
+
if login and password
|
55
|
+
user = User.authenticate(login, password)
|
56
|
+
end
|
57
|
+
|
58
|
+
if id and key and user.nil?
|
59
|
+
user = User.authenticate_by_token(id, key)
|
60
|
+
end
|
61
|
+
|
62
|
+
if user
|
63
|
+
user.update_attribute(:logged_in_at, Time.now)
|
64
|
+
session[:user] = user
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
|
71
|
+
# overwrite this if you want to restrict access to only a few actions
|
72
|
+
# or if you want to check if the user has the correct rights
|
73
|
+
# example:
|
74
|
+
#
|
75
|
+
# # only allow nonbobs
|
76
|
+
# def authorized?(user)
|
77
|
+
# user.login != "bob"
|
78
|
+
# end
|
79
|
+
def authorize?(user)
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
# overwrite this method if you only want to protect certain actions of the controller
|
84
|
+
# example:
|
85
|
+
#
|
86
|
+
# # don't protect the login and the about method
|
87
|
+
# def protect?(action)
|
88
|
+
# if ['action', 'about'].include?(action)
|
89
|
+
# return false
|
90
|
+
# else
|
91
|
+
# return true
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
def protect?(action)
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
# login_required filter. add
|
99
|
+
#
|
100
|
+
# before_filter :login_required
|
101
|
+
#
|
102
|
+
# if the controller should be under any rights management.
|
103
|
+
# for finer access control you can overwrite
|
104
|
+
#
|
105
|
+
# def authorize?(user)
|
106
|
+
#
|
107
|
+
def login_required
|
108
|
+
if not protect?(action_name)
|
109
|
+
return true
|
110
|
+
end
|
111
|
+
|
112
|
+
if user? and authorize?(current_user)
|
113
|
+
return true
|
114
|
+
end
|
115
|
+
|
116
|
+
if authorize_action
|
117
|
+
return true
|
118
|
+
end
|
119
|
+
|
120
|
+
if user?
|
121
|
+
# If we are logged in and were denied, we will need to actually back out of the operation
|
122
|
+
redirect_to request.env['HTTP_REFERER'] || session['prev_uri'] || "/"
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
|
126
|
+
# store current location so that we can
|
127
|
+
# come back after the user logged in
|
128
|
+
store_location
|
129
|
+
|
130
|
+
# call overwriteable reaction to unauthorized access
|
131
|
+
access_denied
|
132
|
+
end
|
133
|
+
|
134
|
+
# overwrite if you want to have special behavior in case the user is not authorized
|
135
|
+
# to access the current operation.
|
136
|
+
# the default action is to redirect to the login screen
|
137
|
+
# example use :
|
138
|
+
# a popup window might just close itself for instance
|
139
|
+
def access_denied
|
140
|
+
logger.info "Access denied! Action: #{action_name} User: #{current_user}"
|
141
|
+
redirect_to AccountEngine.login_page
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
145
|
+
# store current uri in the session.
|
146
|
+
# we can return to this location by calling return_location
|
147
|
+
def store_location
|
148
|
+
session['return-to'] = request.request_uri
|
149
|
+
end
|
150
|
+
|
151
|
+
# move to the last store_location call or to the passed default one
|
152
|
+
def redirect_to_stored_or_default(default=nil)
|
153
|
+
if session['return-to'].nil?
|
154
|
+
redirect_to default
|
155
|
+
else
|
156
|
+
redirect_to session['return-to']
|
157
|
+
session['return-to'] = nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def redirect_back_or_default(default=nil)
|
162
|
+
if request.env["HTTP_REFERER"].nil?
|
163
|
+
redirect_to default
|
164
|
+
else
|
165
|
+
redirect_to(request.env["HTTP_REFERER"]) # same as redirect_to :back
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# This method will return true if:
|
171
|
+
#
|
172
|
+
# * The Guest Role is authorized to perform the current action
|
173
|
+
# * The currently logged in user is omnipotent
|
174
|
+
# * The currently logged in user has permission to perform the current
|
175
|
+
# action.
|
176
|
+
#
|
177
|
+
# In all other cases, it will return false. This method is a replacement
|
178
|
+
# for the +login_required+ method provided by the AccountEngine. If the Guest
|
179
|
+
# role does not have permission for the current action, the user will be
|
180
|
+
# redirected to the login page (and redirected back to this action upon
|
181
|
+
# successful authentication). Users can also authenticate directly via
|
182
|
+
# a security token (see AccountEngine for details).
|
183
|
+
def authorize_action
|
184
|
+
required_permission = "%s/%s" % [ params["controller"], params["action"] ]
|
185
|
+
logger.debug "required_perm is #{required_permission}"
|
186
|
+
|
187
|
+
controller = params["controller"]
|
188
|
+
action = params["action"]
|
189
|
+
|
190
|
+
# EVERYONE should be able to get to the root. This might never come up, but
|
191
|
+
# better to be safe than sorry. This condition could just as easily be
|
192
|
+
# appended to the Guest check below, but it's clearer up here.
|
193
|
+
if (controller == nil && action == nil)
|
194
|
+
return true
|
195
|
+
end
|
196
|
+
|
197
|
+
# if the controller wasn't nil, but the action was, then we want to
|
198
|
+
# set the action to "index" so we can check authentication properly
|
199
|
+
action ||= "index"
|
200
|
+
|
201
|
+
# If someone is or can be logged in...
|
202
|
+
# calling 'user?' from the AccountEngine will ensure that a User is
|
203
|
+
# loaded into the session if possible. It could either be there already
|
204
|
+
# or via a user_id and security key
|
205
|
+
if user?
|
206
|
+
# ... then if that logged user is NOT authorised...
|
207
|
+
|
208
|
+
unless current_user.authorized?(controller, action)
|
209
|
+
# YOU... SHALL... NOT... PASS!
|
210
|
+
|
211
|
+
flash[:message] = "Permission warning: You are not authorized for the action '#{required_permission}'."
|
212
|
+
|
213
|
+
# Here we are distinguishing between unauthorized actions and actions which do
|
214
|
+
# not exist. It *might* be better to employ a 'steath' technique and simple
|
215
|
+
# claim that all nonsense actions are unauthorized too... but that can make it
|
216
|
+
# difficult to debug.
|
217
|
+
if !AccountEngine.stealth
|
218
|
+
if Permission.find_by_controller_and_action(controller, action)
|
219
|
+
|
220
|
+
# This is a real action, but the user is not allowed to perform it.
|
221
|
+
allowed_roles = Permission.find_by_controller_and_action(controller, action).roles.collect {|r| r.name}.join(', ')
|
222
|
+
your_roles = current_user.roles.collect {|r| r.name}.join(', ')
|
223
|
+
flash[:message] << " Allowed Roles: #{allowed_roles}. User '#{current_user.login}' has only the following: #{your_roles}."
|
224
|
+
|
225
|
+
else # This wasn't even a real action.
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
return false
|
230
|
+
end
|
231
|
+
else
|
232
|
+
|
233
|
+
# noone is or can be logged in...
|
234
|
+
unless User.guest_user_authorized?(controller, action)
|
235
|
+
flash[:message] = "You need to log in."
|
236
|
+
return false
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# If we get here, the user is either a guest and this action is permitted
|
241
|
+
# for guest users, or the user is logged in and the action is permitted by
|
242
|
+
# one or more of their associated roles. Let them pass..
|
243
|
+
return true
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
require 'account_engine/support'
|
3
|
+
|
4
|
+
module AccountEngine
|
5
|
+
module Helper
|
6
|
+
include AccountEngine::Support
|
7
|
+
|
8
|
+
def link_to_user(user = current_user)
|
9
|
+
link_to user.fullname, :controller => 'account', :action => 'show'
|
10
|
+
end
|
11
|
+
|
12
|
+
#--
|
13
|
+
# The methods to be included in both ApplicationController and ApplicationHelper
|
14
|
+
#++
|
15
|
+
|
16
|
+
# Returns an HTML link if the user has authorisation to perform the
|
17
|
+
# supplied action. All other options and parameters are identical to
|
18
|
+
# those for ActionView::link_to
|
19
|
+
# e.g.
|
20
|
+
# link_if_authorized("Home", {:controller => "home", :action => "index"})
|
21
|
+
#
|
22
|
+
# If either of the :controller or :action options are ommitted, the
|
23
|
+
# current controller or action will be used instead.
|
24
|
+
#
|
25
|
+
# This method can also take an additional block, which can override the actual
|
26
|
+
# user permissions (i.e. the user must have valid permissions AND this block
|
27
|
+
# must not return false or nil for the link to be generated).
|
28
|
+
#
|
29
|
+
# We also provide special elements with the html_options argument.
|
30
|
+
#
|
31
|
+
# === :wrap_in
|
32
|
+
# This can be used to wrap the link in a given tag. This is useful if some
|
33
|
+
# surrounding markup to the link should also be ommitted if the user is not
|
34
|
+
# authorised for that link. E.g.
|
35
|
+
# <ul>
|
36
|
+
# <%= link_if_authorised("Delete", {:action => "delete"}, :wrap_in => "li") %>
|
37
|
+
# ...
|
38
|
+
# </ul>
|
39
|
+
#
|
40
|
+
# In this case, if the user is not authorised for this link, the <li></li>
|
41
|
+
# element will not be generated. Please note that this is fairly simplistic
|
42
|
+
# and relies on Rails' own #content_tag method. For more sophisticated
|
43
|
+
# control of markup based on authorisation, use the #authorised?() method
|
44
|
+
# directly.
|
45
|
+
#
|
46
|
+
# === :show_text
|
47
|
+
# if this flag is set to true, the text given for the link will be shown
|
48
|
+
# (although not as a link) even if the use is NOT authorised for the given
|
49
|
+
# action.
|
50
|
+
def link_if_authorized(name, options = {}, html_options = {}, *params, &block)
|
51
|
+
result = html_options.delete(:show_text) ? name : ""
|
52
|
+
|
53
|
+
# we need to strip leading slashes when checking authorisation, but not when
|
54
|
+
# actually generating the link.
|
55
|
+
auth_options = options.dup
|
56
|
+
if auth_options[:controller]
|
57
|
+
auth_options[:controller] = auth_options[:controller].gsub(/^\//, '')
|
58
|
+
end
|
59
|
+
|
60
|
+
(block.nil? || (yield block)) && authorized?(auth_options) {
|
61
|
+
#result = link_to_with_current_styling(name, options, html_options, *params)
|
62
|
+
result = link_to(name, options, html_options, *params)
|
63
|
+
|
64
|
+
# TODO: won't this pass other things like html_options[:id], which is EVIL since two
|
65
|
+
# things shouldn't share the same ID.
|
66
|
+
wrap_tag = html_options.delete(:wrap_in)
|
67
|
+
result = content_tag(wrap_tag, result, html_options) if wrap_tag != nil
|
68
|
+
}
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns true, and also executes an optional code block if the current user
|
73
|
+
# is authorised for the supplied controller and action. If no action is
|
74
|
+
# supplied, "index" is used by default. Returns false if the user is not
|
75
|
+
# authorised.
|
76
|
+
# e.g.
|
77
|
+
# <% authorized?("person", "destroy") { %>
|
78
|
+
# <p>You have the power to destroy users! Well done.</p>
|
79
|
+
# <% } %>
|
80
|
+
def authorized?(options, &block) # default the action to "index"
|
81
|
+
controller = options[:controller]
|
82
|
+
action = options[:action]
|
83
|
+
|
84
|
+
# use the current controller/action if none is given in options
|
85
|
+
controller ||= @controller.controller_name
|
86
|
+
action ||= @controller.action_name
|
87
|
+
|
88
|
+
if current_user.nil?
|
89
|
+
RAILS_DEFAULT_LOGGER.debug "checking guest authorisation for #{controller}/#{action}"
|
90
|
+
if User.guest_user_authorized?(controller, action)
|
91
|
+
yield block if block != nil
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
else
|
95
|
+
RAILS_DEFAULT_LOGGER.debug "checking user:#{session[:user].id} authorisation for #{controller}/#{action}"
|
96
|
+
if current_user.authorized?(controller, action)
|
97
|
+
yield block if block != nil
|
98
|
+
return true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|