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 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