From 452f6927c679cb719d57dd0150af66be89f88232 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 29 Jan 2016 12:03:42 -0800 Subject: [PATCH] Add Rails::Auth::ErrorPage::Middleware --- README.md | 38 +++++++++++++++++++ lib/rails/auth/error_page/middleware.rb | 21 ++++++++++ lib/rails/auth/rack.rb | 2 + spec/rails/auth/error_page/middleware_spec.rb | 26 +++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 lib/rails/auth/error_page/middleware.rb create mode 100644 spec/rails/auth/error_page/middleware_spec.rb diff --git a/README.md b/README.md index ef275df..25ef727 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,44 @@ The following matchers are available: * `allow_request`: allows a request with the given Rack environment, and optional principals +### Error Page Middleware + +When an authorization error occurs, the `Rails::Auth::NotAuthorizedError` +exception is raised up the middleware chain. However, it's likely you would +prefer to show an error page than have an unhandled exception. + +You can write your own middleware that catches `Rails::Auth::NotAuthorizedError` +if you'd like. However, a default one is provided which renders a 403 response +with a static page body if you find that helpful. + +To use it, add `Rails::Auth::ErrorPage::Middleware` to your app: + +``` +```ruby +app = MyRackApp.new + +acl = Rails::Auth::ACL.from_yaml( + File.read("/path/to/my/acl.yaml") + matchers: { allow_x509_subject: Rails::Auth::X509::Matcher } +) + +acl_auth = Rails::Auth::ACL::Middleware.new(app, acl: acl) + +x509_auth = Rails::Auth::X509::Middleware.new( + acl_auth, + ca_file: "/path/to/my/cabundle.pem" + cert_filters: { 'X-SSL-Client-Cert' => :pem }, + require_cert: true +) + +error_page = Rails::Auth::ErrorPage::Middleware.new( + x509_auth, + page_body: File.read("path/to/403.html") +) + +run error_page +``` + ## Contributing Any contributors to the master *rails-auth* repository must sign the diff --git a/lib/rails/auth/error_page/middleware.rb b/lib/rails/auth/error_page/middleware.rb new file mode 100644 index 0000000..0c66a58 --- /dev/null +++ b/lib/rails/auth/error_page/middleware.rb @@ -0,0 +1,21 @@ +module Rails + module Auth + module ErrorPage + # Render an error page in the event Rails::Auth::NotAuthorizedError is raised + class Middleware + def initialize(app, page_body: nil) + fail TypeError, "page_body must be a String" unless page_body.is_a?(String) + + @app = app + @page_body = page_body.freeze + end + + def call(env) + @app.call(env) + rescue Rails::Auth::NotAuthorizedError + [403, {}, [@page_body]] + end + end + end + end +end diff --git a/lib/rails/auth/rack.rb b/lib/rails/auth/rack.rb index 26feb9e..5461f4e 100644 --- a/lib/rails/auth/rack.rb +++ b/lib/rails/auth/rack.rb @@ -12,6 +12,8 @@ require "rails/auth/acl/middleware" require "rails/auth/acl/resource" +require "rails/auth/error_page/middleware" + require "rails/auth/x509/filter/pem" require "rails/auth/x509/filter/java" if defined?(JRUBY_VERSION) require "rails/auth/x509/matcher" diff --git a/spec/rails/auth/error_page/middleware_spec.rb b/spec/rails/auth/error_page/middleware_spec.rb new file mode 100644 index 0000000..ba26517 --- /dev/null +++ b/spec/rails/auth/error_page/middleware_spec.rb @@ -0,0 +1,26 @@ +RSpec.describe Rails::Auth::ErrorPage::Middleware do + let(:request) { Rack::MockRequest.env_for("https://www.example.com") } + let(:error_page) { "

Unauthorized!!!

" } + + subject(:middleware) { described_class.new(app, page_body: error_page) } + + context "access granted" do + let(:code) { 200 } + let(:app) { ->(env) { [code, env, "Hello, world!"] } } + + it "renders the expected response" do + response = middleware.call(request) + expect(response.first).to eq code + end + end + + context "access denied" do + let(:app) { ->(_env) { fail(Rails::Auth::NotAuthorizedError, "not authorized!") } } + + it "renders the error page" do + code, _env, body = middleware.call(request) + expect(code).to eq 403 + expect(body).to eq [error_page] + end + end +end