shield 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +19 -0
- data/README.markdown +5 -0
- data/README.rb +73 -0
- data/Rakefile +6 -0
- data/lib/shield.rb +18 -0
- data/lib/shield/helpers.rb +22 -0
- data/lib/shield/login.rb +38 -0
- data/lib/shield/password.rb +24 -0
- data/lib/shield/template/basic_user.rb +37 -0
- data/lib/shield/template/flexi_user.rb +22 -0
- data/lib/shield/template/user.rb +32 -0
- data/test/basic_user_test.rb +37 -0
- data/test/flexi_user_test.rb +40 -0
- data/test/helper.rb +25 -0
- data/test/login_middleware_test.rb +59 -0
- data/test/login_rack_mounting_test.rb +63 -0
- data/test/mounted_middleware_test.rb +96 -0
- data/test/password_hash_test.rb +23 -0
- data/test/shield_test.rb +1 -0
- data/test/sinatra_test.rb +73 -0
- data/test/user_test.rb +33 -0
- metadata +179 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Michel Martens, Damian Janowski and Cyril David
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
data/README.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
## Shield
|
2
|
+
|
3
|
+
# A simple authentication framework for use with a basic Rack application.
|
4
|
+
# It doesn't try to be generic and instead has the following assertions:
|
5
|
+
|
6
|
+
# 1. You use [Ohm][ohm] for your persistence layer.
|
7
|
+
# 2. That your app has some form of a Helper plugin system.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
# The fastest way to get started with `Shield` is to use it as a [Sinatra][sin]
|
12
|
+
# extension on top of your main application.
|
13
|
+
|
14
|
+
# We simply have to require sinatra, shield, and [Ohm][ohm].
|
15
|
+
require "sinatra/base"
|
16
|
+
require "shield"
|
17
|
+
require "ohm"
|
18
|
+
|
19
|
+
# We then define a `User` class on the top-level namespace:
|
20
|
+
class User < Shield::User
|
21
|
+
end
|
22
|
+
|
23
|
+
# Our sinatra application can be a classic style, but for this example,
|
24
|
+
# we'll use the more modular style extending from `Sinatra::Base`.
|
25
|
+
class App < Sinatra::Base
|
26
|
+
# One very very important detail to remember is that you need to
|
27
|
+
# have session support for all of Shield to work.
|
28
|
+
enable :sessions
|
29
|
+
|
30
|
+
# Now for the main highlight: This line simply does two things:
|
31
|
+
#
|
32
|
+
# 1. It adds `Shield::Helpers` to your Sinatra app's helpers.
|
33
|
+
# 2. It adds `Shield::Login` as a middleware for your application.
|
34
|
+
#
|
35
|
+
register Shield
|
36
|
+
|
37
|
+
# This is a normal Sinatra route. No authentication is needed here.
|
38
|
+
get "/public" do
|
39
|
+
"Public"
|
40
|
+
end
|
41
|
+
|
42
|
+
# This is a private Sinatra route, which we enforce using
|
43
|
+
# `ensure_authenticated`. This method comes from `Shield::Helpers` along
|
44
|
+
# with some other helper methods:
|
45
|
+
#
|
46
|
+
# 1. `current_user` - ain't this the standard nowadays?
|
47
|
+
# 2. `logged_in?` - simple sugar to verify if the user is logged in.
|
48
|
+
# 3. `ensure_authenticated` - as shown here.
|
49
|
+
# 4. `redirect_to_stored` - redirects to the previous url the user was
|
50
|
+
# trying to access prior to authenticating.
|
51
|
+
get "/private" do
|
52
|
+
ensure_authenticated
|
53
|
+
|
54
|
+
"Private"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# If this file was run directly i.e. ruby README.rb.
|
59
|
+
if __FILE__ == $0
|
60
|
+
# For the purposes of this quick script, let's create a User. You'll use that
|
61
|
+
# to quickly verify that you can indeed login to the application.
|
62
|
+
if User.all.size == 0
|
63
|
+
User.create(:email => "quentin@test.com",
|
64
|
+
:password => "password",
|
65
|
+
:password_confirmation => "password")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Let Sinatra take the stage.
|
69
|
+
App.run!
|
70
|
+
end
|
71
|
+
|
72
|
+
# [ohm]: http://ohm.keyvalue.org
|
73
|
+
# [sin]: http://sinatrarb.com
|
data/Rakefile
ADDED
data/lib/shield.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Shield
|
2
|
+
VERSION = "0.0.0"
|
3
|
+
|
4
|
+
autoload :BasicUser, "shield/template/basic_user"
|
5
|
+
autoload :User, "shield/template/user"
|
6
|
+
autoload :FlexiUser, "shield/template/flexi_user"
|
7
|
+
autoload :Password, "shield/password"
|
8
|
+
autoload :Login, "shield/login"
|
9
|
+
autoload :Helpers, "shield/helpers"
|
10
|
+
|
11
|
+
def self.registered(app)
|
12
|
+
app.helpers Helpers
|
13
|
+
|
14
|
+
app.use Login do |m|
|
15
|
+
m.settings.set :views, app.views if app.views
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Shield
|
2
|
+
module Helpers
|
3
|
+
def ensure_authenticated
|
4
|
+
return if logged_in?
|
5
|
+
|
6
|
+
session[:return_to] = request.fullpath
|
7
|
+
redirect "/login"
|
8
|
+
end
|
9
|
+
|
10
|
+
def logged_in?
|
11
|
+
!! current_user
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_user
|
15
|
+
@_current_user ||= ::User[session[:user]]
|
16
|
+
end
|
17
|
+
|
18
|
+
def redirect_to_stored(default = "/")
|
19
|
+
redirect(session.delete(:return_to) || default)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/shield/login.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "sinatra/base"
|
2
|
+
|
3
|
+
module Shield
|
4
|
+
class Login < Sinatra::Base
|
5
|
+
enable :sessions
|
6
|
+
helpers Helpers
|
7
|
+
|
8
|
+
set :views, File.join(File.dirname(__FILE__), "..", "..", "views")
|
9
|
+
|
10
|
+
set :auth_success_message, "You have successfully logged in."
|
11
|
+
set :auth_failure_message, "Wrong Username and/or Password combination."
|
12
|
+
|
13
|
+
get "/login/?" do
|
14
|
+
haml :login
|
15
|
+
end
|
16
|
+
|
17
|
+
post "/login/?" do
|
18
|
+
user = ::User.authenticate(params[:login], params[:password])
|
19
|
+
|
20
|
+
if user
|
21
|
+
session[:success] = settings.auth_success_message
|
22
|
+
session[:user] = user.id
|
23
|
+
|
24
|
+
redirect_to_stored
|
25
|
+
else
|
26
|
+
session[:error] = settings.auth_failure_message
|
27
|
+
redirect "/login"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
get "/logout/?" do
|
32
|
+
session.delete(:user)
|
33
|
+
session.delete(:return_to)
|
34
|
+
|
35
|
+
redirect "/"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "digest/sha2"
|
2
|
+
|
3
|
+
module Shield
|
4
|
+
module Password
|
5
|
+
def self.encrypt(password, salt = generate_salt)
|
6
|
+
digest(password, salt) + salt
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.check(password, encrypted)
|
10
|
+
sha512, salt = encrypted.to_s[0..127], encrypted.to_s[128..-1]
|
11
|
+
|
12
|
+
digest(password, salt) == sha512
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def self.digest(password, salt)
|
17
|
+
Digest::SHA512.hexdigest("#{ password }#{ salt }")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.generate_salt
|
21
|
+
Digest::SHA512.hexdigest(Time.now.to_f.to_s)[0, 64]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Shield
|
2
|
+
class BasicUser < Ohm::Model
|
3
|
+
Unimplemented = Class.new(StandardError)
|
4
|
+
|
5
|
+
def self.inherited(model)
|
6
|
+
model.attribute :crypted_password
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO : change this if Ohm decides on implementing subclassing.
|
10
|
+
def self._copy_attributes_indices_counters(model)
|
11
|
+
attributes.each { |att| model.attribute(att) }
|
12
|
+
indices.each { |att| model.index(att) }
|
13
|
+
counters.each { |att| model.counter(att) }
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :password, :password_confirmation
|
17
|
+
attr_writer :password_confirmation
|
18
|
+
|
19
|
+
def self.authenticate(login, password)
|
20
|
+
user = find_by_login(login)
|
21
|
+
|
22
|
+
if user && Shield::Password.check(password, user.crypted_password)
|
23
|
+
return user
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.find_by_login(login)
|
28
|
+
raise Unimplemented
|
29
|
+
end
|
30
|
+
|
31
|
+
def password=(password)
|
32
|
+
write_local :crypted_password, Shield::Password.encrypt(password)
|
33
|
+
|
34
|
+
@password = password
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Shield
|
2
|
+
class FlexiUser < User
|
3
|
+
def self.inherited(model)
|
4
|
+
model.attribute :username
|
5
|
+
model.index :username
|
6
|
+
|
7
|
+
_copy_attributes_indices_counters(model)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.find_by_login(login)
|
11
|
+
super or find(:username => login).first
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
super
|
16
|
+
|
17
|
+
assert_present(:username) &&
|
18
|
+
assert_format(:username, /\A[a-z][a-z0-9\-\_\.]{2,}\z/) &&
|
19
|
+
assert_unique(:username)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "ohm/contrib"
|
2
|
+
|
3
|
+
module Shield
|
4
|
+
class User < BasicUser
|
5
|
+
include Ohm::WebValidations
|
6
|
+
|
7
|
+
def self.inherited(model)
|
8
|
+
model.attribute :email
|
9
|
+
model.index :email
|
10
|
+
|
11
|
+
_copy_attributes_indices_counters(model)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.find_by_login(login)
|
15
|
+
find(:email => login).first
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate
|
19
|
+
super
|
20
|
+
|
21
|
+
assert_present(:email) && assert_email(:email) && assert_unique(:email)
|
22
|
+
|
23
|
+
if new?
|
24
|
+
assert_present :password
|
25
|
+
end
|
26
|
+
|
27
|
+
unless password.to_s.empty?
|
28
|
+
assert password == password_confirmation, [:password, :not_confirmed]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class BasicUser < Shield::BasicUser
|
4
|
+
end
|
5
|
+
|
6
|
+
setup do
|
7
|
+
BasicUser.create(:password => "password")
|
8
|
+
end
|
9
|
+
|
10
|
+
test "allows password checks at the minimum" do |u|
|
11
|
+
assert Shield::Password.check("password", u.crypted_password)
|
12
|
+
end
|
13
|
+
|
14
|
+
test "no find_by_login" do
|
15
|
+
assert_raise Shield::BasicUser::Unimplemented do
|
16
|
+
BasicUser.authenticate("quentin", "password")
|
17
|
+
end
|
18
|
+
|
19
|
+
assert_raise Shield::BasicUser::Unimplemented do
|
20
|
+
BasicUser.find_by_login("quentin")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
test "has password confirmation accesors" do |u|
|
25
|
+
u.password_confirmation = "pass"
|
26
|
+
|
27
|
+
assert "pass" == u.password_confirmation
|
28
|
+
end
|
29
|
+
|
30
|
+
test "writes the new crypted password on set" do
|
31
|
+
u = BasicUser.new(:password => "mypass")
|
32
|
+
assert Shield::Password.check("mypass", u.crypted_password)
|
33
|
+
|
34
|
+
u.save
|
35
|
+
u = BasicUser[u.id]
|
36
|
+
assert Shield::Password.check("mypass", u.crypted_password)
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class FlexiUser < Shield::FlexiUser
|
4
|
+
end
|
5
|
+
|
6
|
+
setup do
|
7
|
+
FlexiUser.create(:email => "quentin@test.com",
|
8
|
+
:username => "quentin",
|
9
|
+
:password => "password",
|
10
|
+
:password_confirmation => "password")
|
11
|
+
end
|
12
|
+
|
13
|
+
test "find_by_login" do |u|
|
14
|
+
assert u == FlexiUser.find_by_login("quentin@test.com")
|
15
|
+
assert u == FlexiUser.find_by_login("quentin")
|
16
|
+
end
|
17
|
+
|
18
|
+
test "authenticate" do |u|
|
19
|
+
assert u == FlexiUser.authenticate("quentin", "password")
|
20
|
+
assert u == FlexiUser.authenticate("quentin@test.com", "password")
|
21
|
+
end
|
22
|
+
|
23
|
+
test "username validation" do |u|
|
24
|
+
u.username = nil
|
25
|
+
assert ! u.valid?
|
26
|
+
assert u.errors.include?([:username, :not_present])
|
27
|
+
|
28
|
+
["1foo", "fo", "foo^", "foo&", "foo#"].each do |username|
|
29
|
+
u.username = username
|
30
|
+
assert ! u.valid?
|
31
|
+
assert u.errors.include?([:username, :format])
|
32
|
+
end
|
33
|
+
|
34
|
+
u.username = "foo"
|
35
|
+
assert u.valid?
|
36
|
+
|
37
|
+
newuser = FlexiUser.new(:username => "quentin")
|
38
|
+
assert ! newuser.valid?
|
39
|
+
assert newuser.errors.include?([:username, :not_unique])
|
40
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require "shield"
|
4
|
+
require "cutest"
|
5
|
+
require "rack/test"
|
6
|
+
require "sinatra/base"
|
7
|
+
require "nokogiri"
|
8
|
+
require "haml"
|
9
|
+
require "ohm"
|
10
|
+
require "ohm/contrib"
|
11
|
+
|
12
|
+
prepare { Ohm.flush }
|
13
|
+
|
14
|
+
class Cutest::Scope
|
15
|
+
include Rack::Test::Methods
|
16
|
+
|
17
|
+
def assert_redirected_to(path)
|
18
|
+
assert 302 == last_response.status
|
19
|
+
assert path == last_response.headers["Location"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def session
|
23
|
+
last_request.env["rack.session"]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class User < Shield::User
|
4
|
+
end
|
5
|
+
|
6
|
+
class Main < Sinatra::Base
|
7
|
+
enable :sessions
|
8
|
+
helpers Shield::Helpers
|
9
|
+
|
10
|
+
get "/public" do
|
11
|
+
"Public"
|
12
|
+
end
|
13
|
+
|
14
|
+
get "/private" do
|
15
|
+
ensure_authenticated
|
16
|
+
|
17
|
+
"Private"
|
18
|
+
end
|
19
|
+
|
20
|
+
use Shield::Login do |login|
|
21
|
+
login.settings.auth_success_message = "Booya!"
|
22
|
+
login.settings.auth_failure_message = "BOOM!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
scope do
|
27
|
+
def app
|
28
|
+
Main.new
|
29
|
+
end
|
30
|
+
|
31
|
+
setup do
|
32
|
+
clear_cookies
|
33
|
+
|
34
|
+
User.create(:email => "quentin@test.com",
|
35
|
+
:password => "password",
|
36
|
+
:password_confirmation => "password")
|
37
|
+
end
|
38
|
+
|
39
|
+
test "public" do
|
40
|
+
get "/public"
|
41
|
+
assert "Public" == last_response.body
|
42
|
+
end
|
43
|
+
|
44
|
+
test "logging in" do
|
45
|
+
post "/login", :login => "quentin@test.com", :password => "password"
|
46
|
+
|
47
|
+
get "/private"
|
48
|
+
assert "Private" == last_response.body
|
49
|
+
assert "Booya!" == session[:success]
|
50
|
+
end
|
51
|
+
|
52
|
+
test "login, logout, login failure" do
|
53
|
+
post "/login", :login => "quentin@test.com", :password => "password"
|
54
|
+
get "/logout"
|
55
|
+
|
56
|
+
post "/login"
|
57
|
+
assert "BOOM!" == session[:error]
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class User < Shield::User
|
4
|
+
end
|
5
|
+
|
6
|
+
class Main < Sinatra::Base
|
7
|
+
enable :sessions
|
8
|
+
helpers Shield::Helpers
|
9
|
+
|
10
|
+
get "/public" do
|
11
|
+
"Public"
|
12
|
+
end
|
13
|
+
|
14
|
+
get "/private" do
|
15
|
+
ensure_authenticated
|
16
|
+
|
17
|
+
"Private"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
$app = Rack::Builder.app {
|
22
|
+
use Shield::Login
|
23
|
+
|
24
|
+
map "/" do
|
25
|
+
run Main
|
26
|
+
end
|
27
|
+
}
|
28
|
+
|
29
|
+
scope do
|
30
|
+
def app
|
31
|
+
$app
|
32
|
+
end
|
33
|
+
|
34
|
+
setup do
|
35
|
+
clear_cookies
|
36
|
+
|
37
|
+
User.create(:email => "quentin@test.com",
|
38
|
+
:password => "password",
|
39
|
+
:password_confirmation => "password")
|
40
|
+
end
|
41
|
+
|
42
|
+
test "public" do
|
43
|
+
get "/public"
|
44
|
+
|
45
|
+
assert "Public" == last_response.body
|
46
|
+
end
|
47
|
+
|
48
|
+
test "logging in" do
|
49
|
+
post "/login", :login => "quentin@test.com",
|
50
|
+
:password => "password"
|
51
|
+
|
52
|
+
get "/private"
|
53
|
+
assert "Private" == last_response.body
|
54
|
+
end
|
55
|
+
|
56
|
+
test "being redirected and then logging in" do
|
57
|
+
get "/private"
|
58
|
+
assert_redirected_to "/login"
|
59
|
+
|
60
|
+
post "/login", :login => "quentin@test.com", :password => "password"
|
61
|
+
assert_redirected_to "/private"
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class User < Shield::User
|
4
|
+
end
|
5
|
+
|
6
|
+
class App < Sinatra::Base
|
7
|
+
enable :sessions
|
8
|
+
|
9
|
+
helpers Shield::Helpers
|
10
|
+
end
|
11
|
+
|
12
|
+
class Main < App
|
13
|
+
get "/public" do
|
14
|
+
"Public"
|
15
|
+
end
|
16
|
+
|
17
|
+
get "/private" do
|
18
|
+
ensure_authenticated
|
19
|
+
|
20
|
+
"Private"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Admin < App
|
25
|
+
before do
|
26
|
+
ensure_authenticated unless request.fullpath == "/admin/login"
|
27
|
+
end
|
28
|
+
|
29
|
+
get "/events" do
|
30
|
+
"Events"
|
31
|
+
end
|
32
|
+
|
33
|
+
get "/sponsors" do
|
34
|
+
"Sponsors"
|
35
|
+
end
|
36
|
+
|
37
|
+
post "/login" do
|
38
|
+
user = User.authenticate(params[:login], params[:password])
|
39
|
+
|
40
|
+
if user
|
41
|
+
session[:success] = "Success"
|
42
|
+
session[:user] = user.id
|
43
|
+
|
44
|
+
redirect_to_stored
|
45
|
+
else
|
46
|
+
session[:error] = "Failure"
|
47
|
+
redirect "/login"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
$app = Rack::Builder.app {
|
53
|
+
map "/" do
|
54
|
+
run Main
|
55
|
+
end
|
56
|
+
|
57
|
+
map "/admin" do
|
58
|
+
run Admin
|
59
|
+
end
|
60
|
+
}
|
61
|
+
|
62
|
+
scope do
|
63
|
+
def app
|
64
|
+
$app
|
65
|
+
end
|
66
|
+
|
67
|
+
setup do
|
68
|
+
User.create(:email => "quentin@test.com",
|
69
|
+
:password => "password",
|
70
|
+
:password_confirmation => "password")
|
71
|
+
end
|
72
|
+
|
73
|
+
test "public" do
|
74
|
+
get "/public"
|
75
|
+
|
76
|
+
assert "Public" == last_response.body
|
77
|
+
end
|
78
|
+
|
79
|
+
test "all admin routes" do
|
80
|
+
get "/admin/events"
|
81
|
+
assert_redirected_to "/login"
|
82
|
+
assert "/admin/events" == session[:return_to]
|
83
|
+
|
84
|
+
get "/admin/sponsors"
|
85
|
+
assert_redirected_to "/login"
|
86
|
+
assert "/admin/sponsors" == session[:return_to]
|
87
|
+
end
|
88
|
+
|
89
|
+
test "single sign on" do
|
90
|
+
post "/admin/login", :login => "quentin@test.com",
|
91
|
+
:password => "password"
|
92
|
+
|
93
|
+
get "/private"
|
94
|
+
assert "Private" == last_response.body
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
test "encrypt" do
|
4
|
+
encrypted = Shield::Password.encrypt("password")
|
5
|
+
assert Shield::Password.check("password", encrypted)
|
6
|
+
end
|
7
|
+
|
8
|
+
test "with custom 64 character salt" do
|
9
|
+
encrypted = Shield::Password.encrypt("password", "A" * 64)
|
10
|
+
assert Shield::Password.check("password", encrypted)
|
11
|
+
end
|
12
|
+
|
13
|
+
test "nil password doesn't raise" do
|
14
|
+
ex = nil
|
15
|
+
|
16
|
+
begin
|
17
|
+
encrypted = Shield::Password.encrypt(nil)
|
18
|
+
rescue Exception => e
|
19
|
+
ex = e
|
20
|
+
end
|
21
|
+
|
22
|
+
assert nil == ex
|
23
|
+
end
|
data/test/shield_test.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class User < Shield::User
|
4
|
+
end
|
5
|
+
|
6
|
+
class SinatraApp < Sinatra::Base
|
7
|
+
enable :sessions
|
8
|
+
register Shield
|
9
|
+
|
10
|
+
get "/public" do
|
11
|
+
"Public"
|
12
|
+
end
|
13
|
+
|
14
|
+
get "/private" do
|
15
|
+
ensure_authenticated
|
16
|
+
|
17
|
+
"Private"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
scope do
|
22
|
+
def app
|
23
|
+
SinatraApp.new
|
24
|
+
end
|
25
|
+
|
26
|
+
setup do
|
27
|
+
User.create(:email => "quentin@test.com",
|
28
|
+
:password => "password",
|
29
|
+
:password_confirmation => "password")
|
30
|
+
end
|
31
|
+
|
32
|
+
test "public" do
|
33
|
+
get "/public"
|
34
|
+
assert "Public" == last_response.body
|
35
|
+
end
|
36
|
+
|
37
|
+
test "private" do
|
38
|
+
get "/private"
|
39
|
+
assert_redirected_to "/login"
|
40
|
+
assert "/private" == session[:return_to]
|
41
|
+
|
42
|
+
post "/login", :login => "quentin@test.com", :password => "password"
|
43
|
+
assert_redirected_to "/private"
|
44
|
+
end
|
45
|
+
|
46
|
+
test "GET /login response" do
|
47
|
+
get "/login"
|
48
|
+
|
49
|
+
doc = Nokogiri(%{<div>#{last_response.body}</div>})
|
50
|
+
|
51
|
+
assert 2 == doc.search("form > fieldset > label > input").size
|
52
|
+
assert 1 == doc.search("form > fieldset > button").size
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class LoginCustomized < Sinatra::Base
|
57
|
+
enable :sessions
|
58
|
+
set :views, File.join(File.dirname(__FILE__), "fixtures", "views")
|
59
|
+
|
60
|
+
register Shield
|
61
|
+
end
|
62
|
+
|
63
|
+
scope do
|
64
|
+
def app
|
65
|
+
LoginCustomized.new
|
66
|
+
end
|
67
|
+
|
68
|
+
test "login response" do
|
69
|
+
get "/login"
|
70
|
+
|
71
|
+
assert "<h1>Login</h1>" == last_response.body.strip
|
72
|
+
end
|
73
|
+
end
|
data/test/user_test.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class User < Shield::User
|
4
|
+
end
|
5
|
+
|
6
|
+
setup do
|
7
|
+
User.create(:email => "quentin@email.com", :password => "password",
|
8
|
+
:password_confirmation => "password")
|
9
|
+
end
|
10
|
+
|
11
|
+
test "authentication default" do |u|
|
12
|
+
assert u == User.authenticate("quentin@email.com", "password")
|
13
|
+
|
14
|
+
assert nil == User.authenticate("quentin@email.com", "pass")
|
15
|
+
assert nil == User.authenticate("quentin@email.co.uk", "password")
|
16
|
+
end
|
17
|
+
|
18
|
+
test "email validation" do |u|
|
19
|
+
u.email = nil
|
20
|
+
assert ! u.valid?
|
21
|
+
assert u.errors.include?([:email, :not_present])
|
22
|
+
|
23
|
+
u.email = "foobar"
|
24
|
+
assert ! u.valid?
|
25
|
+
assert u.errors.include?([:email, :not_email])
|
26
|
+
|
27
|
+
u.email = "foo@bar.com"
|
28
|
+
u.save
|
29
|
+
|
30
|
+
foo = User.new(:email => "foo@bar.com")
|
31
|
+
assert ! foo.valid?
|
32
|
+
assert foo.errors.include?([:email, :not_unique])
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shield
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 0.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Michel Martens
|
13
|
+
- Damian Janowski
|
14
|
+
- Cyril David
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-10-15 00:00:00 +08:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: cutest
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: sinatra
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: haml
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :development
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rack-test
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
type: :development
|
73
|
+
version_requirements: *id004
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: ohm
|
76
|
+
prerelease: false
|
77
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
type: :development
|
86
|
+
version_requirements: *id005
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: ohm-contrib
|
89
|
+
prerelease: false
|
90
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
type: :development
|
99
|
+
version_requirements: *id006
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: nokogiri
|
102
|
+
prerelease: false
|
103
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
type: :development
|
112
|
+
version_requirements: *id007
|
113
|
+
description: "\n Gets you 80-90% of the way regarding your authentication\n requirements. Provides convenience helper functions which you can\n use with your favorite web framework of choice.\n "
|
114
|
+
email:
|
115
|
+
- michel@soveran.com
|
116
|
+
- djanowski@dimaion.com
|
117
|
+
- cyx@pipetodevnull.com
|
118
|
+
executables: []
|
119
|
+
|
120
|
+
extensions: []
|
121
|
+
|
122
|
+
extra_rdoc_files: []
|
123
|
+
|
124
|
+
files:
|
125
|
+
- lib/shield/helpers.rb
|
126
|
+
- lib/shield/login.rb
|
127
|
+
- lib/shield/password.rb
|
128
|
+
- lib/shield/template/basic_user.rb
|
129
|
+
- lib/shield/template/flexi_user.rb
|
130
|
+
- lib/shield/template/user.rb
|
131
|
+
- lib/shield.rb
|
132
|
+
- README.markdown
|
133
|
+
- README.rb
|
134
|
+
- LICENSE
|
135
|
+
- Rakefile
|
136
|
+
- test/basic_user_test.rb
|
137
|
+
- test/flexi_user_test.rb
|
138
|
+
- test/helper.rb
|
139
|
+
- test/login_middleware_test.rb
|
140
|
+
- test/login_rack_mounting_test.rb
|
141
|
+
- test/mounted_middleware_test.rb
|
142
|
+
- test/password_hash_test.rb
|
143
|
+
- test/shield_test.rb
|
144
|
+
- test/sinatra_test.rb
|
145
|
+
- test/user_test.rb
|
146
|
+
has_rdoc: true
|
147
|
+
homepage: http://github.com/cyx/shield
|
148
|
+
licenses: []
|
149
|
+
|
150
|
+
post_install_message:
|
151
|
+
rdoc_options: []
|
152
|
+
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
version: "0"
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
segments:
|
169
|
+
- 0
|
170
|
+
version: "0"
|
171
|
+
requirements: []
|
172
|
+
|
173
|
+
rubyforge_project: shield
|
174
|
+
rubygems_version: 1.3.7
|
175
|
+
signing_key:
|
176
|
+
specification_version: 3
|
177
|
+
summary: Ohm specific authentication solution.
|
178
|
+
test_files: []
|
179
|
+
|