padrino-auth 0.0.12
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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/EXAMPLES.md +249 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +118 -0
- data/Rakefile +12 -0
- data/lib/padrino-auth.rb +2 -0
- data/lib/padrino-auth/access.rb +148 -0
- data/lib/padrino-auth/login.rb +138 -0
- data/lib/padrino-auth/login/controller.rb +20 -0
- data/lib/padrino-auth/login/layouts/layout.slim +10 -0
- data/lib/padrino-auth/login/new.slim +35 -0
- data/lib/padrino-auth/permissions.rb +180 -0
- data/lib/padrino-auth/version.rb +5 -0
- data/padrino-auth.gemspec +29 -0
- data/test/helper.rb +68 -0
- data/test/test_padrino_access.rb +124 -0
- data/test/test_padrino_auth.rb +38 -0
- data/test/test_padrino_login.rb +81 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fe0f85bb7353604b8ba83cc1f2065702a18249a6
|
4
|
+
data.tar.gz: c6e23562acdc4d84ad7df25f53551fc673bd8bd1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7fbe645faf1515806edb29a26d5dad59130fce3a291c666bffdc9585e0aa941e4d324b26fa4be930f2ca4cac1f2c3ba2386138474c8641fe585d07dcae1e2840
|
7
|
+
data.tar.gz: e79a584a29ea547eb66b78afb232add8adb5e8f0560983f5cef18cd3fc2483eeed21488a2df4b370dd5ce52f5643c41713b004c8ce37b999a26f5ae3dd9c122e
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
Gemfile.lock
|
30
|
+
# .ruby-version
|
31
|
+
# .ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
data/EXAMPLES.md
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
### Authentication and authorization example
|
2
|
+
|
3
|
+
```
|
4
|
+
# sketchup some database model
|
5
|
+
module Character
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# db model must have authenticate method which should response with credentials object on
|
9
|
+
# the calls of { :email => 'a@b', :password => 'abc' } to authenticate by email and password
|
10
|
+
# or { :id => 42 } to restore credentials from id saved in session or another persistance storage
|
11
|
+
def authenticate(credentials)
|
12
|
+
case
|
13
|
+
when credentials[:email] && credentials[:password]
|
14
|
+
target = all.find{ |resource| resource.id.to_s == credentials[:email] }
|
15
|
+
(target && target.name.gsub(/[^A-Z]/,'') == credentials[:password]) ? target : nil
|
16
|
+
when credentials.has_key?(:id)
|
17
|
+
all.find{ |resource| resource.id == credentials[:id] }
|
18
|
+
else
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# example collection of users
|
24
|
+
def all
|
25
|
+
@all = [
|
26
|
+
OpenStruct.new(:id => :bender, :name => 'Bender Bending Rodriguez', :role => :robots ),
|
27
|
+
OpenStruct.new(:id => :leela, :name => 'Turanga Leela', :role => :mutants ),
|
28
|
+
OpenStruct.new(:id => :fry, :name => 'Philip J. Fry', :role => :humans ),
|
29
|
+
OpenStruct.new(:id => :ami, :name => 'Amy Wong', :role => :humans ),
|
30
|
+
OpenStruct.new(:id => :zoidberg, :name => 'Dr. John A. Zoidberg', :role => :lobsters),
|
31
|
+
]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Example; end
|
36
|
+
# define an application class
|
37
|
+
class Example::App < Padrino::Application
|
38
|
+
set :login_model, :character
|
39
|
+
|
40
|
+
register Padrino::Rendering
|
41
|
+
register Padrino::Helpers
|
42
|
+
register Padrino::Login
|
43
|
+
register Padrino::Access
|
44
|
+
enable :sessions
|
45
|
+
|
46
|
+
set_access :*, :allow => :index
|
47
|
+
set_access :humans, :allow => :restricted
|
48
|
+
|
49
|
+
get(:index){ 'index' }
|
50
|
+
get(:restricted){ 'secret' }
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
After rackup we have:
|
55
|
+
|
56
|
+
http://localhost:9292/ gets index
|
57
|
+
http://localhost:9292/restricted gets us redirected to '/login'
|
58
|
+
|
59
|
+
If we fill the form with 'leela' and her password 'TL' we get logged in and
|
60
|
+
redirected to '/restricted' location we tried to visit earlier, but Leela has
|
61
|
+
no access to it and we get 403 status.
|
62
|
+
|
63
|
+
http://localhost:9292/login now if we visit '/login' again and ented 'ami'
|
64
|
+
and his password 'AW' we can get to '/restricted' location.
|
65
|
+
|
66
|
+
http://localhost:9292/restricted now gets 'secret' content
|
67
|
+
|
68
|
+
### Authorization-only example
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# sketchup some database model
|
72
|
+
module Character
|
73
|
+
extend self
|
74
|
+
|
75
|
+
# db model must have authenticate method which should response with credentials object on
|
76
|
+
# the calls of { :email => 'a@b', :password => 'abc' } to authenticate by email and password
|
77
|
+
# or { :id => 42 } to restore credentials from id saved in session or another persistance storage
|
78
|
+
def authenticate(credentials)
|
79
|
+
case
|
80
|
+
when credentials[:email] && credentials[:password]
|
81
|
+
target = all.find{ |resource| resource.id.to_s == credentials[:email] }
|
82
|
+
target.name.gsub(/[^A-Z]/,'') == credentials[:password] ? target : nil
|
83
|
+
when credentials.has_key?(:id)
|
84
|
+
all.find{ |resource| resource.id == credentials[:id] }
|
85
|
+
else
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# example collection of users
|
91
|
+
def all
|
92
|
+
@all = [
|
93
|
+
OpenStruct.new(:id => :bender, :name => 'Bender Bending Rodriguez', :role => :robots ),
|
94
|
+
OpenStruct.new(:id => :leela, :name => 'Turanga Leela', :role => :mutants ),
|
95
|
+
OpenStruct.new(:id => :fry, :name => 'Philip J. Fry', :role => :humans ),
|
96
|
+
OpenStruct.new(:id => :ami, :name => 'Amy Wong', :role => :humans ),
|
97
|
+
OpenStruct.new(:id => :zoidberg, :name => 'Dr. John A. Zoidberg', :role => :lobsters),
|
98
|
+
]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module Example; end
|
103
|
+
# define an application class
|
104
|
+
class Example::App < Padrino::Application
|
105
|
+
register Padrino::Access
|
106
|
+
|
107
|
+
# authorization module has no built-in persistance storage, so we have to implement it:
|
108
|
+
enable :sessions
|
109
|
+
helpers do
|
110
|
+
def credentials
|
111
|
+
puts settings.permissions.inspect
|
112
|
+
@visitor ||= Character.authenticate(:id => session[:visitor_id])
|
113
|
+
end
|
114
|
+
def credentials=(user)
|
115
|
+
@visitor = user
|
116
|
+
session[:visitor_id] = @visitor ? @visitor.id : nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# simple authentication controller
|
121
|
+
get(:login, :with => :id) do
|
122
|
+
# this is an example, do not authenticate by user id in real apps
|
123
|
+
self.credentials = Character.authenticate(:id => params[:id].to_sym)
|
124
|
+
end
|
125
|
+
|
126
|
+
# allow everyone to visit '/login'
|
127
|
+
set_access :*, :allow => :login
|
128
|
+
|
129
|
+
# example action
|
130
|
+
get(:index){ 'foo' }
|
131
|
+
|
132
|
+
# robots are allowed to bend
|
133
|
+
set_access :robots, :allow => :bend
|
134
|
+
get(:bend){ 'bend' }
|
135
|
+
|
136
|
+
# humans and robots are allowed to live on surface
|
137
|
+
controller :surface do
|
138
|
+
set_access :humans, :robots
|
139
|
+
get(:live) { 'live on the surface' }
|
140
|
+
end
|
141
|
+
|
142
|
+
# mutants are allowed to live on surface, humans are allowed to visit
|
143
|
+
controller :sewers do
|
144
|
+
set_access :mutants
|
145
|
+
set_access :humans, :allow => :visit
|
146
|
+
get(:live) { 'live in the sewers' }
|
147
|
+
get(:visit) { 'visit the sewers' }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
That's it, rackup it and see what's up:
|
153
|
+
|
154
|
+
http://localhost:9292/ => 403 shows Forbidden
|
155
|
+
http://localhost:9292/login/leela => 200 logs Leela in
|
156
|
+
|
157
|
+
Now we can see that Leela is allowed to live in sewers:
|
158
|
+
|
159
|
+
http://localhost:9292/sewers/live => 200 live in sewers
|
160
|
+
|
161
|
+
Now check if robots can bend and humans to visit sewers:
|
162
|
+
|
163
|
+
http://localhost:9292/login/fry
|
164
|
+
http://localhost:9292/sewers/live
|
165
|
+
http://localhost:9292/sewers/visit
|
166
|
+
|
167
|
+
http://localhost:9292/login/bender
|
168
|
+
http://localhost:9292/bend
|
169
|
+
http://localhost:9292/stop_partying
|
170
|
+
|
171
|
+
### Authentication-only example
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
# sketchup some database model
|
175
|
+
module Character
|
176
|
+
extend self
|
177
|
+
|
178
|
+
# db model must have authenticate method which should response with credentials object on
|
179
|
+
# the calls of { :email => 'a@b', :password => 'abc' } to authenticate by email and password
|
180
|
+
# or { :id => 42 } to restore credentials from id saved in session or another persistance storage
|
181
|
+
def authenticate(credentials)
|
182
|
+
case
|
183
|
+
when credentials[:email] && credentials[:password]
|
184
|
+
target = all.find{ |resource| resource.id.to_s == credentials[:email] }
|
185
|
+
(target && target.name.gsub(/[^A-Z]/,'') == credentials[:password]) ? target : nil
|
186
|
+
when credentials.has_key?(:id)
|
187
|
+
all.find{ |resource| resource.id == credentials[:id] }
|
188
|
+
else
|
189
|
+
false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# example collection of users
|
194
|
+
def all
|
195
|
+
@all = [
|
196
|
+
OpenStruct.new(:id => :bender, :name => 'Bender Bending Rodriguez', :role => :robots ),
|
197
|
+
OpenStruct.new(:id => :leela, :name => 'Turanga Leela', :role => :mutants ),
|
198
|
+
OpenStruct.new(:id => :fry, :name => 'Philip J. Fry', :role => :humans ),
|
199
|
+
OpenStruct.new(:id => :ami, :name => 'Amy Wong', :role => :humans ),
|
200
|
+
OpenStruct.new(:id => :zoidberg, :name => 'Dr. John A. Zoidberg', :role => :lobsters),
|
201
|
+
]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
module Example; end
|
206
|
+
# define an application class
|
207
|
+
class Example::App < Padrino::Application
|
208
|
+
set :login_model, :character
|
209
|
+
|
210
|
+
register Padrino::Rendering
|
211
|
+
register Padrino::Helpers
|
212
|
+
register Padrino::Login
|
213
|
+
enable :sessions
|
214
|
+
|
215
|
+
get(:index){ 'index' }
|
216
|
+
get(:restricted){ 'secret' }
|
217
|
+
|
218
|
+
# if we plan to add custom authorization we need to define #authorized? helper
|
219
|
+
helpers do
|
220
|
+
def authorized?
|
221
|
+
restricted = ['/restricted'].include?(request.env['PATH_INFO'])
|
222
|
+
if credentials
|
223
|
+
case
|
224
|
+
when credentials.id == :bender
|
225
|
+
true
|
226
|
+
else
|
227
|
+
!restricted
|
228
|
+
end
|
229
|
+
else
|
230
|
+
!restricted
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
After rackup we have:
|
238
|
+
|
239
|
+
http://localhost:9292/ gets index
|
240
|
+
http://localhost:9292/restricted gets us redirected to '/login'
|
241
|
+
|
242
|
+
If we fill the form with 'leela' and her password 'TL' we get logged in and
|
243
|
+
redirected to '/restricted' location we tried to visit earlier, but Leela has
|
244
|
+
no access to it and we get 403 status.
|
245
|
+
|
246
|
+
http://localhost:9292/login now if we visit '/login' again and ented 'bender'
|
247
|
+
and his password 'BBR' we can get to '/restricted' location.
|
248
|
+
|
249
|
+
http://localhost:9292/restricted now gets 'secret' content
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Igor Bochkariov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
## Authorization and authentication modules for Padrino framework
|
2
|
+
|
3
|
+
### Overview
|
4
|
+
|
5
|
+
This padrino-auth provides the means to authenticate and authorize users.
|
6
|
+
These modules are designed to be independent but compatible with each other.
|
7
|
+
|
8
|
+
### Padrino::Login, an authentication module for Padrino
|
9
|
+
|
10
|
+
Authentication means identifying the user by comparing provided parameters
|
11
|
+
(usually login or email and password) with the credentials stored in the
|
12
|
+
application database and selecting the matched one. This module provides
|
13
|
+
a simple login form and related helpers methods including saving and
|
14
|
+
restoring user location.
|
15
|
+
|
16
|
+
#### Usage
|
17
|
+
|
18
|
+
##### Holding a session
|
19
|
+
|
20
|
+
Make sure you have sessions enabled: `enable :sessions`.
|
21
|
+
|
22
|
+
If you use a different persistence storage you will have to make #session hash
|
23
|
+
available to call in your app. `Padrino::Login` stores credentials `id` in
|
24
|
+
session[settings.session_key]. You can customize session_key by calling
|
25
|
+
`set :session_key, :current_credentials_id`. Default session_key name is
|
26
|
+
`"_login_#{app.app_name}"`.
|
27
|
+
|
28
|
+
##### Taming a model
|
29
|
+
|
30
|
+
By default `Padrino::Login` uses credentials model name `Account`. You can
|
31
|
+
customize it by setting `set :login_model, :user`.
|
32
|
+
|
33
|
+
Usually a set of credentials are stored in application database. To access
|
34
|
+
stored credentials `Padrino::Login` uses `Account.authenticate` class method.
|
35
|
+
The Account model must provide this class method in at least two forms:
|
36
|
+
authenticate with email/password, or with id. `Account.authenticate` is called
|
37
|
+
|
38
|
+
* with hash like `{ :email => 'user@example.com', :password => 'mypass' }`
|
39
|
+
to authenticate by email and password in response to user request
|
40
|
+
* with hash `{ :id => 42 }` to restore credentials from session
|
41
|
+
|
42
|
+
##### Accessing credentials (optional)
|
43
|
+
|
44
|
+
To be able to access current credentials of the signed user your app will
|
45
|
+
have to provide application helpers. By default `Padrino::Login` adds simple
|
46
|
+
reader and writer with names `credentials` and `credentials=(user)`. You can
|
47
|
+
customize helper name by setting `set :credentials_accessor, :visitor` and you
|
48
|
+
can override default accessor by defining your own reader and writer helpers
|
49
|
+
with the said names.
|
50
|
+
|
51
|
+
* the reader is called before checking if the user is signed in
|
52
|
+
* the writer is called after authenticating user's credentials or restoring
|
53
|
+
it from session
|
54
|
+
|
55
|
+
##### Bypassing authentication (optional)
|
56
|
+
|
57
|
+
By default this option is disabled. To enable it you can call
|
58
|
+
`enable :login_bypass`.
|
59
|
+
|
60
|
+
In development environment it sometimes is convenient to be able to bypass
|
61
|
+
authentication. If you do this you also have to extend your model
|
62
|
+
`Account.authenticate` class method to be able to return default credentials in
|
63
|
+
response to hash `{ :bypass => true }`. This way if the user authenticates with
|
64
|
+
parameter `bypass` she will be assigned the credentials returned by you model
|
65
|
+
and redirected to the stored location.
|
66
|
+
|
67
|
+
##### Customizing the login process (optional)
|
68
|
+
|
69
|
+
By default `Padrino::Login` registers a simple login controller for your app
|
70
|
+
and binds it to `/login`.
|
71
|
+
|
72
|
+
To customize this url it you can call `set :login_url, '/signin'`. Also it's
|
73
|
+
possible to disable registering the default controller by calling
|
74
|
+
`disable :login_controller`. If you do so you should provide a controller for
|
75
|
+
your custom login url which on request of authentication will call
|
76
|
+
`#authenticate` helper. If the result is true it should call
|
77
|
+
`#restore_location`, else it should show an error. Or you can do whatever you
|
78
|
+
like, it's your controller after all.
|
79
|
+
|
80
|
+
##### Synergizing with Padrino::Access and other things (info)
|
81
|
+
|
82
|
+
`Padrino::Login` tries to inform `Padrino::Access` that `/login` url should be
|
83
|
+
accessible for unauthenticated users by setting default
|
84
|
+
`set(:login_permissions) { set_access(:*, :allow => :*, :with => :login) }`.
|
85
|
+
Yes, it's a Proc and `Padrino::Access` tries to call it when registers itself.
|
86
|
+
|
87
|
+
If you name your controller another way you must redefine this. If you use
|
88
|
+
another authorization solution you also should configure it to allow visiting
|
89
|
+
`/login` url without having to authenticate.
|
90
|
+
|
91
|
+
##### Redirecting the user (info)
|
92
|
+
|
93
|
+
To authenticate users `Padrino::Login` defines Sinatra `before_filter` which
|
94
|
+
checks things and acts accordingly.
|
95
|
+
|
96
|
+
The first thing is if the user is already logged in. It uses the credentials
|
97
|
+
reader we mentioned before, or tries to restore credentials from session.
|
98
|
+
|
99
|
+
The second thing is if the user is authorized to look at the requested page.
|
100
|
+
To do so `Padrino::Login` calls `#authorized?` helper and checks it's bollean
|
101
|
+
result. If this helper does not exist then your app is considered not requiring
|
102
|
+
authorization. If it exists and responds with `true` then `before_filter`
|
103
|
+
passes. If the helper exists and returns `false` then the user's location
|
104
|
+
is saved and the user herself is redirected to login url.
|
105
|
+
|
106
|
+
`#authorized?` helper should be defined in your app if you want access control.
|
107
|
+
|
108
|
+
##### Finally registering
|
109
|
+
|
110
|
+
Call `register Padrino::Login` and you are ready to roll.
|
111
|
+
|
112
|
+
### Padrino::Access, an authorization module for Padrino
|
113
|
+
|
114
|
+
https://github.com/padrino/padrino-framework/wiki/Padrino-authorization-module
|
115
|
+
|
116
|
+
### Examples
|
117
|
+
|
118
|
+
[EXAMPLES.md](EXAMPLES.md)
|