authlane 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.md +22 -0
- data/README.md +38 -0
- data/Rakefile +26 -0
- data/authlane.gemspec +28 -0
- data/lib/authlane/helper.rb +191 -0
- data/lib/authlane/serializeduser.rb +85 -0
- data/lib/authlane/version.rb +6 -0
- data/lib/sinatra/authlane.rb +229 -0
- data/spec/authlane_helper_spec.rb +77 -0
- data/spec/authlane_serializeduser_spec.rb +38 -0
- data/spec/helper.rb +11 -0
- data/spec/sinatra_authlane_spec.rb +43 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5d433f29ea95649c1f3c82327cdf25585028d774
|
4
|
+
data.tar.gz: 4a2e2944a3a75b5d263ecbf74cbc55e3327df5e0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 070797815dead62c30f657efc56c5dfb3abdf8f0c792d68062469dcb67de9480907b204391a62b2a48bcf85e08f18e017b290fda9787bc0b3f6bd29bf762a280
|
7
|
+
data.tar.gz: f2191d60b1b6f4e4f6acdf7f393c9e9bbf375a7214edd0ff986f2183f4f7f513c9bb4e8508ddb73355b6491007c9ac41528a77dcbdc80e86b93bb02e0b12417c
|
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private --main=README.md --markup=markdown lib/**/*.rb
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014-2015 Patrick Lam
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# AuthLane
|
2
|
+
|
3
|
+
The **AuthLane** Sinatra Extension allows easy User authentication with support for different User *roles* and automatic login via Cookies. It exposes Helper methods to tell which routes are protected or involved in the authentication process.
|
4
|
+
|
5
|
+
The actual authentication logic (*strategy*) is defined by the Application using a namespaced DSL provided by this extension, while the general Extension configuration is handled with Sinatra's `set` method, which will be described in more detail below.
|
6
|
+
|
7
|
+
# Configuring *AuthLane*
|
8
|
+
|
9
|
+
**AuthLane**'s configuration data is available under Sinatra's `settings` object with the key `:authlane` as a Hash, so changing config values is simply done with Sinatra's `set` method.
|
10
|
+
|
11
|
+
```
|
12
|
+
set :authlane, :failed_route => '/login'
|
13
|
+
```
|
14
|
+
|
15
|
+
The following settings can be customize (the used values are their defaults):
|
16
|
+
|
17
|
+
```
|
18
|
+
set :authlane, :failed_route => '/user/unauthorized',
|
19
|
+
:session_key => :authlane,
|
20
|
+
:remember_cookie => :authlane_token,
|
21
|
+
:serialize_user => [:id]
|
22
|
+
```
|
23
|
+
|
24
|
+
### `:failed_route`
|
25
|
+
|
26
|
+
The `:failed_route` sets the route String, where AuthLane should redirect to in case a route requires authorisation and the User ist not logged in. It typically is the route to display the login form, but can be set to anything that is needed, as long the it is not protected by authorisation as well.
|
27
|
+
|
28
|
+
### `:seession_key`
|
29
|
+
|
30
|
+
The `:session_key` sets the name (as a Symbol) of the Session variable where User credentials of a logged in User are stored. The stored User data are wrapped inside a `SerializedUser` object and can be retrieved by using Sinatra's `session` helper and giving it the key that is defined here `session[:authlane]`. Alternatively, the AuthLane Helper exposes the method `current_user` to provide easy access to User data.
|
31
|
+
|
32
|
+
### `:remember_cookie`
|
33
|
+
|
34
|
+
Customize the Cookie's name that stores the token hash used for the *Remember Me* functionality. The setting (and creation) of the token needs to be implemented by the Extension user in both the Auth and Remember Strategy.
|
35
|
+
|
36
|
+
### `:serialize_user`
|
37
|
+
|
38
|
+
The `:serialized_user` settings contains an Array of Symbols telling AuthLane which attributes of the User model that is used to identify Application useres should be serialized into `SerializedUser`. It is recommended to not store the whole User object, but note that the *id* (or however the unique identifier of the object is named) attribute is required.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'authlane/version'
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
namespace :doc do
|
7
|
+
desc 'Generate YARD documentation database'
|
8
|
+
task :generate do
|
9
|
+
system 'rm -rf .yardoc'
|
10
|
+
system 'yardoc'
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Start the YARD documentation server'
|
14
|
+
task :srv do
|
15
|
+
system 'yard server --reload -- Kargo'
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Open generated YARD documentation website'
|
19
|
+
task :open do
|
20
|
+
system 'open doc/index.html'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec::Core::RakeTask.new(:spec)
|
25
|
+
|
26
|
+
Dir['tasks/*.rake'].sort.each { |ext| load ext }
|
data/authlane.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'authlane/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'authlane'
|
8
|
+
spec.version = Authlane::VERSION
|
9
|
+
spec.authors = ['Patrick Lam']
|
10
|
+
spec.email = ['zidizei@gmail.com']
|
11
|
+
spec.summary = 'Easy User authentication and roles for Sinatra.'
|
12
|
+
spec.description = 'The AuthLane Sinatra Extension allows easy User authentication with support for different User roles.'
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'sinatra'
|
22
|
+
spec.add_dependency 'sinatra-contrib'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.1'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 2.6'
|
27
|
+
spec.add_development_dependency 'yard'
|
28
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
|
2
|
+
module Sinatra
|
3
|
+
module AuthLane
|
4
|
+
##
|
5
|
+
# The **AuthLane** `Helpers` are helper methods that are made available to an application's
|
6
|
+
# route definitions. It is the main interface between a Sinatra Application and AuthLane
|
7
|
+
# and enables easy interaction with the authorization logic by calling a specific helper method.
|
8
|
+
#
|
9
|
+
module Helpers
|
10
|
+
##
|
11
|
+
# @note This method uses {#authorized?} to decide, whether to redirect users to `failed_route`.
|
12
|
+
# Check if a user is authorized to view a route.
|
13
|
+
#
|
14
|
+
# It utilizes the *Role* and *Remember Strategy* to see if a user can access the route this
|
15
|
+
# helper method is called from. The first query inside this method is to look for logged in
|
16
|
+
# user credentials in the Session. If this fails, *AuthLane* attempts to login the user
|
17
|
+
# via Cookie token by calling the *Remember Strategy* that is defined for the application. If it succeeds,
|
18
|
+
# this method will continue normally (as in the user was already logged in 'regularly') and
|
19
|
+
# use the *Role Strategy* to check user privileges for the route, in case there were any specified when
|
20
|
+
# it was being called.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# get '/account' do
|
24
|
+
# protect!
|
25
|
+
#
|
26
|
+
# mustache :account
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# get '/admin' do
|
30
|
+
# protect! roles: [:Admin],
|
31
|
+
# failed_route: '/account'
|
32
|
+
#
|
33
|
+
# mustache :admin
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @param [Array] roles A list of role/privilege requirements for a route, set to `nil` to ignore user roles
|
37
|
+
# @param [String] failed_route Custom route to redirect to in case the user is not authorized
|
38
|
+
#
|
39
|
+
# @see Sinatra::AuthLane.create_role_strategy create_role_strategy
|
40
|
+
# @see Sinatra::AuthLane.create_remember_strategy create_remember_strategy
|
41
|
+
#
|
42
|
+
def protect!(roles: nil, failed_route: nil)
|
43
|
+
# check for custom :failed_route option
|
44
|
+
failed_route ||= settings.authlane[:failed_route]
|
45
|
+
|
46
|
+
redirect failed_route unless authorized?(roles: roles)
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :protected, :protect!
|
50
|
+
|
51
|
+
##
|
52
|
+
# @note For more information about how this method works, refer to {#protect!}.
|
53
|
+
#
|
54
|
+
# Returns a `Boolean` depending on the user's authorization status. This can be useful if
|
55
|
+
# a route wants to handle unauthorized users differently than just redirecting them to the
|
56
|
+
# `:failed_route` setting.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# get '/' do
|
60
|
+
# if authorized?
|
61
|
+
# mustache :welcome
|
62
|
+
# else
|
63
|
+
# mustache :whoareyou
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# @param [Array] roles A list of role/privilege requirements for a route, set to `nil` to ignore user roles
|
68
|
+
#
|
69
|
+
# @return [Boolean] `true` if the user is authorized to view a route, `false` otherwise.
|
70
|
+
#
|
71
|
+
# @see Sinatra::AuthLane::Helpers#protect! protect!
|
72
|
+
#
|
73
|
+
def authorized?(roles: nil)
|
74
|
+
# So, if session[settings.authlane[:session_key]] is available
|
75
|
+
# we're home, otherwise, see if the 'Remember Me' strategy
|
76
|
+
# can come up with some User credentials.
|
77
|
+
if session[settings.authlane[:session_key]].nil?
|
78
|
+
remember_strat = self.instance_eval(&settings.authlane[:remember_strategy])
|
79
|
+
|
80
|
+
if remember_strat
|
81
|
+
user = Sinatra::AuthLane::SerializedUser.new(remember_strat, settings.authlane[:serialize_user])
|
82
|
+
|
83
|
+
# The strategy doesn't log in a User,
|
84
|
+
# it just comes up with the credentials to do that.
|
85
|
+
# The login actually happens right here.
|
86
|
+
session[settings.authlane[:session_key]] = user
|
87
|
+
end
|
88
|
+
else
|
89
|
+
user = session[settings.authlane[:session_key]]
|
90
|
+
end
|
91
|
+
|
92
|
+
if user.nil? || !user
|
93
|
+
# Ok, looks like the User needs to gtfo.
|
94
|
+
return false
|
95
|
+
else
|
96
|
+
# User is logged in ...
|
97
|
+
unless roles.nil?
|
98
|
+
# ... but hold up, he might not have the necessary
|
99
|
+
# privileges to access this particular route.
|
100
|
+
# Let's ask the 'Role' strategy.
|
101
|
+
strat = self.instance_exec roles, &settings.authlane[:role_strategy]
|
102
|
+
return false unless strat
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
return true
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Marks a route as the login route, that typically receives `POST` data from a `<form>`.
|
111
|
+
#
|
112
|
+
# The actual handling of the login logic and anything else the application
|
113
|
+
# requires is defined by creating the *Auth Strategy* using {Sinatra::AuthLane.create_auth_strategy create_auth_strategy}.
|
114
|
+
# the code block will be called within `authorize!` and - depending on the outcome (refer to
|
115
|
+
# {Sinatra::AuthLane.create_auth_strategy create_auth_strategy}'s documentation of its return value) - redirects
|
116
|
+
# to the `:failed_route` AuthLane setting or lets the user 'through' to access the route, which - in most cases - should
|
117
|
+
# be a redirect to the protected route.
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# get '/signin' do
|
121
|
+
# mustache :signin
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# post '/signin' do
|
125
|
+
# authorize!
|
126
|
+
#
|
127
|
+
# redirect '/account'
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# @return [void]
|
131
|
+
#
|
132
|
+
# @see Sinatra::AuthLane.create_auth_strategy create_auth_strategy
|
133
|
+
#
|
134
|
+
def authorize!
|
135
|
+
strat = self.instance_exec &settings.authlane[:auth_strategy]
|
136
|
+
|
137
|
+
unless strat
|
138
|
+
redirect settings.authlane[:failed_route], 303
|
139
|
+
end
|
140
|
+
|
141
|
+
session[settings.authlane[:session_key]] = Sinatra::AuthLane::SerializedUser.new(strat, settings.authlane[:serialize_user])
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Marks a route as the logout route.
|
146
|
+
#
|
147
|
+
# It resets the current Session and deletes any Session data along with it, particularly
|
148
|
+
# the *AuthLane* Session data. The *Forget Strategy* will be called here as well to
|
149
|
+
# 'counteract' the *Remember Me* functionality that is implemented in the *Remember* and
|
150
|
+
# *Auth Strategy*.
|
151
|
+
#
|
152
|
+
# @example
|
153
|
+
# get '/signout' do
|
154
|
+
# unauthorize!
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# @return [void]
|
158
|
+
#
|
159
|
+
# @see Sinatra::AuthLane.create_forget_strategy create_forget_strategy
|
160
|
+
#
|
161
|
+
def unauthorize!
|
162
|
+
token = cookies[settings.authlane[:remember_cookie]]
|
163
|
+
|
164
|
+
self.instance_exec(token, &settings.authlane[:forget_strategy]) unless token.nil?
|
165
|
+
|
166
|
+
# @private
|
167
|
+
cookies.delete(settings.authlane[:remember_cookie])
|
168
|
+
session.destroy
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Gets the credentials of the currently logged in user.
|
173
|
+
#
|
174
|
+
# @example
|
175
|
+
# get '/account' do
|
176
|
+
# authorized?
|
177
|
+
#
|
178
|
+
# @username = current_user.username
|
179
|
+
# @email = current_user[:email] # This works too (refer to SerializedUser for more info)
|
180
|
+
#
|
181
|
+
# mustache :account
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# @return [SerializedUser] the user data serialized into the Session.
|
185
|
+
#
|
186
|
+
def current_user
|
187
|
+
session[settings.authlane[:session_key]]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
module Sinatra
|
3
|
+
module AuthLane
|
4
|
+
##
|
5
|
+
# Storage class for logged in user credentials.
|
6
|
+
# Behaves like a Hash **and** an Object.
|
7
|
+
#
|
8
|
+
class SerializedUser
|
9
|
+
##
|
10
|
+
# Sets up the Object to be serialized.
|
11
|
+
#
|
12
|
+
# Receives an Object `user` and stores its
|
13
|
+
# attributes specified by `attributes` in a Hash.
|
14
|
+
# If `attributes` is empty, the whole object
|
15
|
+
# will be stored as-is.
|
16
|
+
#
|
17
|
+
# @param [Object] user The User object that needs to be serialized
|
18
|
+
# @param [Array<Symbol>] attributes A list of attribute names to be serialized from `user`
|
19
|
+
#
|
20
|
+
def initialize(user, attributes = [])
|
21
|
+
if attributes.size == 0
|
22
|
+
@user = user
|
23
|
+
else
|
24
|
+
@user = Hash.new
|
25
|
+
|
26
|
+
attributes.each do |attrs|
|
27
|
+
@user[attrs] = user.__send__(attrs.to_sym) if user.respond_to? attrs
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Access stored attributes like a Hash.
|
34
|
+
#
|
35
|
+
# If the whole Object was stored, it sends the
|
36
|
+
# Hash key `a` as a message to that Object.
|
37
|
+
#
|
38
|
+
# @param [String, Symbol] a The name of the serialized object's attribute to be read
|
39
|
+
#
|
40
|
+
def [](a)
|
41
|
+
(@user.is_a? Hash) ? @user[a] : @user.__send__(a.to_sym)
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Enables Object-like access to the
|
46
|
+
# stored attributes.
|
47
|
+
#
|
48
|
+
# If the whole Object was stored, it sends the
|
49
|
+
# method name `m` as a message to that Object.
|
50
|
+
# Otherwise it will access the Hash using `m` as
|
51
|
+
# the key.
|
52
|
+
#
|
53
|
+
# @param [Symbol] m The name of the serialized object's attribute to be read
|
54
|
+
#
|
55
|
+
def method_missing(m, *args, &block)
|
56
|
+
if @user.is_a? Hash
|
57
|
+
@user[m]
|
58
|
+
else
|
59
|
+
@user.__send__(m.to_sym, args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Return a Hash representing the serialized object's attributes and values.
|
65
|
+
#
|
66
|
+
# If the whole Object was stored its `to_h` will be called.
|
67
|
+
# In either case, attribute names can be accessed by Symbol or String
|
68
|
+
# key alike.
|
69
|
+
#
|
70
|
+
# @return [Hash] the Hash representation of the stored object.
|
71
|
+
#
|
72
|
+
def to_h
|
73
|
+
universal_hash = Hash.new
|
74
|
+
hash = @user.to_h
|
75
|
+
hash.each_pair do |key, value|
|
76
|
+
universal_hash[key.to_s] = value if key.is_a? Symbol
|
77
|
+
|
78
|
+
universal_hash[key] = value
|
79
|
+
end
|
80
|
+
|
81
|
+
universal_hash
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/cookies'
|
3
|
+
|
4
|
+
require 'authlane/version'
|
5
|
+
require 'authlane/serializeduser'
|
6
|
+
require 'authlane/helper'
|
7
|
+
|
8
|
+
module Sinatra
|
9
|
+
##
|
10
|
+
# The `AuthLane` Sinatra Extension allows easy User
|
11
|
+
# authentication with support for different User *roles*
|
12
|
+
# and automatic login via Cookies. It exposes {Sinatra::AuthLane::Helpers Helper}
|
13
|
+
# methods to tell which routes are protected or involved in the authentication process.
|
14
|
+
#
|
15
|
+
# The actual authentication logic (*strategy*) is defined by the Application using
|
16
|
+
# a namespaced DSL provided by this extension, while the general Extension configuration
|
17
|
+
# is handled with Sinatra's `set` method, which will be described in more detail below.
|
18
|
+
#
|
19
|
+
# # Configuring AuthLane
|
20
|
+
#
|
21
|
+
# **AuthLane**'s configuration data is available under Sinatra's `settings` object
|
22
|
+
# with the key `:authlane` as a Hash, so changing config values is simply done with
|
23
|
+
# Sinatra's `set` method.
|
24
|
+
#
|
25
|
+
# ```
|
26
|
+
# set :authlane, :failed_route => '/login'
|
27
|
+
# ```
|
28
|
+
#
|
29
|
+
# The following settings can be customize (the used values are their defaults):
|
30
|
+
#
|
31
|
+
# ```
|
32
|
+
# set :authlane, :failed_route => '/user/unauthorized',
|
33
|
+
# :session_key => :authlane,
|
34
|
+
# :remember_cookie => :authlane_token,
|
35
|
+
# :serialize_user => [:id]
|
36
|
+
# ```
|
37
|
+
#
|
38
|
+
# ## `:failed_route`
|
39
|
+
#
|
40
|
+
# The `:failed_route` sets the route String, where AuthLane should redirect to in case a
|
41
|
+
# route requires authorisation and the User ist not logged in. It typically is the route
|
42
|
+
# to display the login form, but can be set to anything that is needed, as long the it is
|
43
|
+
# not protected by authorisation as well.
|
44
|
+
#
|
45
|
+
# ## `:seession_key`
|
46
|
+
#
|
47
|
+
# The `:session_key` sets the name (as a Symbol) of the Session variable where User credentials of a logged in
|
48
|
+
# User are stored. The stored User data are wrapped inside a {Sinatra::AuthLane::SerializedUser SerializedUser}
|
49
|
+
# object and can be retrieved by using Sinatra's `session` helper and giving it the key that is defined here
|
50
|
+
# `session[:authlane]`. Alternatively, the AuthLane {Sinatra::AuthLane::Helpers Helper} exposes the method
|
51
|
+
# {Sinatra::AuthLane::Helpers#current_user current_user} to provide easy access to User data.
|
52
|
+
#
|
53
|
+
# ## `:remember_cookie`
|
54
|
+
#
|
55
|
+
# Customize the Cookie's name that stores the token hash used for the *Remember Me* functionality.
|
56
|
+
# The setting (and creation) of the token needs to be implemented by the Extension user in
|
57
|
+
# both the Auth and Remember Strategy.
|
58
|
+
#
|
59
|
+
# ## `:serialize_user`
|
60
|
+
#
|
61
|
+
# The `:serialized_user` settings contains an Array of Symbols telling AuthLane which attributes
|
62
|
+
# of the User model that is used to identify Application useres should
|
63
|
+
# be serialized into {Sinatra::AuthLane::SerializedUser SerializedUser}. It is recommended to not
|
64
|
+
# store the whole User object, but note that the *id* (or however the unique identifier
|
65
|
+
# of the object is named) attribute is required.
|
66
|
+
#
|
67
|
+
module AuthLane
|
68
|
+
##
|
69
|
+
# Initiates **AuthLane** when it is being registered with a Sinatra Application.
|
70
|
+
#
|
71
|
+
# Adds helper methods to App instance and sets the default
|
72
|
+
# settings described above.
|
73
|
+
#
|
74
|
+
# @todo Don't pass `app` to instance variable `@app`. Implement proper encapsulation of AuthLane.
|
75
|
+
#
|
76
|
+
# @api Sinatra
|
77
|
+
#
|
78
|
+
def self.registered(app)
|
79
|
+
app.helpers AuthLane::Helpers
|
80
|
+
|
81
|
+
@app = app
|
82
|
+
|
83
|
+
# default configuration
|
84
|
+
app.set :authlane,
|
85
|
+
:failed_route => '/user/unauthorized', # route to redirect to if the user is required to login
|
86
|
+
:session_key => :authlane, # name of the Session key to store the login data
|
87
|
+
:remember_cookie => :authlane_token, # Cookie name to store 'Remember Me' token
|
88
|
+
:auth_strategy => Proc.new { false }, # strategy to be executed to log in users
|
89
|
+
:role_strategy => Proc.new { true }, # strategy to be executed to check permissions and roles
|
90
|
+
:remember_strategy => Proc.new { false }, # strategy to be executed to log in users via 'Remember Me' token
|
91
|
+
:forget_strategy => Proc.new { false }, # strategy to be executed when logging out and 'forgetting' the user
|
92
|
+
:serialize_user => [:id] # specify User model fields to be serialized into the login session
|
93
|
+
end
|
94
|
+
|
95
|
+
class << self
|
96
|
+
##
|
97
|
+
# Create the **Auth** *Strategy* for AuthLane.
|
98
|
+
#
|
99
|
+
# Used from the Sinatra DSL context to define the **Auth** *Strategy*
|
100
|
+
# to be used by passing the implementation as a Code block. It is then
|
101
|
+
# stored as a `Proc` object and will be called by AuthLane when needed.
|
102
|
+
#
|
103
|
+
# The following describes the `Proc` objects API requirements. It is
|
104
|
+
# required by the implemented strategy to follow these to be usable by AuthLane.
|
105
|
+
#
|
106
|
+
# @note While the **Auth** Strategy is primarily responsible for logging in users,
|
107
|
+
# it usually needs to implement some *Remember Me* logic as well.
|
108
|
+
#
|
109
|
+
# @return [False, Object] The strategy needs to return the User Model object of
|
110
|
+
# the user that successfully logged in or - in case of failure - `false`.
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# Sinatra::AuthLane.create_auth_strategy do
|
114
|
+
# user = User.find_by_username(params[:username])
|
115
|
+
#
|
116
|
+
# (!user.nil? && user.password == params[:pass]) ? user : false
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# @see Sinatra::AuthLane::Helpers#authorize! authorize!
|
120
|
+
#
|
121
|
+
# @api AuthLane
|
122
|
+
#
|
123
|
+
def create_auth_strategy
|
124
|
+
@app.set :authlane, :auth_strategy => Proc.new
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Create the **Role** *Strategy* for AuthLane.
|
129
|
+
#
|
130
|
+
# Used from the Sinatra DSL context to define the **Role** *Strategy*
|
131
|
+
# to be used by passing the implementation as a Code block. It is then
|
132
|
+
# stored as a `Proc` object and will be called by AuthLane when needed.
|
133
|
+
#
|
134
|
+
# The following describes the `Proc` objects API requirements. It is
|
135
|
+
# required by the implemented strategy to follow these to be usable by AuthLane.
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# Sinatra::AuthLane.create_role_strategy do |roles|
|
139
|
+
# user = current_user # AuthLane helper to get the currently logged in user data
|
140
|
+
#
|
141
|
+
# roles.include? user.role # See if the list of role names in `roles` contains the user's role
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# @param [Array] roles The `Proc` receives a list of role names (e.g. as Symbols) a User needs to fullfill
|
145
|
+
# to be allowed to see the route. The list is passed by the {Sinatra::AuthLane::Helpers#authorized? authorized?}
|
146
|
+
# helper.
|
147
|
+
#
|
148
|
+
# @return [Boolean] The strategy returns `true`, if the user met the necessary role requirements or `false` otherwise.
|
149
|
+
#
|
150
|
+
# @see Sinatra::AuthLane::Helpers#authorized? authorized?
|
151
|
+
#
|
152
|
+
# @api AuthLane
|
153
|
+
#
|
154
|
+
def create_role_strategy
|
155
|
+
@app.set :authlane, :role_strategy => Proc.new
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Create the **Remember** *Strategy* for AuthLane.
|
160
|
+
#
|
161
|
+
# Used from the Sinatra DSL context to define the **Remember** *Strategy*
|
162
|
+
# to be used by passing the implementation as a Code block. It is then
|
163
|
+
# stored as a `Proc` object and will be called by AuthLane when needed.
|
164
|
+
#
|
165
|
+
# The following describes the `Proc` objects API requirements. It is
|
166
|
+
# required by the implemented strategy to follow these to be usable by AuthLane.
|
167
|
+
#
|
168
|
+
# @note The **Remember** Strategy is only responsible for automatically logging in a user.
|
169
|
+
# The necessary Cookie token (or whatever technique) is usually set in the **Auth** Strategy.
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
# Sinatra::AuthLane.create_remember_strategy do
|
173
|
+
# remember_token = cookies[:authlane_token]
|
174
|
+
# remembered_user = User.find_by_token(remember_token)
|
175
|
+
#
|
176
|
+
# (remembered_user.nil?) ? false : remembered_user
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# @return [False, Object] The strategy needs to return the User Model object of
|
180
|
+
# the user that successfully logged in or - in case of failure - `false`.
|
181
|
+
#
|
182
|
+
# @see Sinatra::AuthLane::Helpers#authorized? authorized?
|
183
|
+
#
|
184
|
+
# @api AuthLane
|
185
|
+
#
|
186
|
+
def create_remember_strategy
|
187
|
+
@app.set :authlane, :remember_strategy => Proc.new
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Create the **Forget** *Strategy* for AuthLane.
|
192
|
+
#
|
193
|
+
# Used from the Sinatra DSL context to define the **Forget** *Strategy*
|
194
|
+
# to be used by passing the implementation as a Code block. It is then
|
195
|
+
# stored as a `Proc` object and will be called by AuthLane when needed.
|
196
|
+
#
|
197
|
+
# The following describes the `Proc` objects API requirements. It is
|
198
|
+
# required by the implemented strategy to follow these to be usable by AuthLane.
|
199
|
+
#
|
200
|
+
# @note The **Forget** Strategy is the counter-part to the **Remember** Strategy.
|
201
|
+
# It's responsible for disabling the auto login technique and is called when logging out.
|
202
|
+
#
|
203
|
+
# @note While the *Auth* and *Remember Strategy* needs to interact with the Cookie token directly,
|
204
|
+
# the *Forget Strategy* does not need to implement the deletion of the Cookie.
|
205
|
+
# This is done automatically by AuthLane behind the scenes.
|
206
|
+
#
|
207
|
+
# @example
|
208
|
+
# Sinatra::AuthLane.create_forget_strategy do |token|
|
209
|
+
# user = User.find(current_user.id)
|
210
|
+
# user.token = nil if user.token == token
|
211
|
+
# end
|
212
|
+
#
|
213
|
+
# @param [Object] token The `Proc` object receives the *Remember Me* token of the current user.
|
214
|
+
#
|
215
|
+
# @see Sinatra::AuthLane::Helpers#unauthorize! unauthorize!
|
216
|
+
#
|
217
|
+
# @return [void]
|
218
|
+
#
|
219
|
+
# @api AuthLane
|
220
|
+
#
|
221
|
+
def create_forget_strategy
|
222
|
+
@app.set :authlane, :forget_strategy => Proc.new
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
helpers Sinatra::Cookies
|
228
|
+
register Sinatra::AuthLane
|
229
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.expand_path '../helper.rb', __FILE__
|
2
|
+
|
3
|
+
describe Sinatra::AuthLane::Helpers do
|
4
|
+
before :all do
|
5
|
+
mock_app do
|
6
|
+
helpers Sinatra::Cookies
|
7
|
+
register Sinatra::AuthLane
|
8
|
+
|
9
|
+
use Rack::Session::Cookie, :secret => 'rspec'
|
10
|
+
|
11
|
+
Sinatra::AuthLane.create_auth_strategy { { id: 1 } }
|
12
|
+
|
13
|
+
get '/protected' do
|
14
|
+
protect!
|
15
|
+
end
|
16
|
+
|
17
|
+
get '/authorized' do
|
18
|
+
if authorized?
|
19
|
+
'authorized'
|
20
|
+
else
|
21
|
+
'unauthorized'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/authorize' do
|
26
|
+
authorize!
|
27
|
+
protect!
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/unauthorize' do
|
31
|
+
unauthorize!
|
32
|
+
protect!
|
33
|
+
end
|
34
|
+
|
35
|
+
get '/user' do
|
36
|
+
protect!
|
37
|
+
user = current_user
|
38
|
+
user
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to recognize authorized states" do
|
44
|
+
get '/authorized', {}, { 'rack.session' => { authlane: { id: 1 } } }
|
45
|
+
expect(last_response.body).to eq('authorized')
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be able to recognize unauthorized states" do
|
49
|
+
get '/authorized'
|
50
|
+
expect(last_response.body).to eq('unauthorized')
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should redirect from protected route to signin page when not logged in" do
|
54
|
+
get '/protected'
|
55
|
+
expect(last_response.headers['location']).to eq('http://example.org/user/unauthorized')
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should display protected route when logged in" do
|
59
|
+
get '/protected', {}, { 'rack.session' => { authlane: '1' } }
|
60
|
+
last_response.should be_ok
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should authorize a User" do
|
64
|
+
get '/authorize'
|
65
|
+
last_response.should be_ok
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should unauthorize a User" do
|
69
|
+
get '/unauthorize', {}, { 'rack.session' => { authlane: '1' } }
|
70
|
+
expect(last_response.headers['location']).to eq('http://example.org/user/unauthorized')
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be able to get the current User's serialized credentials" do
|
74
|
+
get '/user', {}, { 'rack.session' => { authlane: '1' } }
|
75
|
+
expect(last_response.body).to eq('1')
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path '../helper.rb', __FILE__
|
2
|
+
|
3
|
+
describe Sinatra::AuthLane::SerializedUser do
|
4
|
+
def mock_user
|
5
|
+
Sinatra::AuthLane::SerializedUser.new({ id: 1, name: 'rspec' })
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'can be accessed like a Hash' do
|
9
|
+
user = mock_user
|
10
|
+
user.should respond_to(:[])
|
11
|
+
user[:id].should eql(1)
|
12
|
+
user[:name].should eql('rspec')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can be accessed like an Object' do
|
16
|
+
user = mock_user
|
17
|
+
user.should respond_to(:method_missing)
|
18
|
+
user.id.should eql(1)
|
19
|
+
user.name.should eql('rspec')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should return Hash of the serialized User object' do
|
23
|
+
user = mock_user
|
24
|
+
user.to_h.should be_a(Hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should return Hash of the serialized User object with String keys' do
|
28
|
+
user = mock_user.to_h
|
29
|
+
user['id'].should eql(1)
|
30
|
+
user['name'].should eql('rspec')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return Hash of the serialized User object with Symbol keys' do
|
34
|
+
user = mock_user.to_h
|
35
|
+
user[:id].should eql(1)
|
36
|
+
user[:name].should eql('rspec')
|
37
|
+
end
|
38
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path '../helper.rb', __FILE__
|
2
|
+
|
3
|
+
describe Sinatra::AuthLane do
|
4
|
+
def strategy(type, &block)
|
5
|
+
mock_app do
|
6
|
+
register Sinatra::AuthLane
|
7
|
+
|
8
|
+
Sinatra::AuthLane.send "create_#{type}_strategy", &block
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should allow definition of auth strategy" do
|
13
|
+
strategy(:auth) do
|
14
|
+
'rspec'
|
15
|
+
end
|
16
|
+
|
17
|
+
settings.authlane[:auth_strategy].yield.should == 'rspec'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow definition of role strategy" do
|
21
|
+
strategy(:role) do
|
22
|
+
'rspec'
|
23
|
+
end
|
24
|
+
|
25
|
+
settings.authlane[:role_strategy].yield.should == 'rspec'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should allow definition of remember strategy" do
|
29
|
+
strategy(:remember) do
|
30
|
+
'rspec'
|
31
|
+
end
|
32
|
+
|
33
|
+
settings.authlane[:remember_strategy].yield.should == 'rspec'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should allow definition of forget strategy" do
|
37
|
+
strategy(:forget) do
|
38
|
+
'rspec'
|
39
|
+
end
|
40
|
+
|
41
|
+
settings.authlane[:forget_strategy].yield.should == 'rspec'
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: authlane
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrick Lam
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra-contrib
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: yard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: The AuthLane Sinatra Extension allows easy User authentication with support
|
98
|
+
for different User roles.
|
99
|
+
email:
|
100
|
+
- zidizei@gmail.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- ".yardopts"
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE.md
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- authlane.gemspec
|
112
|
+
- lib/authlane/helper.rb
|
113
|
+
- lib/authlane/serializeduser.rb
|
114
|
+
- lib/authlane/version.rb
|
115
|
+
- lib/sinatra/authlane.rb
|
116
|
+
- spec/authlane_helper_spec.rb
|
117
|
+
- spec/authlane_serializeduser_spec.rb
|
118
|
+
- spec/helper.rb
|
119
|
+
- spec/sinatra_authlane_spec.rb
|
120
|
+
homepage: ''
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.4.1
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Easy User authentication and roles for Sinatra.
|
144
|
+
test_files:
|
145
|
+
- spec/authlane_helper_spec.rb
|
146
|
+
- spec/authlane_serializeduser_spec.rb
|
147
|
+
- spec/helper.rb
|
148
|
+
- spec/sinatra_authlane_spec.rb
|
149
|
+
has_rdoc:
|