Hyerarchical scopes definition and validation for Bolder apps and APIs.
gem 'bolder_scopes'
Scopes are permission trees.
For example, the scope all.users.create represents the following structure:
all
users
create
A request's token scope is compared with a given endpoint's token to check access permissions, from left to right.
allhas access toallallhas access toall.usersall.usershas access toall.users.createall.accountsdoes NOT have access toall.usersall.accountsdoes NOT have access toall.users.create
Wildcard scopes are possible using the special character * as one or more segments in a scope.
For example:
all.*.createhas access toall.accounts.createorall.photos.createall.*.createhas access toall.accounts.*all.*.create.*does not have access toall.accounts.create(because it's more specific).
def index
request_scope = access_token.scope # ex. bolder.accounts.123.shops.*.read
resource_scope = Bolder::Scopes.wrap(['bolder', 'accounts', current_account.id, 'shops', 'read'].join('.'))
if request_scope >= resource_scope
render
else
render :unauthorized, status: 403
end
endDefining scopes as strings can be error prone (easy to make typos or get the hierarchy wrong!).
The Bolder::Scopes::Tree utility can be helpful to define all possible scope hierarchies in a single place.
SCOPES = Bolder::Scopes::Tree.new('all') do |all|
all.users.update
all.users.read
all.users.create
all.orders.read
endThe scope tree will expose all defined hierarchies
SCOPES.all.users # 'all.users'
SCOPES.all.users.create # 'all.users.create'... But not invalid hierarchies.
SCOPES.all.users.orders # => raises Bolder::Scopes::Scope::InvalidScopHierarchyErrorWilcards work too
SCOPES.all.*.read # 'all.*.read`Note that wildcards only allow sub-scopes that are shared by all children.
SCOPES.all.*.read # Ok
SCOPES.all.*.update # raises InvalidScopHierarchyError because not all children of `all.*` support `update`Hierarchies can also be defined using the > operator: This can help avoid typos.
SCOPES = Bolder::Scopes::Tree.new('bolder') do |bolder|
api = 'api'
products = 'products'
orders = 'orders'
own = 'own'
all = 'all'
read = 'read'
bolder > api > products > own > read
bolder > api > products > all > read
bolder > api > orders > own > read
endBlock notation can be used where it makes sense:
SCOPES = Bolder::Scopes::Tree.new('bolder') do |bolder|
bolder.api.products do |n|
n.own do |n|
n.read
n.write
n > 'list' # use `>` to append variables or constants
end
end
endBlock notation also works without explicit node argument (but can't access outer variables):
SCOPES = Bolder::Scopes::Tree.new('bolder') do
api.products do
own do
read
write
end
all do
read
end
end
endUse _any to define segments that can be anything:
SCOPES = Bolder::Scopes::Tree.new('bolder') do |bolder|
bolder.api.products._any.read
end_any takes an optional list of allowed values, in which case it has "any of" semantics.
Values are matched with #=== operator, so they can be regular expressions.
If no values are given, _any has "anything" semantics.
_any can be used to define a catch-all scope:
SCOPES = Bolder::Scopes::Tree.new('bolder') do |bolder|
bolder.api do |s|
s.products do |s|
s._any('my_products', /^\d+$/) do |s| # matches 'my_products' or any number-like string
s.read
end
end
endWith the above, the following scopes are allowed, using parenthesis notation to allow numbers and multiple values
bolder.api.products.(123).read # 'bolder.api.products.123.read'
bolder.api.products.(1, 2, 3).read # 'bolder.api.products.(1,2,3).read'
bolder.api.products.('my_products').read # 'bolder.api.products.my_products.read'
bolder.api.products.my_products.read # works tooBolder::Scopes::Map can be used to expand one scope to others.
map = Bolder::Scopes::Map.new('read' => ['read.users'])
map.map('read') # => ['read.users']
map.map('read:users') # => ['read.users']Use Map#expand to include the original scope in the resulting list.
map = Bolder::Scopes::Map.new('read' => 'read.users')
map.expand('read') # => Scopes['read', 'read.users']Scope trees also work with scope maps.
map = Bolder::Scopes::Map.new(
SCOPES.admin => [SCOPES.api.products.own, SCOPES.api.orders.own, SCOPES.api.all.read],
'god' => [SCOPES.api]
)After checking out the repo, run bin/setup 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 the created tag, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/bolder/bolder_scopes.