Sinatra-Bouncer
Simple permissions extension for Sinatra. Require the gem, then declare which routes are permitted based on your own logic.
Big Picture
Bouncer rules look like:
rules do
# Routes match based on one or more strings.
can get: '/',
post: %w[/user/sign-in
/user/sign-out]
# Wildcards match anything directly under that path
can get: '/lib/js/*'
# Use a conditional rule block for additional logic
can_sometimes get: '/admin/*',
post: '/admin/actions/*' do
current_user.admin?
end
# ... etc ...
end
Features
Here's what this Gem provides:
- Block-by-default
- Any route must be explicitly allowed
- Declarative Syntax
- Straightforward syntax reduces complexity of layered permissions
- Keeps permissions together for clarity
- Conditional Logic Via Blocks
- Often additional checks must be performed to know if a route is allowed.
- Grouping
- Routes can be matched by wildcard
- Forced Boolean Affirmation
- Condition blocks must explicitly return
true, avoiding accidental truthy values
- Condition blocks must explicitly return
Anti-Features
Bouncer intentionally does not support some concepts.
- No User Identification (aka Authentication)
- Bouncer is not a user identification gem. It assumes you already have an existing solution like Warden. Knowing who someone is is already a complex enough problem and should be separate from what they may do.
- No Rails Integration
- Bouncer is not intended to work with Rails. Use one of the Rails-based permissions gems for these situations.
- No Negation
- There is intentionally no negation (eg.
cannot) to preserve the default-deny frame of mind
- There is intentionally no negation (eg.
Installation
Add this to your gemfile:
gem 'sinatra-bouncer'
Then run:
bundle install
Sinatra Modular Style
require 'sinatra/base'
require 'sinatra/bouncer'
class MyApp < Sinatra::Base
register Sinatra::Bouncer
rules do
# ... can statements ...
end
# ... routes and other config
end
Sinatra Classic Style
require 'sinatra'
require 'sinatra/bouncer'
rules do
# ... can statements ...
end
# ... routes and other config
Usage
Call rules with a block that uses the #can and #can_sometimes DSL methods to declare rules for paths.
The rules block is run once as part of the configuration phase but the condition blocks are evaluated in the context of
the
request, which means you will have access to Sinatra helpers,
the request object, and params.
require 'sinatra'
require 'sinatra/bouncer'
rules do
# example: always allow GET requests to root path or /sign-in
can get: %w[/
/sign-in]
# example: logged in users can view (GET) member restricted paths and edit their account (POST)
can_sometimes get: '/members/*',
post: '/members/edit-account' do
!current_user.nil?
end
# example: check an arbitrary request header is present
can_sometimes get: '/bots/*' do
!request.get_header('X-CUSTOM_PROP').nil?
end
end
# ... Sinatra route declarations as normal ...
HTTP Method and Route Matching
Both #can and #can_sometimes accept multiple
HTTP methods as symbols
and each key is paired with one or more path strings.
# example: single method, single route
can get: '/'
# example: multiple methods, single route each
can get: '/',
post: '/blog/action/save'
# example: multiple methods, multiple routes (using string array syntax)
can get: %w[/
/sign-in
/blog/editor],
post: %w[/blog/action/save
/blog/action/delete]
Note Allowing GET implies allowing HEAD, since HEAD is by spec a GET without a response body. The reverse is not true, however; allowing HEAD will not also allow GET.
Wildcards and Special Symbols
Warning Always be cautious when using wildcards and special symbols to not accidentally open up pathways that should remain private.
Provide a wildcard * to match any string excluding slash. There is intentionally no syntax for matching wildcards
recursively, so nested paths will also need to be declared.
# example: match anything directly under the /members/ path
can get: '/members/*'
There are also 2 special symbols:
:anywill match any HTTP method.:allwill match all paths.
# this allows any method type on the / path
can any: '/'
# this allows GET on all paths
can get: :all
Always Allow: can
Any route declared with #can will be accepted without further challenge.
rules do
# Anyone can access this path over GET
can get: '/login'
end
Conditionally Allow: can_sometimes
can_sometimes is for occasions that you to check further, but want to defer that choice until the path is actually
attempted.
can_sometimes takes a block that will be run once the path is attempted. This block *must return an explicit boolean
*
(ie. true or false) to avoid any accidental truthy values creating unwanted access.
rules do
can_sometimes get: '/login' # Anyone can access this path over GET
can_sometimes post: '/user/blog/actions/save' do
!current_user.nil?
end
end
Custom Bounce Behaviour
The default bounce action is to halt 403. Call bounce_with with a block to specify your own behaviour. The block is
also run in a sinatra request context, so you can use helpers here as well.
require 'sinatra'
require 'sinatra/bouncer'
bounce_with do
redirect '/login'
end
# bouncer rules, routes, etc...
Alternatives
The syntax for Bouncer is largely influenced by the now-deprecated CanCan gem.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/TenjinInc/Sinatra-Bouncer.
Valued topics:
- Error messages (clarity, hinting)
- Documentation
- API
- Security correctness
This project is intended to be a friendly space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct. Play nice.
Core Developers
After checking out the repo, run bundle install to install dependencies. Then, run rake spec to run the tests. You
can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the
version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version,
push git commits and tags, and push the .gem file to rubygems.org.
Documentation is produced by Yard. Run bundle exec rake yard. The goal is to have 100% documentation coverage and 100%
test coverage.
Release notes are provided in RELEASE_NOTES.md, and should vaguely
follow Keep A Changelog recommendations.
License
The gem is available as open source under the terms of the MIT License.