sinatra-bouncer 1.3.0 → 2.0.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 +4 -4
- data/.gitignore +2 -7
- data/.rubocop.yml +1 -9
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -3
- data/Gemfile.lock +92 -0
- data/README.md +212 -52
- data/RELEASE_NOTES.md +153 -0
- data/lib/sinatra/bouncer/basic_bouncer.rb +36 -18
- data/lib/sinatra/bouncer/rule.rb +12 -4
- data/lib/sinatra/bouncer/version.rb +1 -1
- data/lib/sinatra/bouncer.rb +7 -27
- data/sinatra_bouncer.gemspec +4 -1
- metadata +5 -14
- data/tests/integrations/dev_defines_legal_routes.feature +0 -57
- data/tests/integrations/dev_installs_bouncer.feature +0 -12
- data/tests/integrations/step_definitions/given.rb +0 -36
- data/tests/integrations/step_definitions/then.rb +0 -9
- data/tests/integrations/step_definitions/when.rb +0 -11
- data/tests/integrations/support/env.rb +0 -30
- data/tests/integrations/support/helpers.rb +0 -55
- data/tests/integrations/support/types.rb +0 -21
- data/tests/spec/basic_bouncer_spec.rb +0 -148
- data/tests/spec/rule_spec.rb +0 -67
- data/tests/spec/spec_helper.rb +0 -11
- data/tests/test_app.rb +0 -9
@@ -4,16 +4,26 @@ require_relative 'rule'
|
|
4
4
|
|
5
5
|
module Sinatra
|
6
6
|
module Bouncer
|
7
|
+
# Core implementation of Bouncer logic
|
7
8
|
class BasicBouncer
|
8
9
|
attr_accessor :bounce_with, :rules_initializer
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
# Enumeration of HTTP method strings based on:
|
12
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
|
13
|
+
# Ignoring CONNECT and TRACE due to rarity
|
14
|
+
HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH].freeze
|
15
|
+
|
16
|
+
# Symbol versions of HTTP_METHODS constant
|
17
|
+
#
|
18
|
+
# @see HTTP_METHODS
|
19
|
+
HTTP_METHOD_SYMBOLS = HTTP_METHODS.collect do |http_method|
|
20
|
+
http_method.downcase.to_sym
|
21
|
+
end.freeze
|
22
|
+
|
23
|
+
# Method symbol used to match any method
|
24
|
+
WILDCARD_METHOD = :any
|
16
25
|
|
26
|
+
def initialize
|
17
27
|
@ruleset = Hash.new do
|
18
28
|
[]
|
19
29
|
end
|
@@ -21,37 +31,45 @@ module Sinatra
|
|
21
31
|
@rules_initializer = proc {}
|
22
32
|
end
|
23
33
|
|
24
|
-
def
|
25
|
-
@ruleset.clear
|
26
|
-
end
|
27
|
-
|
28
|
-
def can(method, *paths)
|
34
|
+
def can(**method_routes)
|
29
35
|
if block_given?
|
30
36
|
hint = 'If you wish to conditionally allow, use #can_sometimes instead.'
|
31
37
|
raise BouncerError, "You cannot provide a block to #can. #{ hint }"
|
32
38
|
end
|
33
39
|
|
34
|
-
can_sometimes(
|
40
|
+
can_sometimes(**method_routes) do
|
35
41
|
true
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
39
|
-
def can_sometimes(
|
45
|
+
def can_sometimes(**method_routes, &block)
|
40
46
|
unless block
|
41
47
|
hint = 'If you wish to always allow, use #can instead.'
|
42
48
|
raise BouncerError, "You must provide a block to #can_sometimes. #{ hint }"
|
43
49
|
end
|
44
50
|
|
45
|
-
|
46
|
-
|
51
|
+
method_routes.each do |method, paths|
|
52
|
+
unless HTTP_METHOD_SYMBOLS.include?(method) || method == WILDCARD_METHOD
|
53
|
+
hint = "Must be one of: #{ HTTP_METHOD_SYMBOLS } or :#{ WILDCARD_METHOD }"
|
54
|
+
raise BouncerError, "'#{ method }' is not a known HTTP method key. #{ hint }"
|
55
|
+
end
|
56
|
+
|
57
|
+
paths = [paths] unless paths.respond_to? :collect
|
58
|
+
|
59
|
+
@ruleset[method] += paths.collect { |path| Rule.new(path, &block) }
|
47
60
|
end
|
48
61
|
end
|
49
62
|
|
50
|
-
def can?(method, path)
|
51
|
-
|
63
|
+
def can?(method, path, context)
|
64
|
+
rulesets = @ruleset[WILDCARD_METHOD] + @ruleset[method]
|
65
|
+
|
66
|
+
# HEAD requests are equivalent to GET requests without response
|
67
|
+
rulesets += @ruleset[:get] if method == :head
|
68
|
+
|
69
|
+
rules = rulesets.select { |rule| rule.match_path?(path) }
|
52
70
|
|
53
71
|
rules.any? do |rule_block|
|
54
|
-
ruling = rule_block.rule_passes?
|
72
|
+
ruling = rule_block.rule_passes? context
|
55
73
|
|
56
74
|
ruling
|
57
75
|
end
|
data/lib/sinatra/bouncer/rule.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Sinatra
|
4
4
|
module Bouncer
|
5
|
+
# Defines a Rule to be evaluated with each request
|
5
6
|
class Rule
|
6
7
|
def initialize(path, &rule_block)
|
7
8
|
if path == :all
|
@@ -15,6 +16,9 @@ module Sinatra
|
|
15
16
|
@rule = rule_block
|
16
17
|
end
|
17
18
|
|
19
|
+
# Determines if the path matches the exact path or wildcard.
|
20
|
+
#
|
21
|
+
# @return `true` if the path matches
|
18
22
|
def match_path?(path)
|
19
23
|
return true if @path == :all
|
20
24
|
|
@@ -33,10 +37,14 @@ module Sinatra
|
|
33
37
|
matches
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
|
40
|
+
# Evaluates the rule's block. Defensively prevents truthy values from the block from allowing a route.
|
41
|
+
#
|
42
|
+
# @raise BouncerError when the rule block is a truthy value but not exactly `true`
|
43
|
+
# @return Exactly `true` or `false`, depending on the result of the rule block
|
44
|
+
def rule_passes?(context)
|
45
|
+
ruling = context.instance_exec(&@rule)
|
38
46
|
|
39
|
-
unless ruling
|
47
|
+
unless !ruling || ruling.is_a?(TrueClass)
|
40
48
|
source = @rule.source_location.join(':')
|
41
49
|
msg = <<~ERR
|
42
50
|
Rule block at does not return explicit true/false.
|
@@ -47,7 +55,7 @@ module Sinatra
|
|
47
55
|
raise BouncerError, msg
|
48
56
|
end
|
49
57
|
|
50
|
-
ruling
|
58
|
+
!!ruling
|
51
59
|
end
|
52
60
|
end
|
53
61
|
end
|
data/lib/sinatra/bouncer.rb
CHANGED
@@ -23,52 +23,32 @@
|
|
23
23
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
24
|
#++
|
25
25
|
|
26
|
+
require 'sinatra/base'
|
26
27
|
require_relative 'bouncer/basic_bouncer'
|
27
28
|
|
29
|
+
# Namespace module
|
28
30
|
module Sinatra
|
31
|
+
# Namespace module
|
29
32
|
module Bouncer
|
30
33
|
def self.registered(base_class)
|
31
|
-
base_class.
|
32
|
-
|
33
|
-
bouncer = BasicBouncer.new
|
34
|
-
|
35
|
-
# TODO: can we instead store it somehow on the actual temp request object?
|
36
|
-
base_class.set :bouncer, bouncer
|
34
|
+
base_class.set :bouncer, BasicBouncer.new
|
37
35
|
|
38
36
|
base_class.before do
|
39
|
-
bouncer.reset! # must clear all rules otherwise will leave doors open
|
40
|
-
|
41
|
-
instance_exec(&bouncer.rules_initializer)
|
42
|
-
|
43
37
|
http_method = request.request_method.downcase.to_sym
|
44
38
|
path = request.path.downcase
|
45
39
|
|
46
|
-
bouncer.bounce(self) unless bouncer.can?(http_method, path)
|
40
|
+
settings.bouncer.bounce(self) unless settings.bouncer.can?(http_method, path, self)
|
47
41
|
end
|
48
42
|
end
|
49
43
|
|
50
|
-
# Start ExtensionMethods
|
51
44
|
def bounce_with(&block)
|
52
45
|
bouncer.bounce_with = block
|
53
46
|
end
|
54
47
|
|
55
48
|
def rules(&block)
|
56
|
-
bouncer.
|
57
|
-
end
|
58
|
-
# End ExtensionMethods
|
59
|
-
|
60
|
-
module HelperMethods
|
61
|
-
def can(*args)
|
62
|
-
settings.bouncer.can(*args)
|
63
|
-
end
|
64
|
-
|
65
|
-
def can_sometimes(...)
|
66
|
-
settings.bouncer.can_sometimes(...)
|
67
|
-
end
|
49
|
+
settings.bouncer.instance_exec(&block)
|
68
50
|
end
|
69
51
|
end
|
70
52
|
|
71
|
-
|
72
|
-
register Bouncer
|
73
|
-
end
|
53
|
+
register Sinatra::Bouncer
|
74
54
|
end
|
data/sinatra_bouncer.gemspec
CHANGED
@@ -19,7 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
'rubygems_mfa_required' => 'true'
|
20
20
|
}
|
21
21
|
|
22
|
-
spec.files
|
22
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
f.match(%r{^(test|spec|features|integrations|benchmarks)/})
|
24
|
+
end
|
25
|
+
|
23
26
|
spec.require_paths = ['lib']
|
24
27
|
|
25
28
|
spec.required_ruby_version = '>= 3.1'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-bouncer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tenjin Inc
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-11-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
@@ -37,9 +37,12 @@ files:
|
|
37
37
|
- ".rubocop.yml"
|
38
38
|
- ".ruby-version"
|
39
39
|
- ".simplecov"
|
40
|
+
- CODE_OF_CONDUCT.md
|
40
41
|
- Gemfile
|
42
|
+
- Gemfile.lock
|
41
43
|
- MIT-LICENSE
|
42
44
|
- README.md
|
45
|
+
- RELEASE_NOTES.md
|
43
46
|
- Rakefile
|
44
47
|
- cucumber.yml
|
45
48
|
- lib/sinatra/bouncer.rb
|
@@ -47,18 +50,6 @@ files:
|
|
47
50
|
- lib/sinatra/bouncer/rule.rb
|
48
51
|
- lib/sinatra/bouncer/version.rb
|
49
52
|
- sinatra_bouncer.gemspec
|
50
|
-
- tests/integrations/dev_defines_legal_routes.feature
|
51
|
-
- tests/integrations/dev_installs_bouncer.feature
|
52
|
-
- tests/integrations/step_definitions/given.rb
|
53
|
-
- tests/integrations/step_definitions/then.rb
|
54
|
-
- tests/integrations/step_definitions/when.rb
|
55
|
-
- tests/integrations/support/env.rb
|
56
|
-
- tests/integrations/support/helpers.rb
|
57
|
-
- tests/integrations/support/types.rb
|
58
|
-
- tests/spec/basic_bouncer_spec.rb
|
59
|
-
- tests/spec/rule_spec.rb
|
60
|
-
- tests/spec/spec_helper.rb
|
61
|
-
- tests/test_app.rb
|
62
53
|
homepage: https://github.com/TenjinInc/Sinatra-Bouncer
|
63
54
|
licenses:
|
64
55
|
- MIT
|
@@ -1,57 +0,0 @@
|
|
1
|
-
Feature: Developer defines legal routes
|
2
|
-
As a developer
|
3
|
-
So that clients can safely access my server
|
4
|
-
I will allow specific routes
|
5
|
-
|
6
|
-
Scenario Outline: it should allows access to whitelist routes
|
7
|
-
Given a sinatra server with bouncer and routes:
|
8
|
-
| method | path | allowed |
|
9
|
-
| get | <path> | yes |
|
10
|
-
When I visit /<path>
|
11
|
-
Then it should be at /<path>
|
12
|
-
And it should have status code 200
|
13
|
-
Examples:
|
14
|
-
| path |
|
15
|
-
| some_path |
|
16
|
-
| another_path |
|
17
|
-
|
18
|
-
Scenario: it should NOT allow access to other routes
|
19
|
-
Given a sinatra server with bouncer and routes:
|
20
|
-
| method | path | allowed |
|
21
|
-
| get | some_path | yes |
|
22
|
-
| get | illegal_path | no |
|
23
|
-
When I visit /illegal_path
|
24
|
-
Then it should have status code 403
|
25
|
-
|
26
|
-
Scenario Outline: it should allow multiple routes with a splat
|
27
|
-
Given a sinatra server with bouncer and routes:
|
28
|
-
| method | path | allowed |
|
29
|
-
| get | admin/* | yes |
|
30
|
-
When I visit /admin/<sub_path>
|
31
|
-
Then it should be at /admin/<sub_path>
|
32
|
-
And it should have status code 200
|
33
|
-
Examples:
|
34
|
-
| sub_path |
|
35
|
-
| dashboard |
|
36
|
-
| users |
|
37
|
-
|
38
|
-
Scenario Outline: it should allow splat to be in the middle of the route
|
39
|
-
Given a sinatra server with bouncer and routes:
|
40
|
-
| method | path | allowed |
|
41
|
-
| get | admin/*/create | yes |
|
42
|
-
When I visit /admin/<sub_path>/create
|
43
|
-
Then it should be at /admin/<sub_path>/create
|
44
|
-
And it should have status code 200
|
45
|
-
Examples:
|
46
|
-
| sub_path |
|
47
|
-
| tasks |
|
48
|
-
| users |
|
49
|
-
|
50
|
-
Scenario: it should forget rules between requests
|
51
|
-
Given a sinatra server with bouncer and routes:
|
52
|
-
| method | path | allowed |
|
53
|
-
| get | some_path | once |
|
54
|
-
When I double visit /some_path
|
55
|
-
Then it should be at /some_path
|
56
|
-
And it should have status code 403
|
57
|
-
|
@@ -1,12 +0,0 @@
|
|
1
|
-
Feature: Developer installs Bouncer
|
2
|
-
As a developer
|
3
|
-
So that I can secure my sinatra server
|
4
|
-
I will install bouncer
|
5
|
-
|
6
|
-
Scenario: it should auto protect all routes
|
7
|
-
Given a sinatra server with bouncer and routes:
|
8
|
-
| method | path |
|
9
|
-
| get | some_path |
|
10
|
-
When I visit /some_path
|
11
|
-
Then it should have status code 403
|
12
|
-
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
Given 'a sinatra server with bouncer and routes:' do |table|
|
4
|
-
app = Capybara.app
|
5
|
-
|
6
|
-
allowed_paths = []
|
7
|
-
|
8
|
-
table.hashes.each do |row|
|
9
|
-
path = row[:path]
|
10
|
-
path = "/#{ path }" if path[0] != '/' # ensure leading slash
|
11
|
-
|
12
|
-
method = row[:method].to_sym
|
13
|
-
# build the routes
|
14
|
-
app.send(method, path) do
|
15
|
-
'The result of path'
|
16
|
-
end
|
17
|
-
|
18
|
-
is_once = row[:allowed] =~ /once/i
|
19
|
-
|
20
|
-
next unless is_once || parse_bool(row[:allowed])
|
21
|
-
|
22
|
-
allowed_paths << path
|
23
|
-
|
24
|
-
@allowed_once_paths << path if is_once
|
25
|
-
end
|
26
|
-
|
27
|
-
onces = @allowed_once_paths
|
28
|
-
|
29
|
-
app.rules do
|
30
|
-
allowed_paths.each do |path|
|
31
|
-
can_sometimes(:any_method, path) do
|
32
|
-
!onces.include?(path)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
src_dir = File.expand_path('../../..', __dir__)
|
4
|
-
$LOAD_PATH.unshift(src_dir) unless $LOAD_PATH.include?(src_dir)
|
5
|
-
|
6
|
-
require 'simplecov'
|
7
|
-
|
8
|
-
SimpleCov.command_name 'spec'
|
9
|
-
|
10
|
-
require 'capybara/cucumber'
|
11
|
-
require 'rspec/expectations'
|
12
|
-
|
13
|
-
require 'tests/test_app'
|
14
|
-
|
15
|
-
# == CAPYBARA ==
|
16
|
-
Capybara.app = Sinatra::Application
|
17
|
-
|
18
|
-
# Set this to whatever the server's normal port is for you. Sinatra is 4567; rack 9292 by default.
|
19
|
-
# Also note: you have to be running the server for this to work.
|
20
|
-
Capybara.asset_host = 'http://localhost:4567'
|
21
|
-
|
22
|
-
# == REGULAR SETTINGS ==
|
23
|
-
Before do
|
24
|
-
Capybara.reset_sessions!
|
25
|
-
Capybara.app.settings.bouncer.instance_variable_get(:@ruleset).clear
|
26
|
-
|
27
|
-
@allowed_once_paths = []
|
28
|
-
end
|
29
|
-
|
30
|
-
World(RSpec::Matchers)
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Helper methods for Cucumber steps
|
4
|
-
module HelperMethods
|
5
|
-
# TEST_TMP_ROOT = Pathname.new(Dir.mktmpdir('bouncer_test_')).expand_path.freeze
|
6
|
-
# TEST_TMP_LOG = (TEST_TMP_ROOT / 'log').expand_path.freeze
|
7
|
-
|
8
|
-
# Data conversion helpers for Cucumber steps
|
9
|
-
module Conversion
|
10
|
-
def extract_list(list_string)
|
11
|
-
(list_string || '').split(',').map(&:strip)
|
12
|
-
end
|
13
|
-
|
14
|
-
def symrow(table)
|
15
|
-
table.symbolic_hashes.first
|
16
|
-
end
|
17
|
-
|
18
|
-
def symtable(table)
|
19
|
-
table.map_headers do |header|
|
20
|
-
header.tr(' ', '_').downcase.to_sym
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def parse_bool(string)
|
25
|
-
!(string =~ /t|y/i).nil?
|
26
|
-
end
|
27
|
-
|
28
|
-
def parse_phone(string)
|
29
|
-
string.to_s.split(/:\s+/)
|
30
|
-
end
|
31
|
-
|
32
|
-
def parse_duration(string)
|
33
|
-
scalar, unit = string.split(/\s/)
|
34
|
-
|
35
|
-
return nil if unit.nil? || unit.empty?
|
36
|
-
|
37
|
-
unit = "#{ unit }s" unless unit.end_with?('s')
|
38
|
-
|
39
|
-
unit_map = {
|
40
|
-
years: 365.25 * 86400,
|
41
|
-
months: 30 * 86400,
|
42
|
-
weeks: 7 * 86400,
|
43
|
-
days: 86400,
|
44
|
-
hours: 3600,
|
45
|
-
minutes: 60,
|
46
|
-
seconds: 1
|
47
|
-
}
|
48
|
-
|
49
|
-
scalar.to_i * unit_map[unit.downcase.to_sym]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Inject the HelperMethods into the Cucumber test context
|
55
|
-
World(HelperMethods, HelperMethods::Conversion)
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
ParameterType(name: 'path',
|
4
|
-
regexp: %r{/(?:\S+/?)*},
|
5
|
-
type: Pathname,
|
6
|
-
transformer: lambda do |str|
|
7
|
-
Pathname.new(str)
|
8
|
-
end)
|
9
|
-
|
10
|
-
ParameterType(name: 'boolean',
|
11
|
-
regexp: /(enabled|disabled|true|false|on|off|yes|no)/,
|
12
|
-
transformer: lambda do |str|
|
13
|
-
%w[enabled true on yes].include? str.downcase
|
14
|
-
end)
|
15
|
-
|
16
|
-
ParameterType(name: 'html element',
|
17
|
-
regexp: /<(\S+)>/,
|
18
|
-
type: String,
|
19
|
-
transformer: lambda do |str|
|
20
|
-
str
|
21
|
-
end)
|
@@ -1,148 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'spec_helper'
|
4
|
-
|
5
|
-
describe Sinatra::Bouncer::BasicBouncer do
|
6
|
-
let(:bouncer) { Sinatra::Bouncer::BasicBouncer.new }
|
7
|
-
|
8
|
-
describe '#can' do
|
9
|
-
it 'should raise an error if provided a block' do
|
10
|
-
msg = <<~ERR
|
11
|
-
You cannot provide a block to #can. If you wish to conditionally allow, use #can_sometimes instead.
|
12
|
-
ERR
|
13
|
-
|
14
|
-
expect do
|
15
|
-
bouncer.can(:post, 'some_path') do
|
16
|
-
# stub
|
17
|
-
end
|
18
|
-
end.to raise_error(Sinatra::Bouncer::BouncerError, msg.chomp)
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should handle a list of paths' do
|
22
|
-
bouncer.can(:post, 'some_path', 'other_path')
|
23
|
-
|
24
|
-
expect(bouncer.can?(:post, 'some_path')).to be true
|
25
|
-
expect(bouncer.can?(:post, 'other_path')).to be true
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'should accept a splat' do
|
29
|
-
bouncer.can(:post, 'directory/*')
|
30
|
-
|
31
|
-
expect(bouncer.can?(:post, 'directory/some_path')).to be true
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
describe '#can_sometimes' do
|
36
|
-
it 'should accept :any_method to mean all http methods' do
|
37
|
-
bouncer.can_sometimes(:any_method, 'some_path') do
|
38
|
-
true
|
39
|
-
end
|
40
|
-
|
41
|
-
expect(bouncer.can?(:get, 'some_path')).to be true
|
42
|
-
expect(bouncer.can?(:post, 'some_path')).to be true
|
43
|
-
expect(bouncer.can?(:put, 'some_path')).to be true
|
44
|
-
expect(bouncer.can?(:delete, 'some_path')).to be true
|
45
|
-
expect(bouncer.can?(:options, 'some_path')).to be true
|
46
|
-
expect(bouncer.can?(:link, 'some_path')).to be true
|
47
|
-
expect(bouncer.can?(:unlink, 'some_path')).to be true
|
48
|
-
expect(bouncer.can?(:head, 'some_path')).to be true
|
49
|
-
expect(bouncer.can?(:trace, 'some_path')).to be true
|
50
|
-
expect(bouncer.can?(:connect, 'some_path')).to be true
|
51
|
-
expect(bouncer.can?(:patch, 'some_path')).to be true
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'should accept :all to mean all paths' do
|
55
|
-
bouncer.can_sometimes(:get, :all) do
|
56
|
-
true
|
57
|
-
end
|
58
|
-
|
59
|
-
expect(bouncer.can?(:get, 'some_path')).to be true
|
60
|
-
end
|
61
|
-
|
62
|
-
it 'should accept a list of paths' do
|
63
|
-
bouncer.can_sometimes(:post, 'some_path', 'other_path') do
|
64
|
-
true
|
65
|
-
end
|
66
|
-
|
67
|
-
expect(bouncer.can?(:post, 'some_path')).to be true
|
68
|
-
expect(bouncer.can?(:post, 'other_path')).to be true
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'should accept a splat' do
|
72
|
-
bouncer.can_sometimes(:post, 'directory/*') do
|
73
|
-
true
|
74
|
-
end
|
75
|
-
|
76
|
-
expect(bouncer.can?(:post, 'directory/some_path')).to be true
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'should not raise an error if provided a block' do
|
80
|
-
expect do
|
81
|
-
bouncer.can_sometimes(:any_method, 'some_path') do
|
82
|
-
true
|
83
|
-
end
|
84
|
-
end.to_not raise_error
|
85
|
-
end
|
86
|
-
|
87
|
-
it 'should raise an error if not provided a block' do
|
88
|
-
msg = <<~ERR
|
89
|
-
You must provide a block to #can_sometimes. If you wish to always allow, use #can instead.
|
90
|
-
ERR
|
91
|
-
|
92
|
-
expect do
|
93
|
-
bouncer.can_sometimes(:any_method, 'some_path')
|
94
|
-
end.to raise_error(Sinatra::Bouncer::BouncerError, msg.chomp)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
describe '#can?' do
|
99
|
-
it 'should pass when declared allowed' do
|
100
|
-
bouncer.can(:any_method, 'some_path')
|
101
|
-
|
102
|
-
expect(bouncer.can?(:post, 'some_path')).to be true
|
103
|
-
end
|
104
|
-
|
105
|
-
it 'should fail when not declared allowed' do
|
106
|
-
expect(bouncer.can?(:post, 'some_path')).to be false
|
107
|
-
end
|
108
|
-
|
109
|
-
it 'should pass if the rule block passes' do
|
110
|
-
bouncer.can_sometimes(:any_method, 'some_path') do
|
111
|
-
true
|
112
|
-
end
|
113
|
-
|
114
|
-
expect(bouncer.can?(:post, 'some_path')).to be true
|
115
|
-
end
|
116
|
-
|
117
|
-
it 'should fail if the rule block fails' do
|
118
|
-
bouncer.can_sometimes(:any_method, 'some_path') do
|
119
|
-
false
|
120
|
-
end
|
121
|
-
|
122
|
-
expect(bouncer.can?(:post, 'some_path')).to be false
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
describe '#bounce' do
|
127
|
-
it 'should run the bounce_with block on sinatra instance' do
|
128
|
-
runner = nil
|
129
|
-
sinatra = double('sinatra')
|
130
|
-
|
131
|
-
bouncer.bounce_with = proc do
|
132
|
-
runner = self # self should be the sinatra double
|
133
|
-
end
|
134
|
-
|
135
|
-
bouncer.bounce(sinatra)
|
136
|
-
|
137
|
-
expect(runner).to be sinatra
|
138
|
-
end
|
139
|
-
|
140
|
-
it 'should halt 403 if no block provided' do
|
141
|
-
app = double('sinatra')
|
142
|
-
|
143
|
-
expect(app).to receive(:halt).with(403)
|
144
|
-
|
145
|
-
bouncer.bounce(app)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|