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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a492a2d3e2769b62746079b9c9d0d56fa13d9c56
4
- data.tar.gz: 6eda7789839d1bb66b642b3c6af8f3ad213f98d9
3
+ metadata.gz: 7f4c0d9519675ef1b560bbc2cc2ef0dcc2b9b178
4
+ data.tar.gz: 2cd1b6f0f0f2462db7ab4bd280f1c54dc6bff513
5
5
  SHA512:
6
- metadata.gz: ad6c4c9707ff5067b0e16f2e78f5afd66602b954a828e1a346128c318c5eeff4fdc2da755e3144a5b92c29f4063aff9c1e2645dad1bd420bb34edb08442ffe88
7
- data.tar.gz: fe5328588172103e3ef15343dfef43b83261db496830375394a5d0151af5f939e39ce5514db5b0e474ff8cdc861a6ab8ef9137d628655f2034ede34d5bf987bf
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
@@ -7,6 +7,7 @@ end
7
7
  group :test do
8
8
  gem "rspec"
9
9
  gem "rubocop", "0.36.0"
10
+ gem "coveralls", require: false
10
11
  gem "certificate_authority", require: false
11
12
  end
12
13
 
data/README.md CHANGED
@@ -1,4 +1,10 @@
1
- # Rails::Auth
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
- request client identities, or "principals", then authorizing the request
11
- via separate AuthZ middleware that consumes these principals, e.g. access
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
- principals by their SSL/TLS client certificates.
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: MyClaimsPredicate }
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 MyClaimsPredicate
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.principals(env)["claims"]
137
- return false unless principal
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, an `Rails::Auth::X509::Principal`
227
- object will be added to the Rack environment under `env["rails-auth.principals"]["x509"]`.
228
- This middleware will never add any certificate to the environment's principals
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(:example_principals) { x509_principal_hash(ou: "ponycopter") }
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(principals: example_principals) }
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
- * `x509_principal`, `x509_principal_hash`: create instance doubles of Rails::Auth::X509::Principals
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 principals
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 all principals access to a given resource
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
@@ -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::Principal instance double
7
- def x509_principal(cn: nil, ou: nil)
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::Principal, subject, cn: cn, ou: ou).tap do |principal|
13
- allow(principal).to receive(:[]) do |key|
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 principals hash containing a single X.509 principal instance double
23
- def x509_principal_hash(**args)
24
- { "x509" => x509_principal(**args) }
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 |principals: {}|
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
- principals.each do |type, value|
42
- Rails::Auth.add_principal(env, type.to_s, value)
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 = env["REQUEST_METHOD"]
4
- principals = Rails::Auth.principals(env)
5
- message = "allow #{method}s by "
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 principals.count.zero?
7
+ return message << "unauthenticated clients" if credentials.count.zero?
8
8
 
9
- message << principals.values.map(&:inspect).join(", ")
9
+ message << credentials.values.map(&:inspect).join(", ")
10
10
  end
11
11
 
12
12
  match { |acl| acl.match(env) }
@@ -3,6 +3,6 @@
3
3
  module Rails
4
4
  # Pluggable authentication and authorization for Rack/Rails
5
5
  module Auth
6
- VERSION = "0.0.1".freeze
6
+ VERSION = "0.1.0".freeze
7
7
  end
8
8
  end
@@ -3,8 +3,8 @@
3
3
  module Rails
4
4
  module Auth
5
5
  module X509
6
- # HTTPS principal identified by an X.509 client certificate
7
- class Principal
6
+ # X.509 client certificates obtained from HTTP requests
7
+ class Certificate
8
8
  attr_reader :certificate
9
9
 
10
10
  def initialize(certificate)
@@ -5,7 +5,7 @@ module Rails
5
5
  module Auth
6
6
  module X509
7
7
  module Filter
8
- # Support for extracting X509::Principals from Java's sun.security.x509.X509CertImpl
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
- # Support for extracting X509::Principals from Privacy Enhanced Mail (PEM) certificates
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 principals
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
- principal = Rails::Auth.principals(env)["x509"]
15
- return false unless principal
14
+ certificate = Rails::Auth.credentials(env)["x509"]
15
+ return false unless certificate
16
16
 
17
- @options.all? { |name, value| principal[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 principal objects for valid
10
- # clients to the rack environment as env["rails-auth.principals"]["x509"]
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
- principal = extract_principal(env)
40
- Rails::Auth.add_principal(env, "x509".freeze, principal.freeze) if principal
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 extract_principal(env)
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::Principal.new(cert)
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
@@ -6,7 +6,7 @@ RSpec.describe Rails::Auth::ACL do
6
6
  example_config,
7
7
  matchers: {
8
8
  allow_x509_subject: Rails::Auth::X509::Matcher,
9
- allow_claims: ClaimsPredicate
9
+ allow_claims: ClaimsMatcher
10
10
  }
11
11
  )
12
12
  end
@@ -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 "#x509_principal" do
6
- subject { x509_principal(cn: example_cn, ou: example_ou) }
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::Principals" do
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 "#x509_principal_hash" do
20
- subject { x509_principal_hash(cn: example_cn, ou: example_ou) }
19
+ describe "#x509_certificate_hash" do
20
+ subject { x509_certificate_hash(cn: example_cn, ou: example_ou) }
21
21
 
22
- it "creates a principal hash with an Rails::Auth::X509::Principal double" do
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(:example_principal) { x509_principal_hash(ou: "ponycopter") }
3
- let(:another_principal) { x509_principal_hash(ou: "derpderp") }
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: ClaimsPredicate
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(principals: example_principal) }
17
- it { is_expected.not_to permit get_request(principals: another_principal) }
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) { OpenSSL::X509::Certificate.new(cert_path("valid.crt").read) }
3
- let(:example_principal) { Rails::Auth::X509::Principal.new(example_cert) }
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::PRINCIPALS_ENV_KEY => { "x509" => example_principal } }
9
+ { Rails::Auth::CREDENTIALS_ENV_KEY => { "x509" => example_certificate } }
10
10
  end
11
11
 
12
- it "matches against a valid Rails::Auth::X509::Principal" do
13
- predicate = described_class.new(ou: example_ou)
14
- expect(predicate.match(example_env)).to eq true
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
- predicate = described_class.new(ou: another_ou)
19
- expect(predicate.match(example_env)).to eq false
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::Principal::X509 from a PEM certificate in the Rack environment" do
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
- principal = Rails::Auth.principals(env).fetch("x509")
29
- expect(principal).to be_a Rails::Auth::X509::Principal
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.principals(env)).to be_empty
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::Principal::X509 from a Java::SunSecurityX509::X509CertImpl" do
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
- principal = Rails::Auth.principals(env).fetch("x509")
53
- expect(principal).to be_a Rails::Auth::X509::Principal
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
- principal = Rails::Auth.principals(env).fetch("x509")
65
- expect(principal).to be_a Rails::Auth::X509::Principal
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/claims_predicate"
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.1
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-01-26 00:00:00.000000000 Z
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/principals_spec.rb
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/claims_predicate.rb
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
@@ -1,11 +0,0 @@
1
- # An additional predicate class for tests
2
- class ClaimsPredicate
3
- def initialize(options)
4
- @options = options
5
- end
6
-
7
- def match(_env)
8
- # Pretend like our principal is in the "example" group
9
- @options["group"] == "example"
10
- end
11
- end