shield 2.1.0 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5e8cddce5d60179e40bb696d3cf99881cef02d32
4
- data.tar.gz: de020f37164376682f9f31b48d3e82c4e31acca0
3
+ metadata.gz: b42c70f64c2292b685387b09ea89df2a0aff18a3
4
+ data.tar.gz: 0782a246fddf9b63acf1cced5f5fb5748d170aab
5
5
  SHA512:
6
- metadata.gz: f7299a1a78a171b4e9b382282552ea521861ac0ecc8f96673998bdce98f8a9886b839e05e9dcb0195c57f55d72cc813cea169f6f675e51687dc034d4be6f2564
7
- data.tar.gz: f85025b14a92309fc03edc3dc978e23e4f6dffd267fa2fd6ef12a372fe56c14e58797901e94f1a7abb86b60b5fccbb840ef81356ed1d9a73cd0524f0f801d666
6
+ metadata.gz: 7b2758cc4a231e6efcc138cc24dc360391b500f23f1970e12c1f42b72817dd017de871cafe37961c5fca4d3433abd258f22b4d05797da1094c533638e7e58ffe
7
+ data.tar.gz: a76c6de3bd2a54f4b29e7974404b8f286d07462948670e2bd986ac913271887f1653ca2a8bdc73ba6806b222d377e89d2ab0b6e4c8362240c0de3bc42f8b1ae7
data/.gems ADDED
@@ -0,0 +1,4 @@
1
+ cutest -v 1.2.1
2
+ rack-test -v 0.6.2
3
+ cuba -v 3.1.0
4
+ armor -v 0.0.3
@@ -0,0 +1 @@
1
+ /pkg
data/README.md CHANGED
@@ -1,27 +1,32 @@
1
- # Shield
1
+ Shield
2
+ ======
2
3
 
3
4
  Shield
4
5
 
5
6
  _n. A solid piece of metal code used to protect your application._
6
7
 
7
- ## Why another authentication library?
8
+ Why another authentication library?
9
+ -----------------------------------
8
10
 
9
11
  1. Because most of the other libraries are too huge.
10
12
  2. Extending other libraries is a pain.
11
- 3. Writing code is fun :-)
13
+ 3. Writing code is fun :-).
12
14
 
13
- ## What shield is
15
+ What shield is
16
+ --------------
14
17
 
15
- 1. Simple (~ 110 lines of Ruby code)
16
- 2. Doesn't get in the way
17
- 3. Treats you like a grown up
18
+ 1. Simple (~ 110 lines of Ruby code).
19
+ 2. Doesn't get in the way.
20
+ 3. Treats you like a grown up.
18
21
 
19
- ## What shield is not
22
+ What shield is not
23
+ ------------------
20
24
 
21
25
  - is _not_ a ready-made end-to-end authentication solution.
22
26
  - is _not_ biased towards any kind of ORM.
23
27
 
24
- ## Understanding Shield in 15 minutes
28
+ Understanding Shield in 15 minutes
29
+ ----------------------------------
25
30
 
26
31
  ### Shield::Model
27
32
 
@@ -82,14 +87,8 @@ Shield uses [Armor][armor] for encrypting passwords. Armor is a pure ruby
82
87
  implementation of [PBKDF2][pbkdf2], a password-based key derivation function
83
88
  recommended for the protection of electronically-stored data.
84
89
 
85
- Shield also includes tests for [ohm][ohm] and [sequel][sequel] and makes sure
86
- that each release works with the latest respective versions.
87
-
88
- Take a look at [test/ohm.rb][ohm-test] and [test/sequel.rb][sequel-test]
89
- to learn more.
90
-
91
- To make Shield work with other ORMs (such as DataMapper), make sure to implement
92
- an `.[]` method which fetches the user instance by id.
90
+ To make Shield work with any ORM, make sure that an `.[]` method which
91
+ fetches the user instance by id is implemented.
93
92
 
94
93
  [armor]: https://github.com/cyx/armor
95
94
  [pbkdf2]: http://en.wikipedia.org/wiki/PBKDF2
@@ -100,7 +99,7 @@ class User
100
99
 
101
100
  # ...
102
101
 
103
- self.[](id)
102
+ def self.[](id)
104
103
  get id
105
104
  end
106
105
  end
@@ -113,15 +112,19 @@ in using either username or email, then you can simply extend `User.fetch`
113
112
  a bit by doing:
