shield 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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.