authcan_easyroller 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +217 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/app/controllers/authcan_easyroller_controller.rb +79 -0
- data/app/controllers/user_sessions_controller.rb +35 -0
- data/app/controllers/users_controller.rb +60 -0
- data/app/helpers/authcan_easyroller_helper.rb +36 -0
- data/app/models/ability.rb +41 -0
- data/app/models/user.rb +54 -0
- data/app/models/user_session.rb +3 -0
- data/app/views/user_sessions/new.html.erb +27 -0
- data/app/views/users/_form.html.erb +23 -0
- data/app/views/users/edit.html.erb +13 -0
- data/app/views/users/index.html.erb +35 -0
- data/app/views/users/new.html.erb +11 -0
- data/app/views/users/show.html.erb +39 -0
- data/authcan_easyroller.gemspec +70 -0
- data/config/routes.rb +4 -0
- data/example/ability.rb +41 -0
- data/example/application.html.erb +40 -0
- data/example/main.css +41 -0
- data/lib/authcan_easyroller.rb +20 -0
- data/test/helper.rb +10 -0
- data/test/test_authcan_easyroller.rb +7 -0
- metadata +100 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Topher Fangio
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
== Rails 3 Notes
|
2
|
+
|
3
|
+
I wanted to put this at the top so that nobody misses it. There are currently some bugs due to
|
4
|
+
Rails 3 still being in beta. As I come across these bugs, I will add them below and any workaround
|
5
|
+
if available.
|
6
|
+
|
7
|
+
* Bug #3928[https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/3928] - process_parameter_filter throws an exception on array parameters
|
8
|
+
|
9
|
+
[Status:] Resolved in source control, awaiting next beta release.
|
10
|
+
[Workaround:] The code to enable parameter filtering in `lib/authcan_easyroller.rb` has been commented out until the next beta release.
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
== authcan_easyroller
|
15
|
+
|
16
|
+
This is a basic Rails engine utilizing Authlogic[http://github.com/binarylogic/authlogic],
|
17
|
+
CanCan[http://github.com/ryanb/cancan] and Easy Roles[http://github.com/platform45/easy_roles]
|
18
|
+
for simple Rails 3.x applications that need authentication and authorization.
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
== Basis for Creation
|
23
|
+
|
24
|
+
As a Rails developer, I find myself needing an elegant and easy-to-use solution for authentication
|
25
|
+
and authorization in the small web-apps that I create. They generally aren't linked together since
|
26
|
+
they are separate clients with completely different ideas and business models, but they both need
|
27
|
+
the same underlying code to manage their users.
|
28
|
+
|
29
|
+
I have used Authlogic for quite a while now and have found it to be very useful and a complete
|
30
|
+
solution for authentication. Adding a dash of functionality here and there is fairly easy and the
|
31
|
+
ability to extend it really drew me in (in fact, I helped develop some of the code behind
|
32
|
+
authlogic_ldap[http://github.com/topherfangio/authlogic_ldap] which I never got to finish but
|
33
|
+
still intend to do).
|
34
|
+
|
35
|
+
After watching the screencast[http://railscasts.com/episodes/192-authorization-with-cancan] by
|
36
|
+
Ryan Bates, I decided that CanCan was a great addition to my applications since it was easy to
|
37
|
+
use and cleanly separated the authorization from the model and view logic. The only other piece
|
38
|
+
was an easy way to manage roles soley in code, yet while storing the assignments in the database;
|
39
|
+
and I found this in Easy Roles.
|
40
|
+
|
41
|
+
Putting the three together, I now have a complete (albeit simple) authentication and authorization
|
42
|
+
solution that I can easily plugin into existing or new applications (and update in one spot).
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
== Future Enhancements
|
47
|
+
|
48
|
+
Before we get into the nitty-gritty of how to use it, I wanted to go ahead and mention the
|
49
|
+
planned enhancements so you can get a feel of where this project will go.
|
50
|
+
|
51
|
+
* Authlogic supports many different types of authentication without you needing to change
|
52
|
+
hardly anything, so I plan on modifying the code to allow for all of the possible authentication
|
53
|
+
schemes. Thus, I definitely plan on integrating OpenID, LDAP, Facebook Connect and OAuth. I may
|
54
|
+
or may not implement PAM, but we'll see.
|
55
|
+
* I plan on adding an easy password reset mechanism for users provided that you use an email column.
|
56
|
+
* I also plan to add the configuration option to turn on e-mail verification before users are granted
|
57
|
+
access to the system.
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
== Installation/Setup
|
62
|
+
|
63
|
+
Now that the code is a Rails 3 engine, installation is very simple; just install the gem and it's dependencies!
|
64
|
+
|
65
|
+
gem install rails authlogic cancan easy_roles authcan_easyroller
|
66
|
+
|
67
|
+
Next, create a migration for the users:
|
68
|
+
|
69
|
+
rails generate migration CreateUsers
|
70
|
+
|
71
|
+
Then, copy the following contents into that file making any changes you see fit:
|
72
|
+
|
73
|
+
class CreateUsers < ActiveRecord::Migration
|
74
|
+
def self.up
|
75
|
+
create_table :users do |t|
|
76
|
+
# Necessary Columns - These are required for AuthcanEasyroller to function properly
|
77
|
+
t.string :email, :null => false
|
78
|
+
t.string :crypted_password, :null => false
|
79
|
+
t.string :password_salt, :null => false
|
80
|
+
t.string :persistence_token, :null => false
|
81
|
+
t.string :single_access_token, :null => false
|
82
|
+
t.string :perishable_token, :null => false
|
83
|
+
t.integer :roles_mask, :null => false, :default => 0
|
84
|
+
|
85
|
+
# Magic Columns - You may leave any of the following out if you wish
|
86
|
+
t.integer :login_count, :null => false, :default => 0
|
87
|
+
t.integer :failed_login_count, :null => false, :default => 0
|
88
|
+
t.datetime :last_request_at
|
89
|
+
t.datetime :current_login_at
|
90
|
+
t.datetime :last_login_at
|
91
|
+
t.string :current_login_ip
|
92
|
+
t.string :last_login_ip
|
93
|
+
|
94
|
+
# Timestamp Columns - You should have these on every database table you create
|
95
|
+
t.timestamps
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.down
|
100
|
+
drop_table :users
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Once saved, migrate your database by running
|
105
|
+
|
106
|
+
rake db:migrate
|
107
|
+
|
108
|
+
Next, copy the following files to their proper locations (feel free to edit them, these are just some basics to
|
109
|
+
help get you started). The <tt>rails.js</tt> file at the bottom of the list is the official Rails jQuery file available
|
110
|
+
at http://github.com/rails/jquery-ujs .
|
111
|
+
|
112
|
+
* ability.rb[http://github.com/topherfangio/authcan_easyroller/raw/master/examples/ability.rb] -> <<APPLICATION>>/app/models/ability.rb
|
113
|
+
* application.html.erb[http://github.com/topherfangio/authcan_easyroller/raw/master/examples/application.html.erb] -> <<APPLICATION>>/app/view/layouts/application.html.erb
|
114
|
+
* main.css[http://github.com/topherfangio/authcan_easyroller/raw/master/examples/main.css] -> <<APPLICATION>/public/stylesheets/main.css
|
115
|
+
* rails.js[http://github.com/rails/jquery-ujs/raw/master/src/rails.js] -> <<APPLICATION>/public/javascripts/rails.js
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
== Starting Your Engine
|
120
|
+
|
121
|
+
Once you have everything setup correctly, simply run the following to start your application, then visit http://localhost:3000
|
122
|
+
|
123
|
+
rails server
|
124
|
+
|
125
|
+
The application realizes that it has no users, and forces you to create one before you can continue
|
126
|
+
to any page. The first user is always created as an administrator and thus has privileges to create
|
127
|
+
new users, give themselves the "developer" role (or any/all roles) and do other adminy things. All
|
128
|
+
users created after this will be given the default role of "user".
|
129
|
+
|
130
|
+
Currently, the available roles are
|
131
|
+
|
132
|
+
1. Developer
|
133
|
+
2. Admin
|
134
|
+
3. Moderator
|
135
|
+
4. User
|
136
|
+
|
137
|
+
I'm working on a way to extend this functionality as a configuration option, or perhaps give it an
|
138
|
+
API so that you can create your own roles or modify the existing ones without having to delve into
|
139
|
+
the gem's code. Check back often to see where that stands if it is something you need.
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
== Usage
|
144
|
+
|
145
|
+
You can find out more by checking each specific project's documentation, but here is the gist:
|
146
|
+
|
147
|
+
A user's abilities are defined in <tt>app/models/ability.rb</tt>. I generally prefer to specify what
|
148
|
+
each role is allowed to do and then give a user all of the roles that they need instead of
|
149
|
+
saying that an admin can do everything that a moderator can do. This tends to keep the ability
|
150
|
+
model cleaner and your views don't change either. In addition, this allows you to assign roles
|
151
|
+
to a user for special circumstances. For instance, if you are writing a help desk app, you may
|
152
|
+
decide that one particular customer is really superb and should also have status update abilities
|
153
|
+
even though he has the customer role.
|
154
|
+
|
155
|
+
You define your abilities in each role's section. For instance, the moderator's role currently looks
|
156
|
+
like so:
|
157
|
+
|
158
|
+
# Moderator role abilities
|
159
|
+
if current_user.is_moderator?
|
160
|
+
end
|
161
|
+
|
162
|
+
If you wanted to let moderators manage users, you would simply call <tt>can</tt>
|
163
|
+
|
164
|
+
# Moderator role abilities
|
165
|
+
if current_user.is_moderator?
|
166
|
+
can :manage, User
|
167
|
+
end
|
168
|
+
|
169
|
+
You can also define your own abilities if they don't tie to a particular object, but you must pass a
|
170
|
+
<tt>nil</tt> object as the second argument to <tt>can?</tt> and you must specify all object types when
|
171
|
+
defining the ability. I'll get in touch with the developer to see if this can be a tad bit more streamlined.
|
172
|
+
|
173
|
+
# ability.rb
|
174
|
+
|
175
|
+
# Moderator role abilities
|
176
|
+
if current_user.is_moderator?
|
177
|
+
can :visit_woot_all_day, :all
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# application.html.erb
|
182
|
+
|
183
|
+
<% if can? :visit_woot_all_day, nil %>
|
184
|
+
<%= link_to "Woot", "http://www.woot.com" %>
|
185
|
+
<% end %>
|
186
|
+
|
187
|
+
Once you have the roles and abilities setup, your views can check who has access by simply calling <tt>can?</tt>
|
188
|
+
|
189
|
+
if can? :create, Comment
|
190
|
+
...
|
191
|
+
end
|
192
|
+
|
193
|
+
You can also use <tt>cannot?</tt>
|
194
|
+
|
195
|
+
link_to "Export", export_users_url unless cannot? :create, Export
|
196
|
+
|
197
|
+
Using <tt>can?</tt> and <tt>cannot?</tt> is the preferred method of checking authorization privileges, however,
|
198
|
+
if you find a rare case that you need to limit access based on the role(s) that a user has, you can
|
199
|
+
always do the following:
|
200
|
+
|
201
|
+
if current_user.is_moderator? || current_user.is_admin?
|
202
|
+
...
|
203
|
+
end
|
204
|
+
|
205
|
+
However, the beauty of authcan_easyroller (more specifically the CanCan integration) is that you
|
206
|
+
don't have to. That is the exact purpose of the <tt>Ability</tt> class! Use it to your advantage and
|
207
|
+
make your life easier.
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
== Special Thanks
|
212
|
+
|
213
|
+
I would like to thank the creators of Authlogic, CanCan and Easy Roles for the effort that they put
|
214
|
+
into these plugins. Adding them together was relatively straightforward and easy and I hope that they
|
215
|
+
realize how much time this saves other developers!
|
216
|
+
|
217
|
+
Copyright (c) 2010 Topher Fangio, released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "authcan_easyroller"
|
8
|
+
gem.summary = %Q{Rails 3 engine for user authentication/authorization utilizing Authlogic, CanCan and EasyRoles}
|
9
|
+
gem.description = %Q{This is a basic Rails engine utilizing Authlogic, CanCan and Easy Roles to create a starting point for simple Rails-based applications that need authentication and authorization. }
|
10
|
+
gem.email = "fangiotophia@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/topherfangio/authcan_easyroller"
|
12
|
+
gem.authors = ["Topher Fangio"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "authcan_easyroller #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class AuthcanEasyrollerController < ApplicationController
|
2
|
+
helper :all # include all helpers, all the time
|
3
|
+
protect_from_forgery # See ActionController::RequestForgeryProtection for details
|
4
|
+
|
5
|
+
# Scrub sensitive parameters from your log
|
6
|
+
helper_method :current_user_session, :current_user
|
7
|
+
|
8
|
+
rescue_from CanCan::AccessDenied do |exception|
|
9
|
+
flash[:error] = exception.message
|
10
|
+
redirect_back_or_default(root_url)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Ensure there is at least one user in the system before trying to do anything
|
14
|
+
before_filter :require_one_user
|
15
|
+
after_filter :store_location
|
16
|
+
|
17
|
+
private
|
18
|
+
def current_user_session
|
19
|
+
return @current_user_session if defined?(@current_user_session)
|
20
|
+
@current_user_session = UserSession.find
|
21
|
+
end
|
22
|
+
|
23
|
+
def current_user
|
24
|
+
if defined?(@current_user)
|
25
|
+
return @current_user
|
26
|
+
end
|
27
|
+
|
28
|
+
@current_user = current_user_session && current_user_session.user
|
29
|
+
|
30
|
+
if @current_user_session and @current_user_session.stale?
|
31
|
+
flash[:warning] = "You have been logged out due to an extended period of inactivity."
|
32
|
+
@current_user_session.destroy
|
33
|
+
end
|
34
|
+
|
35
|
+
return @current_user
|
36
|
+
end
|
37
|
+
|
38
|
+
def require_one_user
|
39
|
+
if User.count == 0 and not users_url.match(/#{request.request_uri}$/) and not new_user_url.match(/#{request.request_uri}$/)
|
40
|
+
flash[:error] = "No users yet, must create one to access the site."
|
41
|
+
|
42
|
+
redirect_to new_user_url
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def require_user
|
48
|
+
unless current_user
|
49
|
+
store_location
|
50
|
+
|
51
|
+
flash[:notice] = "You must be logged in to access this page."
|
52
|
+
redirect_to new_user_session_url
|
53
|
+
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def require_no_user
|
59
|
+
if current_user
|
60
|
+
store_location
|
61
|
+
|
62
|
+
flash[:notice] = "You must be logged out to access this page."
|
63
|
+
redirect_to user_url(current_user)
|
64
|
+
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def store_location
|
70
|
+
unless new_user_url.match(/#{request.request_uri}$/) or new_user_session_url.match(/#{request.request_uri}$/)
|
71
|
+
session[:return_to] = request.request_uri
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def redirect_back_or_default(default)
|
76
|
+
redirect_to(session[:return_to] || default)
|
77
|
+
session[:return_to] = nil
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class UserSessionsController < AuthcanEasyrollerController
|
2
|
+
before_filter :require_no_user, :only => [:new, :create]
|
3
|
+
before_filter :require_user, :only => :destroy
|
4
|
+
|
5
|
+
load_and_authorize_resource
|
6
|
+
|
7
|
+
def new
|
8
|
+
respond_to do |format|
|
9
|
+
if current_user
|
10
|
+
redirect_to :controll => 'users', :action => 'show', :id => current_user
|
11
|
+
else
|
12
|
+
@user_session = UserSession.new
|
13
|
+
|
14
|
+
format.html # new.html.erb
|
15
|
+
format.xml { render :xml => @user_session }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@user_session = UserSession.new(params[:user_session])
|
22
|
+
if @user_session.save
|
23
|
+
flash[:notice] = "Login successful!"
|
24
|
+
redirect_back_or_default user_url(@user_session.user)
|
25
|
+
else
|
26
|
+
render :action => :new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy
|
31
|
+
current_user_session.destroy
|
32
|
+
flash[:notice] = "Logout successful!"
|
33
|
+
redirect_back_or_default '/'
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class UsersController < AuthcanEasyrollerController
|
2
|
+
|
3
|
+
before_filter :load_correct_user, :only => [:show, :edit, :update]
|
4
|
+
before_filter :require_user, :only => [:edit, :update]
|
5
|
+
|
6
|
+
load_and_authorize_resource
|
7
|
+
|
8
|
+
def index
|
9
|
+
@users = User.all
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
@user = User.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
@user = User.new(params[:user])
|
18
|
+
if @user.save
|
19
|
+
flash[:notice] = "Account registered!"
|
20
|
+
redirect_back_or_default user_url(@user)
|
21
|
+
else
|
22
|
+
render :action => :new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def show
|
27
|
+
require_user if @user.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def edit
|
31
|
+
end
|
32
|
+
|
33
|
+
def update
|
34
|
+
if @user.update_attributes(params[:user])
|
35
|
+
flash[:notice] = "Account updated!"
|
36
|
+
redirect_to user_url(@user)
|
37
|
+
else
|
38
|
+
render :action => :edit
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def destroy
|
43
|
+
@user = User.find(params[:id])
|
44
|
+
@user.destroy
|
45
|
+
|
46
|
+
respond_to do |format|
|
47
|
+
format.html { redirect_to(users_url) }
|
48
|
+
format.xml { head :ok }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def load_correct_user
|
54
|
+
if params[:id]
|
55
|
+
@user = User.find(params[:id])
|
56
|
+
else
|
57
|
+
@user = current_user
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module AuthcanEasyrollerHelper
|
2
|
+
def namify(object)
|
3
|
+
if object.present? and object.respond_to? :name
|
4
|
+
object.send(:name)
|
5
|
+
else
|
6
|
+
""
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def datify(object, format = :long)
|
11
|
+
object.to_s(format) unless object.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def phonify(object)
|
15
|
+
if object.respond_to? :gsub
|
16
|
+
s = object.gsub(/[^0-9]/, '')
|
17
|
+
|
18
|
+
case s.length
|
19
|
+
when 11 then "#{s[0..0]} (#{s[1..3]}) #{s[4..6]}-#{s[7..10]}"
|
20
|
+
when 10 then "(#{s[0..2]}) #{s[3..5]}-#{s[6..9]}"
|
21
|
+
when 7 then "#{s[0..2]}-#{s[3..6]}"
|
22
|
+
else s
|
23
|
+
end
|
24
|
+
else
|
25
|
+
object.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def form_descriptor(message = nil)
|
30
|
+
raw "<br /><span class='form-descriptor-element'>#{message}</span>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def link_separator
|
34
|
+
raw " | "
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Ability
|
2
|
+
include CanCan::Ability
|
3
|
+
|
4
|
+
def initialize(current_user)
|
5
|
+
can :read, :all
|
6
|
+
can :manage, UserSession
|
7
|
+
|
8
|
+
if current_user
|
9
|
+
# Abilities for someone with an account (does not necessarily have a "user" role)
|
10
|
+
can [:update, :destroy], User do |user|
|
11
|
+
user == current_user
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# User role abilities
|
16
|
+
if current_user.is_user?
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Moderator role abilities
|
21
|
+
if current_user.is_moderator?
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Admin role abilities
|
26
|
+
if current_user.is_admin?
|
27
|
+
can :manage, :all
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Developer role abilities
|
32
|
+
if current_user.is_developer?
|
33
|
+
can :manage, :all
|
34
|
+
end
|
35
|
+
else
|
36
|
+
can :create, User
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/app/models/user.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
class User < ActiveRecord::Base
|
2
|
+
|
3
|
+
# Constant variable storing roles in the system
|
4
|
+
ROLES_MASK = %w[developer admin moderator user]
|
5
|
+
|
6
|
+
HUMANIZED_COLUMNS = {
|
7
|
+
:email => 'E-mail'
|
8
|
+
}
|
9
|
+
|
10
|
+
|
11
|
+
# Authentication and Authorization
|
12
|
+
acts_as_authentic do |config|
|
13
|
+
config.logged_in_timeout = 1.hour
|
14
|
+
end
|
15
|
+
|
16
|
+
easy_roles :roles_mask, :method => :bitmask
|
17
|
+
|
18
|
+
|
19
|
+
# Ensure the user has the proper roles
|
20
|
+
before_create :assign_user_roles
|
21
|
+
|
22
|
+
|
23
|
+
# Validations
|
24
|
+
validates_each :roles do |record, attr, value|
|
25
|
+
begin
|
26
|
+
# If the roles changed, and it's not a new record whose only role is "user" and there is at least 1 user already in the system...
|
27
|
+
if record.roles_mask_changed? and not (record.new_record? and record.roles == ["user"]) and User.count > 0
|
28
|
+
session = UserSession.find
|
29
|
+
current_user = session.user unless session.nil?
|
30
|
+
|
31
|
+
record.errors.add attr, ' can only be modified by an administrator.' if (not current_user.is_developer? and not current_user.is_admin?)
|
32
|
+
end
|
33
|
+
rescue Authlogic::Session::Activation::NotActivatedError
|
34
|
+
# Do nothing, we are in a console session
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Allows us to easily humanize our attributes in one location
|
40
|
+
# so we don't have to do it in every view
|
41
|
+
def self.human_attribute_name(attribute)
|
42
|
+
HUMANIZED_COLUMNS[attribute.to_sym] || super
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def assign_user_roles
|
47
|
+
self.roles += ["user"]
|
48
|
+
|
49
|
+
if User.count == 0
|
50
|
+
self.roles += ["admin"]
|
51
|
+
self.roles += ["moderator"]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<h1>Login</h1>
|
2
|
+
|
3
|
+
<% form_for @user_session, :url => user_session_path do |f| %>
|
4
|
+
<%= f.error_messages %>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<%= f.label :email %>
|
8
|
+
<%= f.text_field :email %>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<%= f.label :password %>
|
13
|
+
<%= f.password_field :password %>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<p>
|
17
|
+
<%= f.check_box :remember_me %><%= f.label :remember_me %>
|
18
|
+
</p>
|
19
|
+
|
20
|
+
<p>
|
21
|
+
<%= f.submit "Login" %>
|
22
|
+
</p>
|
23
|
+
|
24
|
+
<p>
|
25
|
+
Don't have an account yet? Why not <%= link_to "sign up", new_user_path %>!
|
26
|
+
</p>
|
27
|
+
<% end %>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<%= form.hidden_field :id unless form.object.new_record? %>
|
2
|
+
|
3
|
+
<p>
|
4
|
+
<%= form.label :email, "E-mail" %>
|
5
|
+
<%= form.text_field :email %>
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<p>
|
9
|
+
<%= form.label :password, form.object.new_record? ? "Password" : "Change Password" %>
|
10
|
+
<%= form.password_field :password %>
|
11
|
+
</p>
|
12
|
+
|
13
|
+
<p>
|
14
|
+
<%= form.label :password_confirmation, "Password Confirmation" %>
|
15
|
+
<%= form.password_field :password_confirmation %>
|
16
|
+
</p>
|
17
|
+
|
18
|
+
<% if can? :manage, :roles %>
|
19
|
+
<p>
|
20
|
+
<%= form.label :roles %>
|
21
|
+
<%= form.select :roles, User::ROLES_MASK, {}, { :multiple => true, :size => 4 } %>
|
22
|
+
</p>
|
23
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<h3>Edit My Account</h3>
|
2
|
+
|
3
|
+
<% form_for @user do |f| %>
|
4
|
+
<%= f.error_messages %>
|
5
|
+
|
6
|
+
<%= render :partial => "form", :object => f %>
|
7
|
+
|
8
|
+
<p>
|
9
|
+
<%= f.submit "Update" %> or <%= link_to 'return to list', users_path %>
|
10
|
+
</p>
|
11
|
+
<% end %>
|
12
|
+
|
13
|
+
<br /><%= link_to "My Profile", user_path(@user) %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<h3>Listing Users</h3>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Email</th>
|
6
|
+
<th>Last Seen</th>
|
7
|
+
<th>Action</th>
|
8
|
+
</tr>
|
9
|
+
|
10
|
+
<% @users.each do |user| %>
|
11
|
+
<tr>
|
12
|
+
<td><%= user.email %></td>
|
13
|
+
<td><%= time_ago_in_words user.last_request_at %> ago</td>
|
14
|
+
|
15
|
+
<td>
|
16
|
+
<%= link_to 'Show', user %>
|
17
|
+
|
18
|
+
<% if can? :update, user %>
|
19
|
+
<%= link_separator %>
|
20
|
+
<%= link_to 'Edit', edit_user_path(user) %>
|
21
|
+
<% end %>
|
22
|
+
|
23
|
+
<% if can? :destroy, user %>
|
24
|
+
<%= link_separator %>
|
25
|
+
<%= link_to 'Delete', user, :confirm => 'Are you sure?', :method => :delete %>
|
26
|
+
<% end %>
|
27
|
+
</td>
|
28
|
+
</tr>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
</table>
|
32
|
+
|
33
|
+
<br />
|
34
|
+
|
35
|
+
Not signed up? <%= link_to 'Register', new_user_path %> now!
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<h3>Showing User Information</h3>
|
2
|
+
|
3
|
+
<p>
|
4
|
+
<b>E-mail:</b>
|
5
|
+
<%=h @user.email %>
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<p>
|
9
|
+
<b>Login Count:</b>
|
10
|
+
<%=h @user.login_count %>
|
11
|
+
</p>
|
12
|
+
|
13
|
+
<p>
|
14
|
+
<b>Last Request at:</b>
|
15
|
+
<%= datify @user.last_request_at %>
|
16
|
+
</p>
|
17
|
+
|
18
|
+
<p>
|
19
|
+
<b>Last Login at:</b>
|
20
|
+
<%= datify @user.last_login_at %>
|
21
|
+
</p>
|
22
|
+
|
23
|
+
<p>
|
24
|
+
<b>Current Login at:</b>
|
25
|
+
<%= datify @user.current_login_at %>
|
26
|
+
</p>
|
27
|
+
|
28
|
+
<p>
|
29
|
+
<b>Last Login IP:</b>
|
30
|
+
<%=h @user.last_login_ip %>
|
31
|
+
</p>
|
32
|
+
|
33
|
+
<p>
|
34
|
+
<b>Current Login IP:</b>
|
35
|
+
<%=h @user.current_login_ip %>
|
36
|
+
</p>
|
37
|
+
|
38
|
+
|
39
|
+
<%= link_to 'Edit', edit_user_path(@user) %> or <%= link_to 'return to list', users_path %>
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{authcan_easyroller}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Topher Fangio"]
|
12
|
+
s.date = %q{2010-03-31}
|
13
|
+
s.description = %q{This is a basic Rails engine utilizing Authlogic, CanCan and Easy Roles to create a starting point for simple Rails-based applications that need authentication and authorization. }
|
14
|
+
s.email = %q{fangiotophia@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"app/controllers/authcan_easyroller_controller.rb",
|
26
|
+
"app/controllers/user_sessions_controller.rb",
|
27
|
+
"app/controllers/users_controller.rb",
|
28
|
+
"app/helpers/authcan_easyroller_helper.rb",
|
29
|
+
"app/models/ability.rb",
|
30
|
+
"app/models/user.rb",
|
31
|
+
"app/models/user_session.rb",
|
32
|
+
"app/views/user_sessions/new.html.erb",
|
33
|
+
"app/views/users/_form.html.erb",
|
34
|
+
"app/views/users/edit.html.erb",
|
35
|
+
"app/views/users/index.html.erb",
|
36
|
+
"app/views/users/new.html.erb",
|
37
|
+
"app/views/users/show.html.erb",
|
38
|
+
"authcan_easyroller.gemspec",
|
39
|
+
"config/routes.rb",
|
40
|
+
"example/ability.rb",
|
41
|
+
"example/application.html.erb",
|
42
|
+
"example/main.css",
|
43
|
+
"lib/authcan_easyroller.rb",
|
44
|
+
"test/helper.rb",
|
45
|
+
"test/test_authcan_easyroller.rb"
|
46
|
+
]
|
47
|
+
s.homepage = %q{http://github.com/topherfangio/authcan_easyroller}
|
48
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = %q{1.3.6}
|
51
|
+
s.summary = %q{Rails 3 engine for user authentication/authorization utilizing Authlogic, CanCan and EasyRoles}
|
52
|
+
s.test_files = [
|
53
|
+
"test/helper.rb",
|
54
|
+
"test/test_authcan_easyroller.rb"
|
55
|
+
]
|
56
|
+
|
57
|
+
if s.respond_to? :specification_version then
|
58
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
59
|
+
s.specification_version = 3
|
60
|
+
|
61
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
62
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
65
|
+
end
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/config/routes.rb
ADDED
data/example/ability.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class Ability
|
2
|
+
include CanCan::Ability
|
3
|
+
|
4
|
+
def initialize(current_user)
|
5
|
+
can :read, :all
|
6
|
+
can :manage, UserSession
|
7
|
+
|
8
|
+
if current_user
|
9
|
+
# Abilities for someone with an account (does not necessarily have a "user" role)
|
10
|
+
can [:update, :destroy], User do |user|
|
11
|
+
user == current_user
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# User role abilities
|
16
|
+
if current_user.is_user?
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Moderator role abilities
|
21
|
+
if current_user.is_moderator?
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Admin role abilities
|
26
|
+
if current_user.is_admin?
|
27
|
+
can :manage, :all
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Developer role abilities
|
32
|
+
if current_user.is_developer?
|
33
|
+
can :manage, :all
|
34
|
+
end
|
35
|
+
else
|
36
|
+
can :create, User
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<script src="http://www.google.com/jsapi"></script>
|
4
|
+
<script>google.load("jquery", "1.4");</script>
|
5
|
+
|
6
|
+
<%= stylesheet_link_tag 'main' %>
|
7
|
+
</head>
|
8
|
+
|
9
|
+
<body>
|
10
|
+
<div id='navigation'>
|
11
|
+
<%= link_to "Users", users_path %>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div id='userland'>
|
15
|
+
<% if current_user %>
|
16
|
+
Welcome <%= current_user.email %>!
|
17
|
+
|
18
|
+
<%= link_to "My Account", user_path(current_user) %>
|
19
|
+
<%= link_separator %>
|
20
|
+
<%= link_to "Logout", '/user_sessions/destroy' %>
|
21
|
+
<% else %>
|
22
|
+
You are not currently
|
23
|
+
<%= link_to "logged in", new_user_session_path %>.
|
24
|
+
<% end %>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<div id='flashes'>
|
28
|
+
<%= raw "<h5 class='flash error'>#{flash[:error]}</h5>" unless flash[:error].blank? %>
|
29
|
+
<%= raw "<h5 class='flash notice'>#{flash[:notice]}</h5>" unless flash[:notice].blank? %>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div id='content'>
|
33
|
+
<%= yield %>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<div id='copyright'>
|
37
|
+
Copyright © 2010, MyCompany. All rights reserved.
|
38
|
+
</div>
|
39
|
+
</body>
|
40
|
+
</html>
|
data/example/main.css
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#navigation {
|
2
|
+
float: left;
|
3
|
+
margin-bottom: 10px;
|
4
|
+
}
|
5
|
+
|
6
|
+
#userland {
|
7
|
+
float: right;
|
8
|
+
margin-bottom: 10px;
|
9
|
+
}
|
10
|
+
|
11
|
+
#flashes {
|
12
|
+
clear: both;
|
13
|
+
}
|
14
|
+
|
15
|
+
#content {
|
16
|
+
margin: 10px;
|
17
|
+
}
|
18
|
+
|
19
|
+
#copyright {
|
20
|
+
text-align: center;
|
21
|
+
}
|
22
|
+
|
23
|
+
.flash {
|
24
|
+
border: 1px solid #000000;
|
25
|
+
padding: 10px;
|
26
|
+
}
|
27
|
+
|
28
|
+
.flash.error {
|
29
|
+
}
|
30
|
+
|
31
|
+
.flash.notice {
|
32
|
+
}
|
33
|
+
|
34
|
+
label {
|
35
|
+
font-weight: bold;
|
36
|
+
}
|
37
|
+
|
38
|
+
input[type=text],input[type=password],select,textarea {
|
39
|
+
clear: both;
|
40
|
+
display: block;
|
41
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'authcan_easyroller'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
# AuthcanEasyroller
|
5
|
+
module AuthcanEasyroller
|
6
|
+
class Engine < Rails::Engine
|
7
|
+
engine_name :authcan_easyroller
|
8
|
+
|
9
|
+
initializer "authcan_easyroller.configure_rails_initilization" do |app|
|
10
|
+
# NOTE: The following has been commented out to bypass bug #3928 which will be fixed with the next release.
|
11
|
+
#
|
12
|
+
# Please comment these back in when Rails 3.0.0.beta2 or later version is used.
|
13
|
+
#
|
14
|
+
# Bug here: https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/3928
|
15
|
+
#
|
16
|
+
app.config.filter_parameters << :password
|
17
|
+
app.config.filter_parameters << :password_confirmation
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: authcan_easyroller
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Topher Fangio
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-31 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: thoughtbot-shoulda
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
description: "This is a basic Rails engine utilizing Authlogic, CanCan and Easy Roles to create a starting point for simple Rails-based applications that need authentication and authorization. "
|
33
|
+
email: fangiotophia@gmail.com
|
34
|
+
executables: []
|
35
|
+
|
36
|
+
extensions: []
|
37
|
+
|
38
|
+
extra_rdoc_files:
|
39
|
+
- LICENSE
|
40
|
+
- README.rdoc
|
41
|
+
files:
|
42
|
+
- .gitignore
|
43
|
+
- LICENSE
|
44
|
+
- README.rdoc
|
45
|
+
- Rakefile
|
46
|
+
- VERSION
|
47
|
+
- app/controllers/authcan_easyroller_controller.rb
|
48
|
+
- app/controllers/user_sessions_controller.rb
|
49
|
+
- app/controllers/users_controller.rb
|
50
|
+
- app/helpers/authcan_easyroller_helper.rb
|
51
|
+
- app/models/ability.rb
|
52
|
+
- app/models/user.rb
|
53
|
+
- app/models/user_session.rb
|
54
|
+
- app/views/user_sessions/new.html.erb
|
55
|
+
- app/views/users/_form.html.erb
|
56
|
+
- app/views/users/edit.html.erb
|
57
|
+
- app/views/users/index.html.erb
|
58
|
+
- app/views/users/new.html.erb
|
59
|
+
- app/views/users/show.html.erb
|
60
|
+
- authcan_easyroller.gemspec
|
61
|
+
- config/routes.rb
|
62
|
+
- example/ability.rb
|
63
|
+
- example/application.html.erb
|
64
|
+
- example/main.css
|
65
|
+
- lib/authcan_easyroller.rb
|
66
|
+
- test/helper.rb
|
67
|
+
- test/test_authcan_easyroller.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/topherfangio/authcan_easyroller
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options:
|
74
|
+
- --charset=UTF-8
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
requirements: []
|
92
|
+
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.3.6
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Rails 3 engine for user authentication/authorization utilizing Authlogic, CanCan and EasyRoles
|
98
|
+
test_files:
|
99
|
+
- test/helper.rb
|
100
|
+
- test/test_authcan_easyroller.rb
|