114
113
 
115
114
  ```ruby
116
- # in Sequel
115
+ # in Sequel (http://sequel.rubyforge.org)
117
116
  class User < Sequel::Model
117
+ include Shield::Model
118
+
118
119
  def self.fetch(identifier)
119
120
  filter(email: identifier).first || filter(username: identifier).first
120
121
  end
121
122
  end
122
123
 
123
- # in Ohm
124
+ # in Ohm (http://ohm.keyvalue.org)
124
125
  class User < Ohm::Model
126
+ include Shield::Model
127
+
125
128
  attribute :email
126
129
  attribute :username
127
130
 
@@ -137,12 +140,6 @@ end
137
140
  If you want to allow case-insensitive logins for some reason, you can
138
141
  simply normalize the values to their lowercase form.
139
142
 
140
- [ohm]: http://ohm.keyvalue.org
141
- [sequel]: http://sequel.rubyforge.org
142
-
143
- [ohm-test]: https://github.com/cyx/shield/blob/master/test/ohm.rb
144
- [sequel-test]: https://github.com/cyx/shield/blob/master/test/sequel.rb
145
-
146
143
  ### Shield::Helpers
147
144
 
148
145
  As the name suggests, `Shield::Helpers` is out there to aid you a bit,
@@ -98,17 +98,33 @@ module Shield
98
98
  end
99
99
 
100
100
  module Password
101
+ Error = Class.new(StandardError)
102
+
103
+ # == DOS attack fix
104
+ #
105
+ # Excessively long passwords (e.g. 1MB strings) would hang
106
+ # a server.
107
+ #
108
+ # @see: https://www.djangoproject.com/weblog/2013/sep/15/security/
109
+ MAX_LEN = 4096
110
+
101
111
  def self.encrypt(password, salt = generate_salt)
102
- Armor.digest(password, salt) + salt
112
+ digest(password, salt) + salt
103
113
  end
104
114
 
105
115
  def self.check(password, encrypted)
106
116
  sha512, salt = encrypted.to_s[0...128], encrypted.to_s[128..-1]
107
117
 
108
- Armor.compare(Armor.digest(password, salt), sha512)
118
+ Armor.compare(digest(password, salt), sha512)
109
119
  end
110
120
 
111
121
  protected
122
+ def self.digest(password, salt)
123
+ raise Error if password.length > MAX_LEN
124
+
125
+ Armor.digest(password, salt)
126
+ end
127
+
112
128
  def self.generate_salt
113
129
  Armor.hex(OpenSSL::Random.random_bytes(32))
114
130
  end
@@ -0,0 +1,4 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ cutest -r ./test/helper test/*.rb
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "shield"
3
+ s.version = "2.1.1"
4
+ s.summary = %{Generic authentication protocol for rack applications.}
5
+ s.description = %Q{
6
+ Provides all the protocol you need in order to do authentication on
7
+ your rack application. The implementation specifics can be found in
8
+ http://github.com/cyx/shield-contrib
9
+ }
10
+ s.authors = ["Michel Martens", "Damian Janowski", "Cyril David"]
11
+ s.email = ["michel@soveran.com", "djanowski@dimaion.com", "me@cyrildavid.com"]
12
+ s.homepage = "http://github.com/cyx/shield"
13
+ s.license = "MIT"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+
17
+ s.add_dependency "armor"
18
+ s.add_development_dependency "cutest"
19
+ s.add_development_dependency "rack-test"
20
+ s.add_development_dependency "cuba"
21
+ end
@@ -0,0 +1,91 @@
1
+ require_relative "helper"
2
+ require_relative "user"
3
+ require "cuba"
4
+
5
+ Cuba.use Rack::Session::Cookie, secret: "foo"
6
+ Cuba.use Shield::Middleware
7
+ Cuba.plugin Shield::Helpers
8
+
9
+ Cuba.define do
10
+ on get, "public" do
11
+ res.write "Public"
12
+ end
13
+
14
+ on get, "private" do
15
+ if authenticated(User)
16
+ res.write "Private"
17
+ else
18
+ res.status = 401
19
+ end
20
+ end
21
+
22
+ on get, "login" do
23
+ res.write "Login"
24
+ end
25
+
26
+ on post, "login", param("login"), param("password") do |u, p|
27
+ if login(User, u, p)
28
+ remember if req[:remember_me]
29
+ res.redirect(req[:return] || "/")
30
+ else
31
+ res.redirect "/login"
32
+ end
33
+ end
34
+
35
+ on "logout" do
36
+ logout(User)
37
+ res.redirect "/"
38
+ end
39
+ end
40
+
41
+ scope do
42
+ def app
43
+ Cuba
44
+ end
45
+
46
+ setup do
47
+ clear_cookies
48
+ end
49
+
50
+ test "public" do
51
+ get "/public"
52
+ assert "Public" == last_response.body
53
+ end
54
+
55
+ test "successful logging in" do
56
+ get "/private"
57
+
58
+ assert_equal "/login?return=%2Fprivate", redirection_url
59
+
60
+ post "/login", login: "quentin", password: "password", return: "/private"
61
+
62
+ assert_redirected_to "/private"
63
+
64
+ assert 1001 == session["User"]
65
+ end
66
+
67
+ test "failed login" do
68
+ post "/login", :login => "q", :password => "p"
69
+ assert_redirected_to "/login"
70
+
71
+ assert nil == session["User"]
72
+ end
73
+
74
+ test "logging out" do
75
+ post "/login", :login => "quentin", :password => "password"
76
+
77
+ get "/logout"
78
+
79
+ assert nil == session["User"]
80
+ end
81
+
82
+ test "remember functionality" do
83
+ post "/login", :login => "quentin", :password => "password", :remember_me => "1"
84
+
85
+ assert_equal session[:remember_for], 86400 * 14
86
+
87
+ get "/logout"
88
+
89
+ assert_equal nil, session[:remember_for]
90
+ end
91
+ end
@@ -0,0 +1,23 @@
1
+ require "cutest"
2
+ require "rack/test"
3
+
4
+ require_relative "../lib/shield"
5
+
6
+ class Cutest::Scope
7
+ include Rack::Test::Methods
8
+
9
+ def assert_redirected_to(path)
10
+ unless last_response.status == 302
11
+ flunk
12
+ end
13
+ assert_equal path, URI(redirection_url).path
14
+ end
15
+
16
+ def redirection_url
17
+ last_response.headers["Location"]
18
+ end
19
+
20
+ def session
21
+ last_request.env["rack.session"]
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ require_relative "helper"
2
+ require_relative "user"
3
+ require "cuba"
4
+
5
+ Cuba.use Rack::Session::Cookie, secret: "foo"
6
+ Cuba.use Shield::Middleware
7
+
8
+ Cuba.plugin Shield::Helpers
9
+
10
+ Cuba.define do
11
+ on "secured" do
12
+ if not authenticated(User)
13
+ halt [401, { "Content-Type" => "text/html" }, []]
14
+ end
15
+
16
+ res.write "You're in"
17
+ end
18
+
19
+ on "foo" do
20
+ puts env.inspect
21
+ end
22
+ end
23
+
24
+ test do
25
+ env = { "PATH_INFO" => "/secured", "SCRIPT_NAME" => "" }
26
+ status, headers, body = Cuba.call(env)
27
+
28
+ assert_equal 302, status
29
+ assert_equal "/login?return=%2Fsecured", headers["Location"]
30
+ end
@@ -0,0 +1,51 @@
1
+ require_relative "helper"
2
+
3
+ class User < Struct.new(:crypted_password)
4
+ include Shield::Model
5
+ end
6
+
7
+ test "fetch" do
8
+ ex = nil
9
+
10
+ begin
11
+ User.fetch("quentin")
12
+ rescue Exception => ex
13
+ end
14
+
15
+ assert ex.kind_of?(Shield::Model::FetchMissing)
16
+ assert "User.fetch not implemented" == ex.message
17
+ end
18
+
19
+ test "is_valid_password?" do
20
+ user = User.new(Shield::Password.encrypt("password"))
21
+
22
+ assert User.is_valid_password?(user, "password")
23
+ assert ! User.is_valid_password?(user, "password1")
24
+ end
25
+
26
+ class User
27
+ class << self
28
+ attr_accessor :fetched
29
+ end
30
+
31
+ def self.fetch(username)
32
+ return fetched if username == "quentin"
33
+ end
34
+ end
35
+
36
+ test "authenticate" do
37
+ user = User.new(Shield::Password.encrypt("pass"))
38
+
39
+ User.fetched = user
40
+
41
+ assert user == User.authenticate("quentin", "pass")
42
+ assert nil == User.authenticate("unknown", "pass")
43
+ assert nil == User.authenticate("quentin", "wrongpass")
44
+ end
45
+
46
+ test "#password=" do
47
+ u = User.new
48
+ u.password = "pass1234"
49
+
50
+ assert Shield::Password.check("pass1234", u.crypted_password)
51
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "helper"
2
+
3
+ class User
4
+ include Shield::Model
5
+
6
+ attr_accessor :email, :crypted_password
7
+
8
+ def self.fetch(email)
9
+ $users[email]
10
+ end
11
+
12
+ def initialize(email, password)
13
+ @email = email
14
+ self.password = password
15
+ end
16
+ end
17
+
18
+ setup do
19
+ $users = {}
20
+ $users["foo@bar.com"] = User.new("foo@bar.com", "pass1234")
21
+ end
22
+
23
+ test "fetch" do |user|
24
+ assert_equal user, User.fetch("foo@bar.com")
25
+ end
26
+
27
+ test "authenticate" do |user|
28
+ assert_equal user, User.authenticate("foo@bar.com", "pass1234")
29
+ end
@@ -0,0 +1,41 @@
1
+ require_relative "helper"
2
+ require_relative "user"
3
+ require "cuba"
4
+
5
+ Cuba.use Rack::Session::Cookie, secret: "foo"
6
+ Cuba.plugin Shield::Helpers
7
+
8
+ class Admin < Cuba
9
+ use Shield::Middleware, "/admin/login"
10
+
11
+ define do
12
+ on "login" do
13
+ res.write "Login"
14
+ end
15
+
16
+ on default do
17
+ res.status = 401
18
+ end
19
+ end
20
+ end
21
+
22
+ Cuba.define do
23
+ on "admin" do
24
+ run Admin
25
+ end
26
+ end
27
+
28
+ scope do
29
+ def app
30
+ Cuba
31
+ end
32
+
33
+ setup do
34
+ clear_cookies
35
+ end
36
+
37
+ test "return + return flow" do
38
+ get "/admin"
39
+ assert_equal "/admin/login?return=%2Fadmin", redirection_url
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ require_relative "helper"
2
+
3
+ scope do
4
+ test "encrypt" do
5
+ encrypted = Shield::Password.encrypt("password")
6
+ assert Shield::Password.check("password", encrypted)
7
+ end
8
+
9
+ test "with custom 64 character salt" do
10
+ encrypted = Shield::Password.encrypt("password", "A" * 64)
11
+ assert Shield::Password.check("password", encrypted)
12
+ end
13
+
14
+ test "DOS fix" do
15
+ too_long = '*' * (Shield::Password::MAX_LEN + 1)
16
+
17
+ assert_raise Shield::Password::Error do
18
+ Shield::Password.encrypt(too_long)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,95 @@
1
+ require_relative "helper"
2
+
3
+ class User < Struct.new(:id)
4
+ extend Shield::Model
5
+
6
+ def self.[](id)
7
+ User.new(1) unless id.to_s.empty?
8
+ end
9
+
10
+ def self.authenticate(username, password)
11
+ User.new(1001) if username == "quentin" && password == "password"
12
+ end
13
+ end
14
+
15
+ class Context
16
+ def initialize(path)
17
+ @path = path
18
+ end
19
+
20
+ def env
21
+ { "SCRIPT_NAME" => "", "PATH_INFO" => @path }
22
+ end
23
+
24
+ def session
25
+ @session ||= {}
26
+ end
27
+
28
+ class Request < Struct.new(:fullpath)
29
+ end
30
+
31
+ def req
32
+ Request.new(@path)
33
+ end
34
+
35
+ def redirect(redirect = nil)
36
+ @redirect = redirect if redirect
37
+ @redirect
38
+ end
39
+
40
+ include Shield::Helpers
41
+ end
42
+
43
+ setup do
44
+ Context.new("/events/1")
45
+ end
46
+
47
+ class Admin < Struct.new(:id)
48
+ def self.[](id)
49
+ new(id) unless id.to_s.empty?
50
+ end
51
+ end
52
+
53
+ test "authenticated" do |context|
54
+ context.session["User"] = 1
55
+
56
+ assert User.new(1) == context.authenticated(User)
57
+ assert nil == context.authenticated(Admin)
58
+ end
59
+
60
+ test "caches authenticated in @_shield" do |context|
61
+ context.session["User"] = 1
62
+ context.authenticated(User)
63
+
64
+ assert User.new(1) == context.instance_variable_get(:@_shield)[User]
65
+ end
66
+
67
+ test "login success" do |context|
68
+ assert context.login(User, "quentin", "password")
69
+ assert 1001 == context.session["User"]
70
+ end
71
+
72
+ test "login failure" do |context|
73
+ assert ! context.login(User, "wrong", "creds")
74
+ assert nil == context.session["User"]
75
+ end
76
+
77
+ test "logout" do |context|
78
+ context.session["User"] = 1001
79
+
80
+ # Now let's make it memoize the User
81
+ context.authenticated(User)
82
+
83
+ context.logout(User)
84
+
85
+ assert nil == context.session["User"]
86
+ assert nil == context.authenticated(User)
87
+ end
88
+
89
+ test "authenticate" do |context|
90
+ context.session["no_fixation"] = 1
91
+ context.authenticate(User[1001])
92
+
93
+ assert User[1] == context.authenticated(User)
94
+ assert nil == context.session["no_fixation"]
95
+ end
@@ -0,0 +1,21 @@
1
+ class User
2
+ include Shield::Model
3
+
4
+ def self.[](id)
5
+ User.new(1001) unless id.to_s.empty?
6
+ end
7
+
8
+ def self.fetch(username)
9
+ User.new(1001) if username == "quentin"
10
+ end
11
+
12
+ attr :id
13
+
14
+ def initialize(id)
15
+ @id = id
16
+ end
17
+
18
+ def crypted_password
19
+ @crypted_password ||= Shield::Password.encrypt("password")
20
+ end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shield
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michel Martens
@@ -10,48 +10,62 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-10-10 00:00:00.000000000 Z
13
+ date: 2014-10-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: armor
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - '>='
19
+ - - ">="
20
20
  - !ruby/object:Gem::Version
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - '>='
26
+ - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '0'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: cutest
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - '>='
33
+ - - ">="
34
34
  - !ruby/object:Gem::Version
35
35
  version: '0'
36
36
  type: :development
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - '>='
40
+ - - ">="
41
41
  - !ruby/object:Gem::Version
42
42
  version: '0'
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: rack-test
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - '>='
47
+ - - ">="
48
48
  - !ruby/object:Gem::Version
49
49
  version: '0'
50
50
  type: :development
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
- - - '>='
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: cuba
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
55
69
  - !ruby/object:Gem::Version
56
70
  version: '0'
57
71
  description: "\n Provides all the protocol you need in order to do authentication
@@ -65,11 +79,25 @@ executables: []
65
79
  extensions: []
66
80
  extra_rdoc_files: []
67
81
  files:
68
- - README.md
82
+ - ".gems"
83
+ - ".gitignore"
69
84
  - LICENSE
85
+ - README.md
70
86
  - lib/shield.rb
87
+ - makefile
88
+ - shield.gemspec
89
+ - test/cuba.rb
90
+ - test/helper.rb
91
+ - test/middleware.rb
92
+ - test/model.rb
93
+ - test/model_integration.rb
94
+ - test/nested.rb
95
+ - test/password.rb
96
+ - test/shield.rb
97
+ - test/user.rb
71
98
  homepage: http://github.com/cyx/shield
72
- licenses: []
99
+ licenses:
100
+ - MIT
73
101
  metadata: {}
74
102
  post_install_message:
75
103
  rdoc_options: []
@@ -77,17 +105,17 @@ require_paths:
77
105
  - lib
78
106
  required_ruby_version: !ruby/object:Gem::Requirement
79
107
  requirements:
80
- - - '>='
108
+ - - ">="
81
109
  - !ruby/object:Gem::Version
82
110
  version: '0'
83
111
  required_rubygems_version: !ruby/object:Gem::Requirement
84
112
  requirements:
85
- - - '>='
113
+ - - ">="
86
114
  - !ruby/object:Gem::Version
87
115
  version: '0'
88
116
  requirements: []
89
117
  rubyforge_project:
90
- rubygems_version: 2.0.3
118
+ rubygems_version: 2.2.2
91
119
  signing_key:
92
120
  specification_version: 4
93
121
  summary: Generic authentication protocol for rack applications.