rails-auth 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/rails-auth.svg)](http://rubygems.org/gems/rails-auth)
|
4
|
+
[![Build Status](https://travis-ci.org/square/rails-auth.svg?branch=master)](https://travis-ci.org/square/rails-auth)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/square/rails-auth/badges/gpa.svg)](https://codeclimate.com/github/square/rails-auth)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/github/square/rails-auth/badge.svg?branch=master)](https://coveralls.io/github/square/rails-auth?branch=master)
|
7
|
+
[![Apache 2 licensed](https://img.shields.io/badge/license-Apache2-blue.svg)](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
|