rails-auth 0.0.1 → 0.1.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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -2
- data/BUG-BOUNTY.md +9 -0
- data/CHANGES.md +23 -0
- data/Gemfile +1 -0
- data/README.md +58 -15
- data/lib/rails/auth/acl/matchers/allow_all.rb +1 -1
- data/lib/rails/auth/credentials.rb +36 -0
- data/lib/rails/auth/error_page/middleware.rb +21 -0
- data/lib/rails/auth/rack.rb +5 -2
- data/lib/rails/auth/rspec/helper_methods.rb +10 -10
- data/lib/rails/auth/rspec/matchers/acl_matchers.rb +5 -5
- data/lib/rails/auth/version.rb +1 -1
- data/lib/rails/auth/x509/{principal.rb → certificate.rb} +2 -2
- data/lib/rails/auth/x509/filter/java.rb +1 -1
- data/lib/rails/auth/x509/filter/pem.rb +1 -1
- data/lib/rails/auth/x509/matcher.rb +4 -4
- data/lib/rails/auth/x509/middleware.rb +6 -6
- data/spec/rails/auth/acl_spec.rb +1 -1
- data/spec/rails/auth/credentials_spec.rb +36 -0
- data/spec/rails/auth/error_page/middleware_spec.rb +26 -0
- data/spec/rails/auth/rspec/helper_methods_spec.rb +6 -6
- data/spec/rails/auth/rspec/matchers/acl_matchers_spec.rb +5 -5
- data/spec/rails/auth/x509/certificate_spec.rb +27 -0
- data/spec/rails/auth/x509/matcher_spec.rb +8 -8
- data/spec/rails/auth/x509/middleware_spec.rb +11 -9
- data/spec/spec_helper.rb +4 -1
- data/spec/support/claims_matcher.rb +11 -0
- metadata +11 -7
- data/lib/rails/auth/principals.rb +0 -36
- data/spec/rails/auth/principals_spec.rb +0 -36
- data/spec/rails/auth/x509/principal_spec.rb +0 -27
- data/spec/support/claims_predicate.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f4c0d9519675ef1b560bbc2cc2ef0dcc2b9b178
|
4
|
+
data.tar.gz: 2cd1b6f0f0f2462db7ab4bd280f1c54dc6bff513
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91b47228828b5f448effd6e5b03d0e141b35253a388823ec5fc10ef069f9a86cbff3c7d84efa589ed513da31e1528e176671deff7fe743b7b6ad3f7121d9d7e0
|
7
|
+
data.tar.gz: eaaa0f23d01a3a2efcdd0c4baf65f3f6259a86b9825d10ccd96431d80529eae395f8444cf8881fb799d63a09d682ebccaf4f627b0f0bc2b77c997ce01dda96f4
|
data/.travis.yml
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
3
|
|
4
|
+
before_install:
|
5
|
+
- gem install bundler
|
6
|
+
|
4
7
|
rvm:
|
5
8
|
- 2.0.0
|
6
9
|
- 2.1.8
|
7
10
|
- 2.2.4
|
8
11
|
- 2.3.0
|
9
|
-
- jruby-9.0.4.0
|
10
|
-
|
11
12
|
matrix:
|
13
|
+
include:
|
14
|
+
- rvm: jruby-9.0.4.0
|
15
|
+
env: JRUBY_OPTS="--debug" # for simplecov
|
12
16
|
fast_finish: true
|
data/BUG-BOUNTY.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Break me and win a prize!
|
2
|
+
=========================
|
3
|
+
|
4
|
+
Square recognizes the important contributions the security research community
|
5
|
+
can make. We therefore encourage reporting security issues with the code
|
6
|
+
contained in this repository.
|
7
|
+
|
8
|
+
If you believe you have discovered a security vulnerability, please follow the
|
9
|
+
guidelines at https://hackerone.com/square-open-source
|
data/CHANGES.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
### 0.1.0 (2016-02-10)
|
2
|
+
|
3
|
+
* [#6](https://github.com/square/rails-auth/pull/6):
|
4
|
+
Rename principals to credentials and Rails::Auth::X509::Principals to
|
5
|
+
Rails::Auth::X509::Certificates.
|
6
|
+
([@tarcieri])
|
7
|
+
|
8
|
+
* [#5](https://github.com/square/rails-auth/pull/5):
|
9
|
+
Add Rails::Auth::ErrorPage::Middleware.
|
10
|
+
([@tarcieri])
|
11
|
+
|
12
|
+
### 0.0.1 (2016-01-26)
|
13
|
+
|
14
|
+
* [#1](https://github.com/square/rails-auth/pull/1):
|
15
|
+
Initial implementation.
|
16
|
+
([@tarcieri])
|
17
|
+
|
18
|
+
### 0.0.0 (2016-01-04)
|
19
|
+
|
20
|
+
* Vaporware release to claim the "rails-auth" gem name
|
21
|
+
|
22
|
+
|
23
|
+
[@tarcieri]: https://github.com/tarcieri
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
|
1
|
+
Rails::Auth
|
2
|
+
===========
|
3
|
+
[](http://rubygems.org/gems/rails-auth)
|
4
|
+
[](https://travis-ci.org/square/rails-auth)
|
5
|
+
[](https://codeclimate.com/github/square/rails-auth)
|
6
|
+
[](https://coveralls.io/github/square/rails-auth?branch=master)
|
7
|
+
[](https://github.com/square/rails-auth/blob/master/LICENSE)
|
2
8
|
|
3
9
|
Modular resource-based authentication and authorization for Rails/Rack
|
4
10
|
|
@@ -7,8 +13,8 @@ Modular resource-based authentication and authorization for Rails/Rack
|
|
7
13
|
Rails::Auth is a flexible library designed for both authentication (AuthN) and
|
8
14
|
authorization (AuthZ) using Rack Middleware. It splits the AuthN and AuthZ
|
9
15
|
steps into separate middleware classes, using AuthN middleware to first verify
|
10
|
-
|
11
|
-
via separate AuthZ middleware that consumes these
|
16
|
+
credentials (such as X.509 certificates or cookies), then authorizing the request
|
17
|
+
via separate AuthZ middleware that consumes these credentials, e.g. access
|
12
18
|
control lists (ACLs).
|
13
19
|
|
14
20
|
Rails::Auth can be used to authenticate and authorize end users using browser
|
@@ -39,7 +45,7 @@ middleware for your app.
|
|
39
45
|
Rails::Auth ships with the following middleware:
|
40
46
|
|
41
47
|
* **AuthN**: `Rails::Auth::X509::Middleware`: support for authenticating
|
42
|
-
|
48
|
+
clients by their SSL/TLS client certificates.
|
43
49
|
* **AuthZ**: `Rails::Auth::ACL::Middleware`: support for authorizing requests
|
44
50
|
using Access Control Lists (ACLs).
|
45
51
|
|
@@ -104,7 +110,7 @@ app = MyRackApp.new
|
|
104
110
|
|
105
111
|
acl = Rails::Auth::ACL.from_yaml(
|
106
112
|
File.read("/path/to/my/acl.yaml"),
|
107
|
-
matchers: { allow_claims:
|
113
|
+
matchers: { allow_claims: MyClaimsMatcher }
|
108
114
|
)
|
109
115
|
|
110
116
|
acl_auth = Rails::Auth::ACL::Middleware.new(app, acl: acl)
|
@@ -127,14 +133,14 @@ object from the ACL definition is passed to the class's `#initialize` method.
|
|
127
133
|
Here is an example of a simple custom predicate matcher:
|
128
134
|
|
129
135
|
```ruby
|
130
|
-
class
|
136
|
+
class MyClaimsMatcher
|
131
137
|
def initialize(options)
|
132
138
|
@options = options
|
133
139
|
end
|
134
140
|
|
135
141
|
def match(env)
|
136
|
-
claims = Rails::Auth.
|
137
|
-
return false unless
|
142
|
+
claims = Rails::Auth.credentials(env)["claims"]
|
143
|
+
return false unless credential
|
138
144
|
|
139
145
|
@options["groups"].any? { |group| claims["groups"].include?(group) }
|
140
146
|
end
|
@@ -223,9 +229,9 @@ certificates:
|
|
223
229
|
cert_filters: { 'X-SSL-Client-Cert' => proc { |pem| OpenSSL::X509::Certificate.new(pem) } }
|
224
230
|
```
|
225
231
|
|
226
|
-
When certificates are recognized and verified,
|
227
|
-
object will be added to the Rack environment under `env["rails-auth.
|
228
|
-
This middleware will never add any certificate to the environment's
|
232
|
+
When certificates are recognized and verified, a `Rails::Auth::X509::Certificate`
|
233
|
+
object will be added to the Rack environment under `env["rails-auth.credentials"]["x509"]`.
|
234
|
+
This middleware will never add any certificate to the environment's credentials
|
229
235
|
that hasn't been verified against the configured CA bundle.
|
230
236
|
|
231
237
|
## RSpec integration
|
@@ -243,7 +249,7 @@ Below is an example of how to write an ACL spec:
|
|
243
249
|
|
244
250
|
```ruby
|
245
251
|
RSpec.describe "example_acl.yml", acl_spec: true do
|
246
|
-
let(:
|
252
|
+
let(:example_credentials) { x509_certificate_hash(ou: "ponycopter") }
|
247
253
|
|
248
254
|
subject do
|
249
255
|
Rails::Auth::ACL.from_yaml(
|
@@ -253,7 +259,7 @@ RSpec.describe "example_acl.yml", acl_spec: true do
|
|
253
259
|
end
|
254
260
|
|
255
261
|
describe "/path/to/resource" do
|
256
|
-
it { is_expected.to permit get_request(
|
262
|
+
it { is_expected.to permit get_request(credentials: example_credentials) }
|
257
263
|
it { is_expected.not_to permit get_request) }
|
258
264
|
end
|
259
265
|
end
|
@@ -261,7 +267,7 @@ end
|
|
261
267
|
|
262
268
|
The following helper methods are available:
|
263
269
|
|
264
|
-
* `
|
270
|
+
* `x509_certificate`, `x509_certificate_hash`: create instance doubles of Rails::Auth::X509::Certificate
|
265
271
|
* Request builders: The following methods build requests from the described path:
|
266
272
|
* `get_request`
|
267
273
|
* `head_request`
|
@@ -275,7 +281,44 @@ The following helper methods are available:
|
|
275
281
|
|
276
282
|
The following matchers are available:
|
277
283
|
|
278
|
-
* `allow_request`: allows a request with the given Rack environment, and optional
|
284
|
+
* `allow_request`: allows a request with the given Rack environment, and optional credentials
|
285
|
+
|
286
|
+
### Error Page Middleware
|
287
|
+
|
288
|
+
When an authorization error occurs, the `Rails::Auth::NotAuthorizedError`
|
289
|
+
exception is raised up the middleware chain. However, it's likely you would
|
290
|
+
prefer to show an error page than have an unhandled exception.
|
291
|
+
|
292
|
+
You can write your own middleware that catches `Rails::Auth::NotAuthorizedError`
|
293
|
+
if you'd like. However, a default one is provided which renders a 403 response
|
294
|
+
with a static page body if you find that helpful.
|
295
|
+
|
296
|
+
To use it, add `Rails::Auth::ErrorPage::Middleware` to your app:
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
app = MyRackApp.new
|
300
|
+
|
301
|
+
acl = Rails::Auth::ACL.from_yaml(
|
302
|
+
File.read("/path/to/my/acl.yaml")
|
303
|
+
matchers: { allow_x509_subject: Rails::Auth::X509::Matcher }
|
304
|
+
)
|
305
|
+
|
306
|
+
acl_auth = Rails::Auth::ACL::Middleware.new(app, acl: acl)
|
307
|
+
|
308
|
+
x509_auth = Rails::Auth::X509::Middleware.new(
|
309
|
+
acl_auth,
|
310
|
+
ca_file: "/path/to/my/cabundle.pem"
|
311
|
+
cert_filters: { 'X-SSL-Client-Cert' => :pem },
|
312
|
+
require_cert: true
|
313
|
+
)
|
314
|
+
|
315
|
+
error_page = Rails::Auth::ErrorPage::Middleware.new(
|
316
|
+
x509_auth,
|
317
|
+
page_body: File.read("path/to/403.html")
|
318
|
+
)
|
319
|
+
|
320
|
+
run error_page
|
321
|
+
```
|
279
322
|
|
280
323
|
## Contributing
|
281
324
|
|
@@ -3,7 +3,7 @@ module Rails
|
|
3
3
|
class ACL
|
4
4
|
# Built-in predicate matchers
|
5
5
|
module Matchers
|
6
|
-
# Allows
|
6
|
+
# Allows unauthenticated clients to access to a given resource
|
7
7
|
class AllowAll
|
8
8
|
def initialize(enabled)
|
9
9
|
fail ArgumentError, "enabled must be true/false" unless [true, false].include?(enabled)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
# Modular resource-based authentication and authorization for Rails/Rack
|
5
|
+
module Auth
|
6
|
+
# Rack environment key for all rails-auth credentials
|
7
|
+
CREDENTIALS_ENV_KEY = "rails-auth.credentials".freeze
|
8
|
+
|
9
|
+
# Functionality for storing credentials in the Rack environment
|
10
|
+
module Credentials
|
11
|
+
# Obtain credentials from a Rack environment
|
12
|
+
#
|
13
|
+
# @param [Hash] :env Rack environment
|
14
|
+
#
|
15
|
+
def credentials(env)
|
16
|
+
env.fetch(CREDENTIALS_ENV_KEY, {})
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add a credential to the Rack environment
|
20
|
+
#
|
21
|
+
# @param [Hash] :env Rack environment
|
22
|
+
# @param [String] :type credential type to add to the environment
|
23
|
+
# @param [Object] :credential object to add to the environment
|
24
|
+
#
|
25
|
+
def add_credential(env, type, credential)
|
26
|
+
credentials = env[CREDENTIALS_ENV_KEY] ||= {}
|
27
|
+
|
28
|
+
fail ArgumentError, "credential #{type} already added to request" if credentials.key?(type)
|
29
|
+
credentials[type] = credential
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Include these functions in Rails::Auth for convenience
|
34
|
+
extend Credentials
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rails
|
2
|
+
module Auth
|
3
|
+
module ErrorPage
|
4
|
+
# Render an error page in the event Rails::Auth::NotAuthorizedError is raised
|
5
|
+
class Middleware
|
6
|
+
def initialize(app, page_body: nil)
|
7
|
+
fail TypeError, "page_body must be a String" unless page_body.is_a?(String)
|
8
|
+
|
9
|
+
@app = app
|
10
|
+
@page_body = page_body.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
@app.call(env)
|
15
|
+
rescue Rails::Auth::NotAuthorizedError
|
16
|
+
[403, {}, [@page_body]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/rails/auth/rack.rb
CHANGED
@@ -5,15 +5,18 @@ require "rack"
|
|
5
5
|
require "openssl"
|
6
6
|
|
7
7
|
require "rails/auth/version"
|
8
|
+
|
9
|
+
require "rails/auth/credentials"
|
8
10
|
require "rails/auth/exceptions"
|
9
|
-
require "rails/auth/principals"
|
10
11
|
|
11
12
|
require "rails/auth/acl"
|
12
13
|
require "rails/auth/acl/middleware"
|
13
14
|
require "rails/auth/acl/resource"
|
14
15
|
|
16
|
+
require "rails/auth/error_page/middleware"
|
17
|
+
|
18
|
+
require "rails/auth/x509/certificate"
|
15
19
|
require "rails/auth/x509/filter/pem"
|
16
20
|
require "rails/auth/x509/filter/java" if defined?(JRUBY_VERSION)
|
17
21
|
require "rails/auth/x509/matcher"
|
18
22
|
require "rails/auth/x509/middleware"
|
19
|
-
require "rails/auth/x509/principal"
|
@@ -3,14 +3,14 @@ module Rails
|
|
3
3
|
module RSpec
|
4
4
|
# RSpec helper methods
|
5
5
|
module HelperMethods
|
6
|
-
# Creates an Rails::Auth::X509::
|
7
|
-
def
|
6
|
+
# Creates an Rails::Auth::X509::Certificate instance double
|
7
|
+
def x509_certificate(cn: nil, ou: nil)
|
8
8
|
subject = ""
|
9
9
|
subject << "CN=#{cn}" if cn
|
10
10
|
subject << "OU=#{ou}" if ou
|
11
11
|
|
12
|
-
instance_double(X509::
|
13
|
-
allow(
|
12
|
+
instance_double(Rails::Auth::X509::Certificate, subject, cn: cn, ou: ou).tap do |certificate|
|
13
|
+
allow(certificate).to receive(:[]) do |key|
|
14
14
|
{
|
15
15
|
"CN" => cn,
|
16
16
|
"OU" => ou
|
@@ -19,13 +19,13 @@ module Rails
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
# Creates a
|
23
|
-
def
|
24
|
-
{ "x509" =>
|
22
|
+
# Creates a certificates hash containing a single X.509 certificate instance double
|
23
|
+
def x509_certificate_hash(**args)
|
24
|
+
{ "x509" => x509_certificate(**args) }
|
25
25
|
end
|
26
26
|
|
27
27
|
Rails::Auth::ACL::Resource::HTTP_METHODS.each do |method|
|
28
|
-
define_method("#{method.downcase}_request") do |
|
28
|
+
define_method("#{method.downcase}_request") do |certificates: {}|
|
29
29
|
path = self.class.description
|
30
30
|
|
31
31
|
# Warn if methods are improperly used
|
@@ -38,8 +38,8 @@ module Rails
|
|
38
38
|
"REQUEST_PATH" => self.class.description
|
39
39
|
}
|
40
40
|
|
41
|
-
|
42
|
-
Rails::Auth.
|
41
|
+
certificates.each do |type, value|
|
42
|
+
Rails::Auth.add_credential(env, type.to_s, value)
|
43
43
|
end
|
44
44
|
|
45
45
|
env
|
@@ -1,12 +1,12 @@
|
|
1
1
|
RSpec::Matchers.define(:permit) do |env|
|
2
2
|
description do
|
3
|
-
method
|
4
|
-
|
5
|
-
message
|
3
|
+
method = env["REQUEST_METHOD"]
|
4
|
+
credentials = Rails::Auth.credentials(env)
|
5
|
+
message = "allow #{method}s by "
|
6
6
|
|
7
|
-
return message << "unauthenticated clients" if
|
7
|
+
return message << "unauthenticated clients" if credentials.count.zero?
|
8
8
|
|
9
|
-
message <<
|
9
|
+
message << credentials.values.map(&:inspect).join(", ")
|
10
10
|
end
|
11
11
|
|
12
12
|
match { |acl| acl.match(env) }
|
data/lib/rails/auth/version.rb
CHANGED
@@ -5,7 +5,7 @@ module Rails
|
|
5
5
|
module Auth
|
6
6
|
module X509
|
7
7
|
module Filter
|
8
|
-
#
|
8
|
+
# Extract OpenSSL::X509::Certificates from Java's sun.security.x509.X509CertImpl
|
9
9
|
class Java
|
10
10
|
def call(cert)
|
11
11
|
OpenSSL::X509::Certificate.new(extract_der(cert)).freeze
|
@@ -2,7 +2,7 @@ module Rails
|
|
2
2
|
module Auth
|
3
3
|
module X509
|
4
4
|
module Filter
|
5
|
-
#
|
5
|
+
# Extract OpenSSL::X509::Certificates from Privacy Enhanced Mail (PEM) certificates
|
6
6
|
class Pem
|
7
7
|
def call(pem)
|
8
8
|
OpenSSL::X509::Certificate.new(pem).freeze
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Rails
|
2
2
|
module Auth
|
3
3
|
module X509
|
4
|
-
# Predicate matcher for making assertions about X.509
|
4
|
+
# Predicate matcher for making assertions about X.509 certificates
|
5
5
|
class Matcher
|
6
6
|
# @option options [String] cn Common Name of the subject
|
7
7
|
# @option options [String] ou Organizational Unit of the subject
|
@@ -11,10 +11,10 @@ module Rails
|
|
11
11
|
|
12
12
|
# @param [Hash] env Rack environment
|
13
13
|
def match(env)
|
14
|
-
|
15
|
-
return false unless
|
14
|
+
certificate = Rails::Auth.credentials(env)["x509"]
|
15
|
+
return false unless certificate
|
16
16
|
|
17
|
-
@options.all? { |name, value|
|
17
|
+
@options.all? { |name, value| certificate[name] == value }
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -6,8 +6,8 @@ module Rails
|
|
6
6
|
# Raised when certificate verification is mandatory
|
7
7
|
CertificateVerifyFailed = Class.new(NotAuthorizedError)
|
8
8
|
|
9
|
-
# Validates X.509 client certificates and adds
|
10
|
-
# clients to the rack environment as env["rails-auth.
|
9
|
+
# Validates X.509 client certificates and adds credential objects for valid
|
10
|
+
# clients to the rack environment as env["rails-auth.credentials"]["x509"]
|
11
11
|
class Middleware
|
12
12
|
# Create a new X.509 Middleware object
|
13
13
|
#
|
@@ -36,15 +36,15 @@ module Rails
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def call(env)
|
39
|
-
|
40
|
-
Rails::Auth.
|
39
|
+
credential = extract_credential(env)
|
40
|
+
Rails::Auth.add_credential(env, "x509".freeze, credential.freeze) if credential
|
41
41
|
|
42
42
|
@app.call(env)
|
43
43
|
end
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
def
|
47
|
+
def extract_credential(env)
|
48
48
|
@cert_filters.each do |key, filter|
|
49
49
|
raw_cert = env[key]
|
50
50
|
next unless raw_cert
|
@@ -54,7 +54,7 @@ module Rails
|
|
54
54
|
|
55
55
|
if @truststore.verify(cert)
|
56
56
|
log("Verified", cert)
|
57
|
-
return Rails::Auth::X509::
|
57
|
+
return Rails::Auth::X509::Certificate.new(cert)
|
58
58
|
else
|
59
59
|
log("Verify FAILED", cert)
|
60
60
|
fail CertificateVerifyFailed, "verify failed: #{subject(cert)}" if @require_cert
|
data/spec/rails/auth/acl_spec.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
RSpec.describe Rails::Auth::Credentials do
|
2
|
+
describe "#credentials" do
|
3
|
+
let(:example_type) { "example" }
|
4
|
+
let(:example_credentials) { { example_type => double(:credential) } }
|
5
|
+
|
6
|
+
let(:example_env) do
|
7
|
+
env_for(:get, "/").tap do |env|
|
8
|
+
env[Rails::Auth::CREDENTIALS_ENV_KEY] = example_credentials
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "extracts credentials from Rack environments" do
|
13
|
+
expect(Rails::Auth.credentials(example_env)).to eq example_credentials
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#add_credential" do
|
18
|
+
let(:example_type) { "example" }
|
19
|
+
let(:example_credential) { double(:credential) }
|
20
|
+
let(:example_env) { env_for(:get, "/") }
|
21
|
+
|
22
|
+
it "adds credentials to a Rack environment" do
|
23
|
+
expect(Rails::Auth.credentials(example_env)[example_type]).to be_nil
|
24
|
+
Rails::Auth.add_credential(example_env, example_type, example_credential)
|
25
|
+
expect(Rails::Auth.credentials(example_env)[example_type]).to eq example_credential
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises ArgumentError if the same type of credential is added twice" do
|
29
|
+
Rails::Auth.add_credential(example_env, example_type, example_credential)
|
30
|
+
|
31
|
+
expect do
|
32
|
+
Rails::Auth.add_credential(example_env, example_type, example_credential)
|
33
|
+
end.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
RSpec.describe Rails::Auth::ErrorPage::Middleware do
|
2
|
+
let(:request) { Rack::MockRequest.env_for("https://www.example.com") }
|
3
|
+
let(:error_page) { "<h1> Unauthorized!!! </h1>" }
|
4
|
+
|
5
|
+
subject(:middleware) { described_class.new(app, page_body: error_page) }
|
6
|
+
|
7
|
+
context "access granted" do
|
8
|
+
let(:code) { 200 }
|
9
|
+
let(:app) { ->(env) { [code, env, "Hello, world!"] } }
|
10
|
+
|
11
|
+
it "renders the expected response" do
|
12
|
+
response = middleware.call(request)
|
13
|
+
expect(response.first).to eq code
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "access denied" do
|
18
|
+
let(:app) { ->(_env) { fail(Rails::Auth::NotAuthorizedError, "not authorized!") } }
|
19
|
+
|
20
|
+
it "renders the error page" do
|
21
|
+
code, _env, body = middleware.call(request)
|
22
|
+
expect(code).to eq 403
|
23
|
+
expect(body).to eq [error_page]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -2,10 +2,10 @@ RSpec.describe Rails::Auth::RSpec::HelperMethods, acl_spec: true do
|
|
2
2
|
let(:example_cn) { "127.0.0.1" }
|
3
3
|
let(:example_ou) { "ponycopter" }
|
4
4
|
|
5
|
-
describe "#
|
6
|
-
subject {
|
5
|
+
describe "#x509_certificate" do
|
6
|
+
subject { x509_certificate(cn: example_cn, ou: example_ou) }
|
7
7
|
|
8
|
-
it "creates instance doubles for Rails::Auth::X509::
|
8
|
+
it "creates instance doubles for Rails::Auth::X509::Certificates" do
|
9
9
|
# Method syntax
|
10
10
|
expect(subject.cn).to eq example_cn
|
11
11
|
expect(subject.ou).to eq example_ou
|
@@ -16,10 +16,10 @@ RSpec.describe Rails::Auth::RSpec::HelperMethods, acl_spec: true do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
describe "#
|
20
|
-
subject {
|
19
|
+
describe "#x509_certificate_hash" do
|
20
|
+
subject { x509_certificate_hash(cn: example_cn, ou: example_ou) }
|
21
21
|
|
22
|
-
it "creates a
|
22
|
+
it "creates a certificate hash with an Rails::Auth::X509::Certificate double" do
|
23
23
|
expect(subject["x509"].cn).to eq example_cn
|
24
24
|
end
|
25
25
|
end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
RSpec.describe "RSpec ACL matchers", acl_spec: true do
|
2
|
-
let(:
|
3
|
-
let(:
|
2
|
+
let(:example_certificate) { x509_certificate_hash(ou: "ponycopter") }
|
3
|
+
let(:another_certificate) { x509_certificate_hash(ou: "derpderp") }
|
4
4
|
|
5
5
|
subject do
|
6
6
|
Rails::Auth::ACL.from_yaml(
|
7
7
|
fixture_path("example_acl.yml").read,
|
8
8
|
matchers: {
|
9
9
|
allow_x509_subject: Rails::Auth::X509::Matcher,
|
10
|
-
allow_claims:
|
10
|
+
allow_claims: ClaimsMatcher
|
11
11
|
}
|
12
12
|
)
|
13
13
|
end
|
14
14
|
|
15
15
|
describe "/baz/quux" do
|
16
|
-
it { is_expected.to permit get_request(
|
17
|
-
it { is_expected.not_to permit get_request(
|
16
|
+
it { is_expected.to permit get_request(certificates: example_certificate) }
|
17
|
+
it { is_expected.not_to permit get_request(certificates: another_certificate) }
|
18
18
|
it { is_expected.not_to permit get_request }
|
19
19
|
end
|
20
20
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
RSpec.describe Rails::Auth::X509::Certificate do
|
2
|
+
let(:example_cert) { OpenSSL::X509::Certificate.new(cert_path("valid.crt").read) }
|
3
|
+
let(:example_certificate) { described_class.new(example_cert) }
|
4
|
+
|
5
|
+
let(:example_cn) { "127.0.0.1" }
|
6
|
+
let(:example_ou) { "ponycopter" }
|
7
|
+
|
8
|
+
describe "#[]" do
|
9
|
+
it "allows access to subject components via strings" do
|
10
|
+
expect(example_certificate["CN"]).to eq example_cn
|
11
|
+
expect(example_certificate["OU"]).to eq example_ou
|
12
|
+
end
|
13
|
+
|
14
|
+
it "allows access to subject components via symbols" do
|
15
|
+
expect(example_certificate[:cn]).to eq example_cn
|
16
|
+
expect(example_certificate[:ou]).to eq example_ou
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "knows its #cn" do
|
21
|
+
expect(example_certificate.cn).to eq example_cn
|
22
|
+
end
|
23
|
+
|
24
|
+
it "knows its #ou" do
|
25
|
+
expect(example_certificate.ou).to eq example_ou
|
26
|
+
end
|
27
|
+
end
|
@@ -1,21 +1,21 @@
|
|
1
1
|
RSpec.describe Rails::Auth::X509::Matcher do
|
2
|
-
let(:example_cert)
|
3
|
-
let(:
|
2
|
+
let(:example_cert) { OpenSSL::X509::Certificate.new(cert_path("valid.crt").read) }
|
3
|
+
let(:example_certificate) { Rails::Auth::X509::Certificate.new(example_cert) }
|
4
4
|
|
5
5
|
let(:example_ou) { "ponycopter" }
|
6
6
|
let(:another_ou) { "somethingelse" }
|
7
7
|
|
8
8
|
let(:example_env) do
|
9
|
-
{ Rails::Auth::
|
9
|
+
{ Rails::Auth::CREDENTIALS_ENV_KEY => { "x509" => example_certificate } }
|
10
10
|
end
|
11
11
|
|
12
|
-
it "matches against a valid Rails::Auth::X509::
|
13
|
-
|
14
|
-
expect(
|
12
|
+
it "matches against a valid Rails::Auth::X509::Credential" do
|
13
|
+
matcher = described_class.new(ou: example_ou)
|
14
|
+
expect(matcher.match(example_env)).to eq true
|
15
15
|
end
|
16
16
|
|
17
17
|
it "doesn't match if the subject mismatches" do
|
18
|
-
|
19
|
-
expect(
|
18
|
+
matcher = described_class.new(ou: another_ou)
|
19
|
+
expect(matcher.match(example_env)).to eq false
|
20
20
|
end
|
21
21
|
end
|
@@ -22,19 +22,20 @@ RSpec.describe Rails::Auth::X509::Middleware do
|
|
22
22
|
|
23
23
|
context "certificate types" do
|
24
24
|
describe "PEM certificates" do
|
25
|
-
it "extracts Rails::Auth::
|
25
|
+
it "extracts Rails::Auth::X509::Certificate from a PEM certificate in the Rack environment" do
|
26
26
|
_response, env = middleware.call(request.merge(example_key => valid_cert_pem))
|
27
27
|
|
28
|
-
|
29
|
-
expect(
|
28
|
+
credential = Rails::Auth.credentials(env).fetch("x509")
|
29
|
+
expect(credential).to be_a Rails::Auth::X509::Certificate
|
30
30
|
end
|
31
31
|
|
32
32
|
it "ignores unverified certificates" do
|
33
33
|
_response, env = middleware.call(request.merge(example_key => bad_cert_pem))
|
34
|
-
expect(Rails::Auth.
|
34
|
+
expect(Rails::Auth.credentials(env)).to be_empty
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
# :nocov:
|
38
39
|
describe "Java certificates" do
|
39
40
|
let(:example_key) { "javax.servlet.request.X509Certificate" }
|
40
41
|
let(:cert_filter) { :java }
|
@@ -44,15 +45,16 @@ RSpec.describe Rails::Auth::X509::Middleware do
|
|
44
45
|
Java::SunSecurityX509::X509CertImpl.new(ruby_cert.to_der.to_java_bytes)
|
45
46
|
end
|
46
47
|
|
47
|
-
it "extracts Rails::Auth::
|
48
|
+
it "extracts Rails::Auth::Credential::X509 from a Java::SunSecurityX509::X509CertImpl" do
|
48
49
|
skip "JRuby only" unless defined?(JRUBY_VERSION)
|
49
50
|
|
50
51
|
_response, env = middleware.call(request.merge(example_key => java_cert))
|
51
52
|
|
52
|
-
|
53
|
-
expect(
|
53
|
+
credential = Rails::Auth.credentials(env).fetch("x509")
|
54
|
+
expect(credential).to be_a Rails::Auth::X509::Certificate
|
54
55
|
end
|
55
56
|
end
|
57
|
+
# :nocov:
|
56
58
|
end
|
57
59
|
|
58
60
|
describe "require_cert: true" do
|
@@ -61,8 +63,8 @@ RSpec.describe Rails::Auth::X509::Middleware do
|
|
61
63
|
it "functions normally for valid certificates" do
|
62
64
|
_response, env = middleware.call(request.merge(example_key => valid_cert_pem))
|
63
65
|
|
64
|
-
|
65
|
-
expect(
|
66
|
+
credential = Rails::Auth.credentials(env).fetch("x509")
|
67
|
+
expect(credential).to be_a Rails::Auth::X509::Certificate
|
66
68
|
end
|
67
69
|
|
68
70
|
it "raises Rails::Auth::X509::CertificateVerifyFailed for unverified certificates" do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
require "coveralls"
|
2
|
+
Coveralls.wear!
|
3
|
+
|
1
4
|
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
2
5
|
require "rails/auth"
|
3
6
|
require "rails/auth/rspec"
|
4
7
|
require "support/create_certs"
|
5
|
-
require "support/
|
8
|
+
require "support/claims_matcher"
|
6
9
|
require "pathname"
|
7
10
|
|
8
11
|
RSpec.configure(&:disable_monkey_patching!)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# A strawman matcher for claims-based credentials for use in tests
|
2
|
+
class ClaimsMatcher
|
3
|
+
def initialize(options)
|
4
|
+
@options = options
|
5
|
+
end
|
6
|
+
|
7
|
+
def match(_env)
|
8
|
+
# Pretend like we have a claim to be in the "example" group
|
9
|
+
@options["group"] == "example"
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Arcieri
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -65,6 +65,8 @@ files:
|
|
65
65
|
- ".rspec"
|
66
66
|
- ".rubocop.yml"
|
67
67
|
- ".travis.yml"
|
68
|
+
- BUG-BOUNTY.md
|
69
|
+
- CHANGES.md
|
68
70
|
- CONDUCT.md
|
69
71
|
- CONTRIBUTING.md
|
70
72
|
- Gemfile
|
@@ -77,33 +79,35 @@ files:
|
|
77
79
|
- lib/rails/auth/acl/matchers/allow_all.rb
|
78
80
|
- lib/rails/auth/acl/middleware.rb
|
79
81
|
- lib/rails/auth/acl/resource.rb
|
82
|
+
- lib/rails/auth/credentials.rb
|
83
|
+
- lib/rails/auth/error_page/middleware.rb
|
80
84
|
- lib/rails/auth/exceptions.rb
|
81
|
-
- lib/rails/auth/principals.rb
|
82
85
|
- lib/rails/auth/rack.rb
|
83
86
|
- lib/rails/auth/rspec.rb
|
84
87
|
- lib/rails/auth/rspec/helper_methods.rb
|
85
88
|
- lib/rails/auth/rspec/matchers/acl_matchers.rb
|
86
89
|
- lib/rails/auth/version.rb
|
90
|
+
- lib/rails/auth/x509/certificate.rb
|
87
91
|
- lib/rails/auth/x509/filter/java.rb
|
88
92
|
- lib/rails/auth/x509/filter/pem.rb
|
89
93
|
- lib/rails/auth/x509/matcher.rb
|
90
94
|
- lib/rails/auth/x509/middleware.rb
|
91
|
-
- lib/rails/auth/x509/principal.rb
|
92
95
|
- rails-auth.gemspec
|
93
96
|
- spec/fixtures/example_acl.yml
|
94
97
|
- spec/rails/auth/acl/matchers/allow_all_spec.rb
|
95
98
|
- spec/rails/auth/acl/middleware_spec.rb
|
96
99
|
- spec/rails/auth/acl/resource_spec.rb
|
97
100
|
- spec/rails/auth/acl_spec.rb
|
98
|
-
- spec/rails/auth/
|
101
|
+
- spec/rails/auth/credentials_spec.rb
|
102
|
+
- spec/rails/auth/error_page/middleware_spec.rb
|
99
103
|
- spec/rails/auth/rspec/helper_methods_spec.rb
|
100
104
|
- spec/rails/auth/rspec/matchers/acl_matchers_spec.rb
|
105
|
+
- spec/rails/auth/x509/certificate_spec.rb
|
101
106
|
- spec/rails/auth/x509/matcher_spec.rb
|
102
107
|
- spec/rails/auth/x509/middleware_spec.rb
|
103
|
-
- spec/rails/auth/x509/principal_spec.rb
|
104
108
|
- spec/rails/auth_spec.rb
|
105
109
|
- spec/spec_helper.rb
|
106
|
-
- spec/support/
|
110
|
+
- spec/support/claims_matcher.rb
|
107
111
|
- spec/support/create_certs.rb
|
108
112
|
homepage: https://github.com/square/rails-auth/
|
109
113
|
licenses:
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rails
|
4
|
-
# Modular resource-based authentication and authorization for Rails/Rack
|
5
|
-
module Auth
|
6
|
-
# Rack environment key for all rails-auth principals
|
7
|
-
PRINCIPALS_ENV_KEY = "rails-auth.principals".freeze
|
8
|
-
|
9
|
-
# Functionality for storing principals in the Rack environment
|
10
|
-
module Principals
|
11
|
-
# Obtain principals from a Rack environment
|
12
|
-
#
|
13
|
-
# @param [Hash] :env Rack environment
|
14
|
-
#
|
15
|
-
def principals(env)
|
16
|
-
env.fetch(PRINCIPALS_ENV_KEY, {})
|
17
|
-
end
|
18
|
-
|
19
|
-
# Add a principal to the Rack environment
|
20
|
-
#
|
21
|
-
# @param [Hash] :env Rack environment
|
22
|
-
# @param [String] :type principal type to add to the environment
|
23
|
-
# @param [Object] :principal principal object to add to the environment
|
24
|
-
#
|
25
|
-
def add_principal(env, type, principal)
|
26
|
-
principals = env[PRINCIPALS_ENV_KEY] ||= {}
|
27
|
-
|
28
|
-
fail ArgumentError, "principal #{type} already added to request" if principals.key?(type)
|
29
|
-
principals[type] = principal
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# Include these functions in Rails::Auth for convenience
|
34
|
-
extend Principals
|
35
|
-
end
|
36
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
RSpec.describe Rails::Auth::Principals do
|
2
|
-
describe "#principals" do
|
3
|
-
let(:example_type) { "example" }
|
4
|
-
let(:example_principals) { { example_type => double(:principal) } }
|
5
|
-
|
6
|
-
let(:example_env) do
|
7
|
-
env_for(:get, "/").tap do |env|
|
8
|
-
env[Rails::Auth::PRINCIPALS_ENV_KEY] = example_principals
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
it "extracts principals from Rack environments" do
|
13
|
-
expect(Rails::Auth.principals(example_env)).to eq example_principals
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
describe "#add_principal" do
|
18
|
-
let(:example_type) { "example" }
|
19
|
-
let(:example_principal) { double(:principal) }
|
20
|
-
let(:example_env) { env_for(:get, "/") }
|
21
|
-
|
22
|
-
it "adds principals to a Rack environment" do
|
23
|
-
expect(Rails::Auth.principals(example_env)[example_type]).to be_nil
|
24
|
-
Rails::Auth.add_principal(example_env, example_type, example_principal)
|
25
|
-
expect(Rails::Auth.principals(example_env)[example_type]).to eq example_principal
|
26
|
-
end
|
27
|
-
|
28
|
-
it "raises ArgumentError if the same type of principal is added twice" do
|
29
|
-
Rails::Auth.add_principal(example_env, example_type, example_principal)
|
30
|
-
|
31
|
-
expect do
|
32
|
-
Rails::Auth.add_principal(example_env, example_type, example_principal)
|
33
|
-
end.to raise_error(ArgumentError)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
RSpec.describe Rails::Auth::X509::Principal do
|
2
|
-
let(:example_cert) { OpenSSL::X509::Certificate.new(cert_path("valid.crt").read) }
|
3
|
-
let(:example_principal) { described_class.new(example_cert) }
|
4
|
-
|
5
|
-
let(:example_cn) { "127.0.0.1" }
|
6
|
-
let(:example_ou) { "ponycopter" }
|
7
|
-
|
8
|
-
describe "#[]" do
|
9
|
-
it "allows access to subject components via strings" do
|
10
|
-
expect(example_principal["CN"]).to eq example_cn
|
11
|
-
expect(example_principal["OU"]).to eq example_ou
|
12
|
-
end
|
13
|
-
|
14
|
-
it "allows access to subject components via symbols" do
|
15
|
-
expect(example_principal[:cn]).to eq example_cn
|
16
|
-
expect(example_principal[:ou]).to eq example_ou
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
it "knows its #cn" do
|
21
|
-
expect(example_principal.cn).to eq example_cn
|
22
|
-
end
|
23
|
-
|
24
|
-
it "knows its #ou" do
|
25
|
-
expect(example_principal.ou).to eq example_ou
|
26
|
-
end
|
27
|
-
end
|