Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### 0.1.0 (2016-02-10)

* [#6](https://github.com/square/rails-auth/pull/6):
Rename principals to credentials and Rails::Auth::X509::Principals to
Rails::Auth::X509::Certificates.
([@tarcieri])

* [#5](https://github.com/square/rails-auth/pull/5):
Add Rails::Auth::ErrorPage::Middleware.
([@tarcieri])

### 0.0.1 (2016-01-26)

* [#1](https://github.com/square/rails-auth/pull/1):
Initial implementation.
([@tarcieri])

### 0.0.0 (2016-01-04)

* Vaporware release to claim the "rails-auth" gem name


[@tarcieri]: https://github.com/tarcieri
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Modular resource-based authentication and authorization for Rails/Rack
Rails::Auth is a flexible library designed for both authentication (AuthN) and
authorization (AuthZ) using Rack Middleware. It splits the AuthN and AuthZ
steps into separate middleware classes, using AuthN middleware to first verify
request client identities, or "principals", then authorizing the request
via separate AuthZ middleware that consumes these principals, e.g. access
credentials (such as X.509 certificates or cookies), then authorizing the request
via separate AuthZ middleware that consumes these credentials, e.g. access
control lists (ACLs).

Rails::Auth can be used to authenticate and authorize end users using browser
Expand Down Expand Up @@ -45,7 +45,7 @@ middleware for your app.
Rails::Auth ships with the following middleware:

* **AuthN**: `Rails::Auth::X509::Middleware`: support for authenticating
principals by their SSL/TLS client certificates.
clients by their SSL/TLS client certificates.
* **AuthZ**: `Rails::Auth::ACL::Middleware`: support for authorizing requests
using Access Control Lists (ACLs).

Expand Down Expand Up @@ -110,7 +110,7 @@ app = MyRackApp.new

acl = Rails::Auth::ACL.from_yaml(
File.read("/path/to/my/acl.yaml"),
matchers: { allow_claims: MyClaimsPredicate }
matchers: { allow_claims: MyClaimsMatcher }
)

acl_auth = Rails::Auth::ACL::Middleware.new(app, acl: acl)
Expand All @@ -133,14 +133,14 @@ object from the ACL definition is passed to the class's `#initialize` method.
Here is an example of a simple custom predicate matcher:

```ruby
class MyClaimsPredicate
class MyClaimsMatcher
def initialize(options)
@options = options
end

def match(env)
claims = Rails::Auth.principals(env)["claims"]
return false unless principal
claims = Rails::Auth.credentials(env)["claims"]
return false unless credential

@options["groups"].any? { |group| claims["groups"].include?(group) }
end
Expand Down Expand Up @@ -229,9 +229,9 @@ certificates:
cert_filters: { 'X-SSL-Client-Cert' => proc { |pem| OpenSSL::X509::Certificate.new(pem) } }
```

When certificates are recognized and verified, an `Rails::Auth::X509::Principal`
object will be added to the Rack environment under `env["rails-auth.principals"]["x509"]`.
This middleware will never add any certificate to the environment's principals
When certificates are recognized and verified, a `Rails::Auth::X509::Certificate`
object will be added to the Rack environment under `env["rails-auth.credentials"]["x509"]`.
This middleware will never add any certificate to the environment's credentials
that hasn't been verified against the configured CA bundle.

## RSpec integration
Expand All @@ -249,7 +249,7 @@ Below is an example of how to write an ACL spec:

```ruby
RSpec.describe "example_acl.yml", acl_spec: true do
let(:example_principals) { x509_principal_hash(ou: "ponycopter") }
let(:example_credentials) { x509_certificate_hash(ou: "ponycopter") }

subject do
Rails::Auth::ACL.from_yaml(
Expand All @@ -259,15 +259,15 @@ RSpec.describe "example_acl.yml", acl_spec: true do
end

describe "/path/to/resource" do
it { is_expected.to permit get_request(principals: example_principals) }
it { is_expected.to permit get_request(credentials: example_credentials) }
it { is_expected.not_to permit get_request) }
end
end
```

The following helper methods are available:

* `x509_principal`, `x509_principal_hash`: create instance doubles of Rails::Auth::X509::Principals
* `x509_certificate`, `x509_certificate_hash`: create instance doubles of Rails::Auth::X509::Certificate
* Request builders: The following methods build requests from the described path:
* `get_request`
* `head_request`
Expand All @@ -281,7 +281,7 @@ The following helper methods are available:

The following matchers are available:

* `allow_request`: allows a request with the given Rack environment, and optional principals
* `allow_request`: allows a request with the given Rack environment, and optional credentials

### Error Page Middleware

Expand Down
2 changes: 1 addition & 1 deletion lib/rails/auth/acl/matchers/allow_all.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Auth
class ACL
# Built-in predicate matchers
module Matchers
# Allows all principals access to a given resource
# Allows unauthenticated clients to access to a given resource
class AllowAll
def initialize(enabled)
fail ArgumentError, "enabled must be true/false" unless [true, false].include?(enabled)
Expand Down
36 changes: 36 additions & 0 deletions lib/rails/auth/credentials.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Rails
# Modular resource-based authentication and authorization for Rails/Rack
module Auth
# Rack environment key for all rails-auth credentials
CREDENTIALS_ENV_KEY = "rails-auth.credentials".freeze

# Functionality for storing credentials in the Rack environment
module Credentials
# Obtain credentials from a Rack environment
#
# @param [Hash] :env Rack environment
#
def credentials(env)
env.fetch(CREDENTIALS_ENV_KEY, {})
end

# Add a credential to the Rack environment
#
# @param [Hash] :env Rack environment
# @param [String] :type credential type to add to the environment
# @param [Object] :credential object to add to the environment
#
def add_credential(env, type, credential)
credentials = env[CREDENTIALS_ENV_KEY] ||= {}

fail ArgumentError, "credential #{type} already added to request" if credentials.key?(type)
credentials[type] = credential
end
end

# Include these functions in Rails::Auth for convenience
extend Credentials
end
end
36 changes: 0 additions & 36 deletions lib/rails/auth/principals.rb

This file was deleted.

5 changes: 3 additions & 2 deletions lib/rails/auth/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
require "openssl"

require "rails/auth/version"

require "rails/auth/credentials"
require "rails/auth/exceptions"
require "rails/auth/principals"

require "rails/auth/acl"
require "rails/auth/acl/middleware"
require "rails/auth/acl/resource"

require "rails/auth/error_page/middleware"

require "rails/auth/x509/certificate"
require "rails/auth/x509/filter/pem"
require "rails/auth/x509/filter/java" if defined?(JRUBY_VERSION)
require "rails/auth/x509/matcher"
require "rails/auth/x509/middleware"
require "rails/auth/x509/principal"
20 changes: 10 additions & 10 deletions lib/rails/auth/rspec/helper_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ module Auth
module RSpec
# RSpec helper methods
module HelperMethods
# Creates an Rails::Auth::X509::Principal instance double
def x509_principal(cn: nil, ou: nil)
# Creates an Rails::Auth::X509::Certificate instance double
def x509_certificate(cn: nil, ou: nil)
subject = ""
subject << "CN=#{cn}" if cn
subject << "OU=#{ou}" if ou

instance_double(X509::Principal, subject, cn: cn, ou: ou).tap do |principal|
allow(principal).to receive(:[]) do |key|
instance_double(Rails::Auth::X509::Certificate, subject, cn: cn, ou: ou).tap do |certificate|
allow(certificate).to receive(:[]) do |key|
{
"CN" => cn,
"OU" => ou
Expand All @@ -19,13 +19,13 @@ def x509_principal(cn: nil, ou: nil)
end
end

# Creates a principals hash containing a single X.509 principal instance double
def x509_principal_hash(**args)
{ "x509" => x509_principal(**args) }
# Creates a certificates hash containing a single X.509 certificate instance double
def x509_certificate_hash(**args)
{ "x509" => x509_certificate(**args) }
end

Rails::Auth::ACL::Resource::HTTP_METHODS.each do |method|
define_method("#{method.downcase}_request") do |principals: {}|
define_method("#{method.downcase}_request") do |certificates: {}|
path = self.class.description

# Warn if methods are improperly used
Expand All @@ -38,8 +38,8 @@ def x509_principal_hash(**args)
"REQUEST_PATH" => self.class.description
}

principals.each do |type, value|
Rails::Auth.add_principal(env, type.to_s, value)
certificates.each do |type, value|
Rails::Auth.add_credential(env, type.to_s, value)
end

env
Expand Down
10 changes: 5 additions & 5 deletions lib/rails/auth/rspec/matchers/acl_matchers.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
RSpec::Matchers.define(:permit) do |env|
description do
method = env["REQUEST_METHOD"]
principals = Rails::Auth.principals(env)
message = "allow #{method}s by "
method = env["REQUEST_METHOD"]
credentials = Rails::Auth.credentials(env)
message = "allow #{method}s by "

return message << "unauthenticated clients" if principals.count.zero?
return message << "unauthenticated clients" if credentials.count.zero?

message << principals.values.map(&:inspect).join(", ")
message << credentials.values.map(&:inspect).join(", ")
end

match { |acl| acl.match(env) }
Expand Down
2 changes: 1 addition & 1 deletion lib/rails/auth/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
module Rails
# Pluggable authentication and authorization for Rack/Rails
module Auth
VERSION = "0.0.1".freeze
VERSION = "0.1.0".freeze
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
module Rails
module Auth
module X509
# HTTPS principal identified by an X.509 client certificate
class Principal
# X.509 client certificates obtained from HTTP requests
class Certificate
attr_reader :certificate

def initialize(certificate)
Expand Down
2 changes: 1 addition & 1 deletion lib/rails/auth/x509/filter/java.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Rails
module Auth
module X509
module Filter
# Support for extracting X509::Principals from Java's sun.security.x509.X509CertImpl
# Extract OpenSSL::X509::Certificates from Java's sun.security.x509.X509CertImpl
class Java
def call(cert)
OpenSSL::X509::Certificate.new(extract_der(cert)).freeze
Expand Down
2 changes: 1 addition & 1 deletion lib/rails/auth/x509/filter/pem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Rails
module Auth
module X509
module Filter
# Support for extracting X509::Principals from Privacy Enhanced Mail (PEM) certificates
# Extract OpenSSL::X509::Certificates from Privacy Enhanced Mail (PEM) certificates
class Pem
def call(pem)
OpenSSL::X509::Certificate.new(pem).freeze
Expand Down
8 changes: 4 additions & 4 deletions lib/rails/auth/x509/matcher.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Rails
module Auth
module X509
# Predicate matcher for making assertions about X.509 principals
# Predicate matcher for making assertions about X.509 certificates
class Matcher
# @option options [String] cn Common Name of the subject
# @option options [String] ou Organizational Unit of the subject
Expand All @@ -11,10 +11,10 @@ def initialize(options)

# @param [Hash] env Rack environment
def match(env)
principal = Rails::Auth.principals(env)["x509"]
return false unless principal
certificate = Rails::Auth.credentials(env)["x509"]
return false unless certificate

@options.all? { |name, value| principal[name] == value }
@options.all? { |name, value| certificate[name] == value }
end
end
end
Expand Down
12 changes: 6 additions & 6 deletions lib/rails/auth/x509/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ module X509
# Raised when certificate verification is mandatory
CertificateVerifyFailed = Class.new(NotAuthorizedError)

# Validates X.509 client certificates and adds principal objects for valid
# clients to the rack environment as env["rails-auth.principals"]["x509"]
# Validates X.509 client certificates and adds credential objects for valid
# clients to the rack environment as env["rails-auth.credentials"]["x509"]
class Middleware
# Create a new X.509 Middleware object
#
Expand Down Expand Up @@ -36,15 +36,15 @@ def initialize(app, cert_filters: {}, ca_file: nil, truststore: nil, require_cer
end

def call(env)
principal = extract_principal(env)
Rails::Auth.add_principal(env, "x509".freeze, principal.freeze) if principal
credential = extract_credential(env)
Rails::Auth.add_credential(env, "x509".freeze, credential.freeze) if credential

@app.call(env)
end

private

def extract_principal(env)
def extract_credential(env)
@cert_filters.each do |key, filter|
raw_cert = env[key]
next unless raw_cert
Expand All @@ -54,7 +54,7 @@ def extract_principal(env)

if @truststore.verify(cert)
log("Verified", cert)
return Rails::Auth::X509::Principal.new(cert)
return Rails::Auth::X509::Certificate.new(cert)
else
log("Verify FAILED", cert)
fail CertificateVerifyFailed, "verify failed: #{subject(cert)}" if @require_cert
Expand Down
2 changes: 1 addition & 1 deletion spec/rails/auth/acl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
example_config,
matchers: {
allow_x509_subject: Rails::Auth::X509::Matcher,
allow_claims: ClaimsPredicate
allow_claims: ClaimsMatcher
}
)
end
Expand Down
Loading