sand 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +6 -0
- data/.rdoc_options +16 -0
- data/.rspec +1 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +8 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +26 -0
- data/lib/sand.rb +44 -0
- data/lib/sand/helpers.rb +35 -0
- data/lib/sand/middleware.rb +40 -0
- data/lib/sand/policy_finder.rb +48 -0
- data/lib/sand/util.rb +7 -0
- data/lib/sand/version.rb +3 -0
- data/sand.gemspec +33 -0
- data/spec/.rubocop.yml +2 -0
- data/spec/application/rack_spec.rb +11 -0
- data/spec/application/shared_application_examples.rb +72 -0
- data/spec/application/sinatra_spec.rb +12 -0
- data/spec/sand_spec.rb +97 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/rack_app.rb +47 -0
- data/spec/support/shared_application_code.rb +58 -0
- data/spec/support/sinatra_app.rb +46 -0
- metadata +234 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3e35fa0c449b867bf7a2a4ca9896bcef5dab595c
|
4
|
+
data.tar.gz: 97ae09fb52cb0a5e79f3a026682a8ee136dc7a47
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e207da09669c379140f79f5b976f61f9a19479ea4b883112beaa0853588f7fc4da5f543febee4c7b6fb68b532d49b17a077cdf980ea103f063ad3a37c077e58a
|
7
|
+
data.tar.gz: 81a1995877fdeb8491961fde669fde5ad89a29cdb16c2d000ef0d02ed6754ffd1d96ee8b6449fe30493a6b2f892efa95c12bb34f96bddf96e5ce3b4c9d67c954
|
data/.document
ADDED
data/.gitignore
ADDED
data/.rdoc_options
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
--- !ruby/object:RDoc::Options
|
2
|
+
encoding: UTF-8
|
3
|
+
static_path: []
|
4
|
+
rdoc_include:
|
5
|
+
- .
|
6
|
+
charset: UTF-8
|
7
|
+
exclude:
|
8
|
+
hyperlink_all: false
|
9
|
+
line_numbers: false
|
10
|
+
main_page: README.md
|
11
|
+
markup: markdown
|
12
|
+
show_hash: false
|
13
|
+
tab_width: 8
|
14
|
+
title: sand Documentation
|
15
|
+
visibility: :protected
|
16
|
+
webcvs:
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Rack Specific modifications Copyright (c) 2016 Nick Tomlin
|
2
|
+
Otherwise Copyright (c) 2012 Jonas Nicklas, Elabs AB
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
sand [![Build Status](https://travis-ci.org/NickTomlin/ruby-sand.svg?branch=master)](https://travis-ci.org/NickTomlin/ruby-sand)
|
2
|
+
===
|
3
|
+
|
4
|
+
A ruby gem for authorization in rack/sinatra applications. Code mostly stolen from [Pundit](https://github.com/elabs/pundit).
|
5
|
+
|
6
|
+
Usage
|
7
|
+
---
|
8
|
+
|
9
|
+
The [Pundit policy](https://github.com/elabs/pundit#policies) documentation provides an excellent introduction into creating defining policies.
|
10
|
+
|
11
|
+
Once you've built your policies, you can start to use sand. By default, you can include sand in your rack application like so:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'sand'
|
15
|
+
use Sand::Middleware
|
16
|
+
|
17
|
+
class MyModel < MyOrm::Model
|
18
|
+
# ...
|
19
|
+
end
|
20
|
+
|
21
|
+
class MyModelPolicy
|
22
|
+
# ...
|
23
|
+
end
|
24
|
+
|
25
|
+
class Routes
|
26
|
+
env['sand'].authorize(user, MyModel, :can_greet?)
|
27
|
+
[200, {}, ['Hello world']]
|
28
|
+
end
|
29
|
+
|
30
|
+
MyRackApp = Rack::Builder.new do
|
31
|
+
use Sand::Middleware
|
32
|
+
run SandApp.new
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
This will add `authorize` and `policy_scope` underneath env['sand'], that you can call in your middleware / routes.
|
37
|
+
|
38
|
+
Sinatra users can access sand's middleware via helpers by adding `Sand::Helpers`:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
require 'sinatra'
|
42
|
+
|
43
|
+
use Sand::Helpers
|
44
|
+
|
45
|
+
get '/' do
|
46
|
+
user = User.find(params[:user_id])
|
47
|
+
accounts = policy_scope(user, Account)
|
48
|
+
json accounts: accounts
|
49
|
+
end
|
50
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bundler/setup'
|
7
|
+
rescue LoadError => e
|
8
|
+
abort e.message
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rake'
|
12
|
+
|
13
|
+
require 'rubygems/tasks'
|
14
|
+
Gem::Tasks.new
|
15
|
+
|
16
|
+
require 'rdoc/task'
|
17
|
+
RDoc::Task.new
|
18
|
+
task doc: :rdoc
|
19
|
+
|
20
|
+
require 'rspec/core/rake_task'
|
21
|
+
RSpec::Core::RakeTask.new
|
22
|
+
|
23
|
+
require 'rubocop/rake_task'
|
24
|
+
RuboCop::RakeTask.new
|
25
|
+
|
26
|
+
task default: %w(rubocop spec)
|
data/lib/sand.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'sand/version'
|
2
|
+
require 'sand/policy_finder'
|
3
|
+
require 'sand/util'
|
4
|
+
require 'sand/helpers'
|
5
|
+
require 'sand/middleware'
|
6
|
+
|
7
|
+
module Sand
|
8
|
+
class Error < StandardError; end
|
9
|
+
class NotDefinedError < Error; end
|
10
|
+
class AuthorizationNotPerformed < Error; end
|
11
|
+
class ScopeNotPerformed < Error; end
|
12
|
+
|
13
|
+
class NotAuthorizedError < Error
|
14
|
+
attr_reader :record, :query, :policy
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
@record = options[:record]
|
18
|
+
@query = options[:query]
|
19
|
+
@policy = options[:policy]
|
20
|
+
|
21
|
+
super("not allowed to #{query} this #{record.inspect}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.authorize(user, record, query)
|
26
|
+
policy = policy!(user, record)
|
27
|
+
|
28
|
+
unless policy.public_send(query)
|
29
|
+
raise NotAuthorizedError.new(policy: policy, record: record, query: query) # rubocop:disable Style/RaiseArgs, Metrics/LineLength
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.policy!(user, record)
|
36
|
+
policy = PolicyFinder.new(record).policy!
|
37
|
+
policy.new(user, record)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.policy_scope(user, scope)
|
41
|
+
policy_scope = PolicyFinder.new(scope).scope!
|
42
|
+
policy_scope.new(user, scope).resolve
|
43
|
+
end
|
44
|
+
end
|
data/lib/sand/helpers.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Sand
|
2
|
+
module Helpers
|
3
|
+
def policy_scope(user, record)
|
4
|
+
scoped!
|
5
|
+
scope = PolicyFinder.new(record).scope!
|
6
|
+
scope.new(user, record).resolve if scope
|
7
|
+
end
|
8
|
+
|
9
|
+
def authorize(user, record, query)
|
10
|
+
authorized!
|
11
|
+
policy = Sand.policy!(user, record)
|
12
|
+
unless policy.public_send(query)
|
13
|
+
raise Sand::NotAuthorizedError.new(policy: policy, query: query, record: record) # rubocop:disable Metrics/LineLength, Style/RaiseArgs
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def skip_sand_authorization
|
18
|
+
authorized!
|
19
|
+
end
|
20
|
+
|
21
|
+
def skip_sand_scoping
|
22
|
+
scoped!
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def authorized!
|
28
|
+
env['sand.authorized'] = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def scoped!
|
32
|
+
env['sand.scoped'] = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Sand
|
2
|
+
class RequestMethods
|
3
|
+
attr_accessor :env
|
4
|
+
include Sand::Helpers
|
5
|
+
|
6
|
+
def initialize(env)
|
7
|
+
@env = env
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Middleware
|
12
|
+
attr_reader :app, :env, :options, :response
|
13
|
+
|
14
|
+
DEFAULT_OPTIONS = {
|
15
|
+
pass: []
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def initialize(app, options = {})
|
19
|
+
@env = nil
|
20
|
+
@app = app
|
21
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def passed?
|
25
|
+
return true if options[:pass].any? { |r| r =~ env['REQUEST_PATH'] }
|
26
|
+
return true if env['sand.pass'] == true || env['sand.scoped'] || env['sand.authorized'] == true # rubocop:disable Metrics/LineLength
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(env)
|
31
|
+
@env = env
|
32
|
+
env['sand'] = RequestMethods.new(env)
|
33
|
+
|
34
|
+
result = app.call(env)
|
35
|
+
|
36
|
+
return result if passed?
|
37
|
+
raise Sand::AuthorizationNotPerformed
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Sand
|
2
|
+
class PolicyFinder
|
3
|
+
POLICY_SUFFIX = 'Policy'.freeze
|
4
|
+
|
5
|
+
def initialize(object)
|
6
|
+
@object = object
|
7
|
+
end
|
8
|
+
|
9
|
+
def scope!
|
10
|
+
policy!::Scope
|
11
|
+
rescue NameError
|
12
|
+
raise NotDefinedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def policy!
|
16
|
+
klass = find
|
17
|
+
klass = Util.constantize(klass) if klass.is_a?(String)
|
18
|
+
klass
|
19
|
+
rescue NameError
|
20
|
+
raise NotDefinedError
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/LineLength
|
26
|
+
def find
|
27
|
+
klass = @object
|
28
|
+
return klass if @object.nil?
|
29
|
+
|
30
|
+
if @object.respond_to?(:sand_policy)
|
31
|
+
@object.sand_policy
|
32
|
+
elsif @object.class.respond_to?(:sand_policy)
|
33
|
+
@object.sand_policy
|
34
|
+
else
|
35
|
+
klass = if @object.is_a?(Symbol)
|
36
|
+
@object.to_s.camelize
|
37
|
+
elsif @object.respond_to?(:model)
|
38
|
+
@object.model
|
39
|
+
elsif @object.respond_to?(:model_class)
|
40
|
+
@object.model_class
|
41
|
+
else
|
42
|
+
@object.to_s
|
43
|
+
end
|
44
|
+
"#{klass}#{POLICY_SUFFIX}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/sand/util.rb
ADDED
data/lib/sand/version.rb
ADDED
data/sand.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'sand/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = 'sand'
|
9
|
+
gem.version = Sand::VERSION
|
10
|
+
gem.summary = 'Authorization for rack-based applications'
|
11
|
+
gem.description = 'A ruby gem for authorization for use in sinatra/rack applications. Heavily inspired [Pundit](https://github.com/elabs/pundit)' # rubocop:disable Metrics/LineLength
|
12
|
+
gem.license = 'MIT'
|
13
|
+
gem.authors = ['Nick Tomlin']
|
14
|
+
gem.email = 'nick.tomlin@gmail.com'
|
15
|
+
gem.homepage = 'https://rubygems.org/gems/sand'
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
|
+
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ['lib']
|
21
|
+
|
22
|
+
gem.add_development_dependency 'bundler', '~> 1.10'
|
23
|
+
gem.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
gem.add_development_dependency 'rdoc', '~> 4.0'
|
25
|
+
gem.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
27
|
+
gem.add_development_dependency 'rubocop', '~> 0.37.0'
|
28
|
+
gem.add_development_dependency 'pry', '0.10.3'
|
29
|
+
gem.add_development_dependency 'rack-test', '~> 0.6.3'
|
30
|
+
gem.add_development_dependency 'sinatra', '~> 1.4.7'
|
31
|
+
gem.add_development_dependency 'sequel', '~> 4.31'
|
32
|
+
gem.add_development_dependency 'sqlite3'
|
33
|
+
end
|
data/spec/.rubocop.yml
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
RSpec.shared_examples 'RackApplications' do
|
2
|
+
include Rack::Test::Methods
|
3
|
+
|
4
|
+
before(:each) do
|
5
|
+
# poor man's database cleaner
|
6
|
+
User.all.each(&:delete)
|
7
|
+
Account.all.each(&:delete)
|
8
|
+
end
|
9
|
+
|
10
|
+
let!(:user) { User.create(name: 'Normal User', admin: false) }
|
11
|
+
let!(:admin_user) { User.create(name: 'Admin User', admin: true) }
|
12
|
+
|
13
|
+
describe 'finding resources via policy_scope' do
|
14
|
+
it "returns resources based on the Policy's Scope.resolve for 'admin' users" do
|
15
|
+
3.times { |x| Account.create(title: "Account #{x}") }
|
16
|
+
get "/users/#{admin_user.id}/accounts"
|
17
|
+
|
18
|
+
expect(last_response).to be_ok
|
19
|
+
body = JSON.parse(last_response.body)
|
20
|
+
expect(body.size).to eq(3)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns resources based on the Policy's Scope.resolve for 'normal'" do
|
24
|
+
2.times do |x|
|
25
|
+
account = Account.create(title: "Owned Account #{x}")
|
26
|
+
Role.create(role: 'Normal', user_id: user.id, account_id: account.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
Account.create(title: 'Not owned Account')
|
30
|
+
|
31
|
+
get "/users/#{user.id}/accounts"
|
32
|
+
|
33
|
+
expect(last_response).to be_ok
|
34
|
+
body = JSON.parse(last_response.body)
|
35
|
+
expect(body.size).to eq(2)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'verify_scoped' do
|
40
|
+
it 'raises a Sand::NotAuthorizedError if policy has not been authorized' do
|
41
|
+
expect do
|
42
|
+
get '/verify_scoped/fail'
|
43
|
+
end.to raise_error Sand::AuthorizationNotPerformed
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does nothing if resource has been authorized' do
|
47
|
+
get '/verify_scoped/succeed'
|
48
|
+
|
49
|
+
expect(last_response).to be_ok
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'allows users to skip authorization' do
|
53
|
+
get '/verify_scoped/pass'
|
54
|
+
|
55
|
+
expect(last_response).to be_ok
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'verify_authorized' do
|
60
|
+
before { Account.create(title: 'Test') }
|
61
|
+
it 'raises an error if resource has not been authorized' do
|
62
|
+
expect do
|
63
|
+
get '/verify_authorized/fail'
|
64
|
+
end.to raise_error Sand::NotAuthorizedError
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does nothing if resource has been authorized' do
|
68
|
+
get '/verify_authorized/success'
|
69
|
+
expect(last_response).to be_ok
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/sand_spec.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Sand do
|
4
|
+
class PostPolicy < Struct.new(:user, :post) # rubocop:disable Style/StructInheritance
|
5
|
+
def update?
|
6
|
+
post.user == user
|
7
|
+
end
|
8
|
+
|
9
|
+
def destroy?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def show?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class PostPolicy::Scope < Struct.new(:user, :scope) # rubocop:disable Style/StructInheritance, Style/ClassAndModuleChildren
|
19
|
+
def resolve
|
20
|
+
scope.published
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Post < Struct.new(:user) # rubocop:disable Style/StructInheritance
|
25
|
+
def self.published
|
26
|
+
:published
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
'Post'
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
'#<Post>'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:user) { double }
|
39
|
+
let(:post) { Post.new(user) }
|
40
|
+
|
41
|
+
describe '.authorize' do
|
42
|
+
it 'infers the policy and authorizes based on it' do
|
43
|
+
expect(Sand.authorize(user, post, :update?)).to be_truthy
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'raises an error with a query and action' do
|
47
|
+
expect { Sand.authorize(user, post, :destroy?) }.to raise_error(Sand::NotAuthorizedError, 'not allowed to destroy? this #<Post>') do |error|
|
48
|
+
expect(error.query).to eq :destroy?
|
49
|
+
expect(error.record).to eq post
|
50
|
+
expect(error.policy).to eq Sand.policy!(user, post)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '.policy_scope' do
|
56
|
+
it 'returns an instantiated policy scope given a plain model class' do
|
57
|
+
expect(Sand.policy_scope(user, Post)).to eq :published
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'uses Model.policy if it is defined' do
|
61
|
+
class NonStandardPolicyKlass < Struct.new(:user) # rubocop:disable Style/StructInheritance
|
62
|
+
class Scope < Struct.new(:user, :resource) # rubocop:disable Style/StructInheritance
|
63
|
+
def resolve
|
64
|
+
:resolved
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
class NonStandardKlass
|
69
|
+
def to_s
|
70
|
+
'WillBreak'
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.sand_policy
|
74
|
+
NonStandardPolicyKlass
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
expect(Sand.policy_scope(user, NonStandardKlass)).to eq(:resolved)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '.policy!' do
|
83
|
+
it 'rests on default NameErrors if policy does not exist' do
|
84
|
+
class Foo
|
85
|
+
end
|
86
|
+
|
87
|
+
expect do
|
88
|
+
Sand.policy!(user, Foo)
|
89
|
+
end.to raise_error Sand::NotDefinedError
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'succesfully finds a policy if one is defined' do
|
93
|
+
policy = Sand.policy!(user, post)
|
94
|
+
expect(policy).to be_an_instance_of(PostPolicy)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'json'
|
3
|
+
require 'support/shared_application_code'
|
4
|
+
|
5
|
+
class SandApp # rubocop:disable
|
6
|
+
def json_response(data)
|
7
|
+
[200, {}, JSON.dump(data)]
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
request = Rack::Request.new(env)
|
12
|
+
|
13
|
+
case request.path_info
|
14
|
+
when %r{/accounts$}
|
15
|
+
id = /\d/.match(request.path_info)[0]
|
16
|
+
user = User[id]
|
17
|
+
accounts = env['sand'].policy_scope(user, Account)
|
18
|
+
json_response(accounts.map(&:to_hash))
|
19
|
+
when %r{/verify_scoped/failure}
|
20
|
+
[200, {}, [1, 2, 3]]
|
21
|
+
when %r{/verify_scoped/succeed}
|
22
|
+
accounts = env['sand'].policy_scope(user, Account)
|
23
|
+
json_response(accounts.map(&:to_hash))
|
24
|
+
when %r{/verify_scoped/pass}
|
25
|
+
env['sand'].skip_sand_scoping
|
26
|
+
[200, {}, ['yay']]
|
27
|
+
when %r{/verify_authorized/fail}
|
28
|
+
user = User.find('1')
|
29
|
+
account = Account.first
|
30
|
+
env['sand'].authorize(user, account, :will_fail_action)
|
31
|
+
[200, {}, ['yay']]
|
32
|
+
when %r{/verify_authorized/success}
|
33
|
+
user = User.find('1')
|
34
|
+
account = Account.first
|
35
|
+
env['sand'].authorize(user, account, :will_succeed_action)
|
36
|
+
[200, {}, ['yay']]
|
37
|
+
else
|
38
|
+
[404, {}, []]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
MyRackApp = Rack::Builder.new do
|
44
|
+
use Sand::Middleware
|
45
|
+
|
46
|
+
run SandApp.new
|
47
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
DB = Sequel.sqlite
|
4
|
+
|
5
|
+
DB.create_table :users do
|
6
|
+
primary_key :id
|
7
|
+
String :name
|
8
|
+
Boolean :admin
|
9
|
+
end
|
10
|
+
|
11
|
+
DB.create_table :accounts do
|
12
|
+
primary_key :id
|
13
|
+
String :title
|
14
|
+
end
|
15
|
+
|
16
|
+
DB.create_table :roles do
|
17
|
+
Integer 'user_id'
|
18
|
+
Integer 'account_id'
|
19
|
+
String 'role'
|
20
|
+
end
|
21
|
+
|
22
|
+
class Account < Sequel::Model; end
|
23
|
+
class User < Sequel::Model; end
|
24
|
+
class Role < Sequel::Model
|
25
|
+
many_to_one :user
|
26
|
+
many_to_one :account
|
27
|
+
end
|
28
|
+
|
29
|
+
class AccountPolicy
|
30
|
+
def initialize(user, record)
|
31
|
+
@user = user
|
32
|
+
@record = record
|
33
|
+
end
|
34
|
+
|
35
|
+
def will_fail_action
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def will_succeed_action
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
class Scope
|
44
|
+
attr_reader :user, :scope
|
45
|
+
|
46
|
+
def initialize(user, scope)
|
47
|
+
@user = user
|
48
|
+
@scope = scope
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve
|
52
|
+
return [] if user.nil?
|
53
|
+
return scope.all if user.admin
|
54
|
+
|
55
|
+
scope.join(:roles, account_id: :id).where(user_id: user.id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'sand'
|
3
|
+
require 'json'
|
4
|
+
require 'support/shared_application_code'
|
5
|
+
|
6
|
+
get '/' do
|
7
|
+
'Index'
|
8
|
+
end
|
9
|
+
|
10
|
+
helpers Sand::Helpers
|
11
|
+
use Sand::Middleware
|
12
|
+
|
13
|
+
get '/users/:user_id/accounts' do
|
14
|
+
user = User[params[:user_id]]
|
15
|
+
accounts = policy_scope(user, Account)
|
16
|
+
send :erb, accounts.map(&:to_hash).to_json
|
17
|
+
end
|
18
|
+
|
19
|
+
get '/verify_scoped/fail' do
|
20
|
+
send :erb, Account.all.map(&:to_hash).to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
get '/verify_scoped/succeed' do
|
24
|
+
user = User.find('1')
|
25
|
+
accounts = policy_scope(user, Account)
|
26
|
+
send :erb, accounts.map(&:to_hash).to_json
|
27
|
+
end
|
28
|
+
|
29
|
+
get '/verify_scoped/pass' do
|
30
|
+
skip_sand_scoping
|
31
|
+
200
|
32
|
+
end
|
33
|
+
|
34
|
+
get '/verify_authorized/fail' do
|
35
|
+
user = User.find('1')
|
36
|
+
account = Account.first
|
37
|
+
authorize(user, account, :will_fail_action)
|
38
|
+
send :erb, account.to_json
|
39
|
+
end
|
40
|
+
|
41
|
+
get '/verify_authorized/success' do
|
42
|
+
user = User.find('1')
|
43
|
+
account = Account.first
|
44
|
+
authorize(user, account, :will_succeed_action)
|
45
|
+
send :erb, account.to_json
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sand
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Tomlin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rdoc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubygems-tasks
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.37.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.37.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.10.3
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.10.3
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rack-test
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.6.3
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.6.3
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: sinatra
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 1.4.7
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 1.4.7
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sequel
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '4.31'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '4.31'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: sqlite3
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: A ruby gem for authorization for use in sinatra/rack applications. Heavily
|
168
|
+
inspired [Pundit](https://github.com/elabs/pundit)
|
169
|
+
email: nick.tomlin@gmail.com
|
170
|
+
executables: []
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- ".document"
|
175
|
+
- ".gitignore"
|
176
|
+
- ".rdoc_options"
|
177
|
+
- ".rspec"
|
178
|
+
- ".rubocop.yml"
|
179
|
+
- ".travis.yml"
|
180
|
+
- ChangeLog.md
|
181
|
+
- Gemfile
|
182
|
+
- LICENSE.txt
|
183
|
+
- README.md
|
184
|
+
- Rakefile
|
185
|
+
- lib/sand.rb
|
186
|
+
- lib/sand/helpers.rb
|
187
|
+
- lib/sand/middleware.rb
|
188
|
+
- lib/sand/policy_finder.rb
|
189
|
+
- lib/sand/util.rb
|
190
|
+
- lib/sand/version.rb
|
191
|
+
- sand.gemspec
|
192
|
+
- spec/.rubocop.yml
|
193
|
+
- spec/application/rack_spec.rb
|
194
|
+
- spec/application/shared_application_examples.rb
|
195
|
+
- spec/application/sinatra_spec.rb
|
196
|
+
- spec/sand_spec.rb
|
197
|
+
- spec/spec_helper.rb
|
198
|
+
- spec/support/rack_app.rb
|
199
|
+
- spec/support/shared_application_code.rb
|
200
|
+
- spec/support/sinatra_app.rb
|
201
|
+
homepage: https://rubygems.org/gems/sand
|
202
|
+
licenses:
|
203
|
+
- MIT
|
204
|
+
metadata: {}
|
205
|
+
post_install_message:
|
206
|
+
rdoc_options: []
|
207
|
+
require_paths:
|
208
|
+
- lib
|
209
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
210
|
+
requirements:
|
211
|
+
- - ">="
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
|
+
requirements:
|
216
|
+
- - ">="
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
version: '0'
|
219
|
+
requirements: []
|
220
|
+
rubyforge_project:
|
221
|
+
rubygems_version: 2.4.5
|
222
|
+
signing_key:
|
223
|
+
specification_version: 4
|
224
|
+
summary: Authorization for rack-based applications
|
225
|
+
test_files:
|
226
|
+
- spec/.rubocop.yml
|
227
|
+
- spec/application/rack_spec.rb
|
228
|
+
- spec/application/shared_application_examples.rb
|
229
|
+
- spec/application/sinatra_spec.rb
|
230
|
+
- spec/sand_spec.rb
|
231
|
+
- spec/spec_helper.rb
|
232
|
+
- spec/support/rack_app.rb
|
233
|
+
- spec/support/shared_application_code.rb
|
234
|
+
- spec/support/sinatra_app.rb
|