sand 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 [](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
|