sinatra-bouncer 1.1.2 → 1.3.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 +5 -5
- data/.gitignore +23 -0
- data/.rubocop.yml +22 -0
- data/.ruby-version +1 -0
- data/.simplecov +11 -0
- data/Gemfile +17 -2
- data/Rakefile +15 -0
- data/cucumber.yml +1 -0
- data/lib/sinatra/{basic_bouncer.rb → bouncer/basic_bouncer.rb} +13 -10
- data/lib/sinatra/{rule.rb → bouncer/rule.rb} +15 -9
- data/lib/sinatra/bouncer/version.rb +8 -0
- data/lib/sinatra/bouncer.rb +8 -9
- data/sinatra_bouncer.gemspec +28 -0
- data/tests/integrations/dev_defines_legal_routes.feature +57 -0
- data/tests/integrations/dev_installs_bouncer.feature +12 -0
- data/tests/integrations/step_definitions/given.rb +36 -0
- data/tests/integrations/step_definitions/then.rb +9 -0
- data/tests/integrations/step_definitions/when.rb +11 -0
- data/tests/integrations/support/env.rb +30 -0
- data/tests/integrations/support/helpers.rb +55 -0
- data/tests/integrations/support/types.rb +21 -0
- data/tests/spec/basic_bouncer_spec.rb +148 -0
- data/tests/spec/rule_spec.rb +67 -0
- data/tests/spec/spec_helper.rb +11 -0
- data/tests/test_app.rb +9 -0
- metadata +37 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6b8592539c3824fa1985a4c02b877bd7d53e6ab2635f4227b173325f16484465
|
4
|
+
data.tar.gz: 1aac0ef35f8584e83cbafd5843172798f8967ad217235f9048e476cf3cc8945f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83ed25a0acff1d864f978dbbd3667d20f53be0bea36fa5db5abd55eeb4353cad53e29ebeb424f1c462e4a8790444f4a5850cad33c579729aab684ff4f0d482aa
|
7
|
+
data.tar.gz: a3caa0c02709faa60e5512f73f2b4c4f21066f519999e014ffac2545271198d8a98a7a73d430494a185a00e0ea255170e1693274c7fb8bca51b56c6108fcd8a6
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile ~/.gitignore_global
|
6
|
+
|
7
|
+
# Ignore bundler config
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore all logfiles and tempfiles.
|
11
|
+
*.log
|
12
|
+
tmp/*
|
13
|
+
|
14
|
+
# Rubymine stuff
|
15
|
+
.idea
|
16
|
+
|
17
|
+
# Ignore all gem files.
|
18
|
+
*.gem
|
19
|
+
|
20
|
+
core/config.yml
|
21
|
+
persist/database.yml
|
22
|
+
|
23
|
+
Gemfile.lock
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
inherit_from: ~/.config/rubocop/config.yml
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
Exclude:
|
5
|
+
- 'bin/*'
|
6
|
+
|
7
|
+
Layout/LineLength:
|
8
|
+
Exclude:
|
9
|
+
- 'spec/**/*.rb'
|
10
|
+
|
11
|
+
# setting to 6 to match RubyMine autoformat
|
12
|
+
Layout/FirstArrayElementIndentation:
|
13
|
+
IndentationWidth: 6
|
14
|
+
|
15
|
+
# rspec blocks are huge by design
|
16
|
+
Metrics/BlockLength:
|
17
|
+
Exclude:
|
18
|
+
- 'tests/spec/**/*.rb'
|
19
|
+
|
20
|
+
Metrics/ModuleLength:
|
21
|
+
Exclude:
|
22
|
+
- 'tests/spec/**/*.rb'
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-3.1.4
|
data/.simplecov
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source 'https://rubygems.org'
|
2
4
|
|
3
|
-
#
|
4
|
-
gemspec
|
5
|
+
# Gem dependencies in dirt_core.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
gem 'bundler', '~> 2.4'
|
10
|
+
gem 'rake', '~> 13.0'
|
11
|
+
gem 'yard', '~> 0.9'
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'capybara', '~> 3.39'
|
16
|
+
gem 'cucumber', '~> 8.0'
|
17
|
+
gem 'rspec', '~> 3.12'
|
18
|
+
gem 'simplecov', '~> 0.22'
|
19
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'yard'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
task default: :spec
|
10
|
+
|
11
|
+
YARD::Rake::YardocTask.new do |t|
|
12
|
+
t.files = %w[lib/**/*.rb]
|
13
|
+
# t.options = %w[--some-option]
|
14
|
+
t.stats_options = ['--list-undoc']
|
15
|
+
end
|
data/cucumber.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: --publish-quiet
|
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'rule'
|
2
4
|
|
3
5
|
module Sinatra
|
4
6
|
module Bouncer
|
5
7
|
class BasicBouncer
|
6
|
-
attr_accessor :bounce_with
|
7
|
-
attr_accessor :rules_initializer
|
8
|
+
attr_accessor :bounce_with, :rules_initializer
|
8
9
|
|
9
10
|
def initialize
|
10
11
|
# @rules = Hash.new do |method_to_paths, method|
|
@@ -13,10 +14,11 @@ module Sinatra
|
|
13
14
|
# end
|
14
15
|
# end
|
15
16
|
|
16
|
-
@ruleset
|
17
|
+
@ruleset = Hash.new do
|
17
18
|
[]
|
18
19
|
end
|
19
|
-
|
20
|
+
|
21
|
+
@rules_initializer = proc {}
|
20
22
|
end
|
21
23
|
|
22
24
|
def reset!
|
@@ -25,7 +27,8 @@ module Sinatra
|
|
25
27
|
|
26
28
|
def can(method, *paths)
|
27
29
|
if block_given?
|
28
|
-
|
30
|
+
hint = 'If you wish to conditionally allow, use #can_sometimes instead.'
|
31
|
+
raise BouncerError, "You cannot provide a block to #can. #{ hint }"
|
29
32
|
end
|
30
33
|
|
31
34
|
can_sometimes(method, *paths) do
|
@@ -34,8 +37,9 @@ module Sinatra
|
|
34
37
|
end
|
35
38
|
|
36
39
|
def can_sometimes(method, *paths, &block)
|
37
|
-
unless
|
38
|
-
|
40
|
+
unless block
|
41
|
+
hint = 'If you wish to always allow, use #can instead.'
|
42
|
+
raise BouncerError, "You must provide a block to #can_sometimes. #{ hint }"
|
39
43
|
end
|
40
44
|
|
41
45
|
paths.each do |path|
|
@@ -55,7 +59,7 @@ module Sinatra
|
|
55
59
|
|
56
60
|
def bounce(instance)
|
57
61
|
if bounce_with
|
58
|
-
instance.instance_exec
|
62
|
+
instance.instance_exec(&bounce_with)
|
59
63
|
else
|
60
64
|
instance.halt 403
|
61
65
|
end
|
@@ -63,7 +67,6 @@ module Sinatra
|
|
63
67
|
end
|
64
68
|
|
65
69
|
class BouncerError < StandardError
|
66
|
-
|
67
70
|
end
|
68
71
|
end
|
69
|
-
end
|
72
|
+
end
|
@@ -1,22 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Sinatra
|
2
4
|
module Bouncer
|
3
5
|
class Rule
|
4
|
-
def initialize(path, &
|
6
|
+
def initialize(path, &rule_block)
|
5
7
|
if path == :all
|
6
8
|
@path = :all
|
7
9
|
else
|
8
|
-
path =
|
10
|
+
path = "/#{ path }" unless path.start_with?('/')
|
9
11
|
|
10
12
|
@path = path.split('/')
|
11
13
|
end
|
12
14
|
|
13
|
-
@rule =
|
15
|
+
@rule = rule_block
|
14
16
|
end
|
15
17
|
|
16
18
|
def match_path?(path)
|
17
19
|
return true if @path == :all
|
18
20
|
|
19
|
-
path =
|
21
|
+
path = "/#{ path }" unless path.start_with?('/')
|
20
22
|
|
21
23
|
split_path = path.split('/')
|
22
24
|
matches = @path.length == split_path.length
|
@@ -34,15 +36,19 @@ module Sinatra
|
|
34
36
|
def rule_passes?
|
35
37
|
ruling = @rule.call
|
36
38
|
|
37
|
-
unless ruling.is_a?(TrueClass)|| ruling.is_a?(FalseClass)
|
39
|
+
unless ruling.is_a?(TrueClass) || ruling.is_a?(FalseClass)
|
38
40
|
source = @rule.source_location.join(':')
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
msg = <<~ERR
|
42
|
+
Rule block at does not return explicit true/false.
|
43
|
+
Rules must return explicit true or false to prevent accidental truthy values.
|
44
|
+
Source: #{ source }
|
45
|
+
ERR
|
46
|
+
|
47
|
+
raise BouncerError, msg
|
42
48
|
end
|
43
49
|
|
44
50
|
ruling
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
48
|
-
end
|
54
|
+
end
|
data/lib/sinatra/bouncer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#--
|
2
4
|
# Copyright (c) 2014 Tenjin Inc.
|
3
5
|
#
|
@@ -21,7 +23,7 @@
|
|
21
23
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
24
|
#++
|
23
25
|
|
24
|
-
require_relative 'basic_bouncer'
|
26
|
+
require_relative 'bouncer/basic_bouncer'
|
25
27
|
|
26
28
|
module Sinatra
|
27
29
|
module Bouncer
|
@@ -36,14 +38,12 @@ module Sinatra
|
|
36
38
|
base_class.before do
|
37
39
|
bouncer.reset! # must clear all rules otherwise will leave doors open
|
38
40
|
|
39
|
-
|
41
|
+
instance_exec(&bouncer.rules_initializer)
|
40
42
|
|
41
43
|
http_method = request.request_method.downcase.to_sym
|
42
44
|
path = request.path.downcase
|
43
45
|
|
44
|
-
unless bouncer.can?(http_method, path)
|
45
|
-
bouncer.bounce(self)
|
46
|
-
end
|
46
|
+
bouncer.bounce(self) unless bouncer.can?(http_method, path)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
@@ -55,7 +55,6 @@ module Sinatra
|
|
55
55
|
def rules(&block)
|
56
56
|
bouncer.rules_initializer = block
|
57
57
|
end
|
58
|
-
|
59
58
|
# End ExtensionMethods
|
60
59
|
|
61
60
|
module HelperMethods
|
@@ -63,8 +62,8 @@ module Sinatra
|
|
63
62
|
settings.bouncer.can(*args)
|
64
63
|
end
|
65
64
|
|
66
|
-
def can_sometimes(
|
67
|
-
settings.bouncer.can_sometimes(
|
65
|
+
def can_sometimes(...)
|
66
|
+
settings.bouncer.can_sometimes(...)
|
68
67
|
end
|
69
68
|
end
|
70
69
|
end
|
@@ -72,4 +71,4 @@ module Sinatra
|
|
72
71
|
if defined? register
|
73
72
|
register Bouncer
|
74
73
|
end
|
75
|
-
end
|
74
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'sinatra/bouncer/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'sinatra-bouncer'
|
10
|
+
spec.version = Sinatra::Bouncer::VERSION
|
11
|
+
spec.authors = ['Tenjin Inc', 'Robin Miller']
|
12
|
+
spec.email = %w[contact@tenjin.ca robin@tenjin.ca]
|
13
|
+
|
14
|
+
spec.summary = 'Sinatra permissions plugin'
|
15
|
+
spec.description = 'Bouncer brings simple authorization to Sinatra.'
|
16
|
+
spec.homepage = 'https://github.com/TenjinInc/Sinatra-Bouncer'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
spec.metadata = {
|
19
|
+
'rubygems_mfa_required' => 'true'
|
20
|
+
}
|
21
|
+
|
22
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|integrations)/}) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.required_ruby_version = '>= 3.1'
|
26
|
+
|
27
|
+
spec.add_dependency 'sinatra', '>= 2.2'
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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
|
+
|
@@ -0,0 +1,12 @@
|
|
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
|
+
|
@@ -0,0 +1,36 @@
|
|
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
|
@@ -0,0 +1,30 @@
|
|
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)
|
@@ -0,0 +1,55 @@
|
|
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)
|
@@ -0,0 +1,21 @@
|
|
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)
|
@@ -0,0 +1,148 @@
|
|
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
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'spec_helper'
|
4
|
+
|
5
|
+
describe Sinatra::Bouncer::Rule do
|
6
|
+
describe '#match_path?' do
|
7
|
+
it 'should match simple paths' do
|
8
|
+
rule = Sinatra::Bouncer::Rule.new('/some_path') { true }
|
9
|
+
|
10
|
+
expect(rule.match_path?('/some_path')).to be true
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should append leading slashes to the given path' do
|
14
|
+
rule = Sinatra::Bouncer::Rule.new('some_path') { true }
|
15
|
+
|
16
|
+
expect(rule.match_path?('/some_path')).to be true
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should append leading slashes to the tested path' do
|
20
|
+
rule = Sinatra::Bouncer::Rule.new('/other_path') { true }
|
21
|
+
|
22
|
+
expect(rule.match_path?('other_path')).to be true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should match splats' do
|
26
|
+
rule = Sinatra::Bouncer::Rule.new('/directory/*') { true }
|
27
|
+
|
28
|
+
%w[/directory/one /directory/two /directory/three].each do |path|
|
29
|
+
expect(rule.match_path?(path)).to be true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should NOT match empty string to a splat' do
|
34
|
+
rule = Sinatra::Bouncer::Rule.new('/directory/*') { true }
|
35
|
+
|
36
|
+
expect(rule.match_path?('/directory/')).to be false
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should require that both paths are same length' do
|
40
|
+
rule = Sinatra::Bouncer::Rule.new('/directory/*') { true }
|
41
|
+
|
42
|
+
%w[/directory /directory/extra/length].each do |path|
|
43
|
+
expect(rule.match_path?(path)).to be false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#rule_passes?' do
|
49
|
+
it 'should raise an error if rule returns nonbool' do
|
50
|
+
rule = Sinatra::Bouncer::Rule.new('/something') { nil }
|
51
|
+
|
52
|
+
expect { rule.rule_passes? }.to raise_error Sinatra::Bouncer::BouncerError
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should return true when the block is true' do
|
56
|
+
rule = Sinatra::Bouncer::Rule.new('/something') { true }
|
57
|
+
|
58
|
+
expect(rule.rule_passes?).to be true
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should return true when the block is false' do
|
62
|
+
rule = Sinatra::Bouncer::Rule.new('/something') { false }
|
63
|
+
|
64
|
+
expect(rule.rule_passes?).to be false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,11 @@
|
|
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 'lib/sinatra/bouncer'
|
11
|
+
require 'rspec/matchers'
|
data/tests/test_app.rb
ADDED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-bouncer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Tenjin
|
7
|
+
- Tenjin Inc
|
8
8
|
- Robin Miller
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-09-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
@@ -17,113 +17,53 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '2.2'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
28
|
-
- !ruby/object:Gem::Dependency
|
29
|
-
name: simplecov
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ">="
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '0'
|
35
|
-
type: :development
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '0'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: rspec
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - "~>"
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: 2.14.1
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - "~>"
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: 2.14.1
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: cucumber
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - "~>"
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: 1.3.19
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - "~>"
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 1.3.19
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: capybara
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
73
|
-
requirements:
|
74
|
-
- - ">="
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: '0'
|
77
|
-
type: :development
|
78
|
-
prerelease: false
|
79
|
-
version_requirements: !ruby/object:Gem::Requirement
|
80
|
-
requirements:
|
81
|
-
- - ">="
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: '0'
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: launchy
|
86
|
-
requirement: !ruby/object:Gem::Requirement
|
87
|
-
requirements:
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: '0'
|
91
|
-
type: :development
|
92
|
-
prerelease: false
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
94
|
-
requirements:
|
95
|
-
- - ">="
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '0'
|
98
|
-
- !ruby/object:Gem::Dependency
|
99
|
-
name: parallel_tests
|
100
|
-
requirement: !ruby/object:Gem::Requirement
|
101
|
-
requirements:
|
102
|
-
- - ">="
|
103
|
-
- !ruby/object:Gem::Version
|
104
|
-
version: '0'
|
105
|
-
type: :development
|
106
|
-
prerelease: false
|
107
|
-
version_requirements: !ruby/object:Gem::Requirement
|
108
|
-
requirements:
|
109
|
-
- - ">="
|
110
|
-
- !ruby/object:Gem::Version
|
111
|
-
version: '0'
|
27
|
+
version: '2.2'
|
112
28
|
description: Bouncer brings simple authorization to Sinatra.
|
113
|
-
email:
|
29
|
+
email:
|
30
|
+
- contact@tenjin.ca
|
31
|
+
- robin@tenjin.ca
|
114
32
|
executables: []
|
115
33
|
extensions: []
|
116
34
|
extra_rdoc_files: []
|
117
35
|
files:
|
36
|
+
- ".gitignore"
|
37
|
+
- ".rubocop.yml"
|
38
|
+
- ".ruby-version"
|
39
|
+
- ".simplecov"
|
118
40
|
- Gemfile
|
119
41
|
- MIT-LICENSE
|
120
42
|
- README.md
|
121
|
-
-
|
43
|
+
- Rakefile
|
44
|
+
- cucumber.yml
|
122
45
|
- lib/sinatra/bouncer.rb
|
123
|
-
- lib/sinatra/
|
124
|
-
|
125
|
-
|
126
|
-
|
46
|
+
- lib/sinatra/bouncer/basic_bouncer.rb
|
47
|
+
- lib/sinatra/bouncer/rule.rb
|
48
|
+
- lib/sinatra/bouncer/version.rb
|
49
|
+
- 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
|
+
homepage: https://github.com/TenjinInc/Sinatra-Bouncer
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata:
|
66
|
+
rubygems_mfa_required: 'true'
|
127
67
|
post_install_message:
|
128
68
|
rdoc_options: []
|
129
69
|
require_paths:
|
@@ -132,15 +72,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
132
72
|
requirements:
|
133
73
|
- - ">="
|
134
74
|
- !ruby/object:Gem::Version
|
135
|
-
version: 1
|
75
|
+
version: '3.1'
|
136
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
77
|
requirements:
|
138
78
|
- - ">="
|
139
79
|
- !ruby/object:Gem::Version
|
140
80
|
version: '0'
|
141
81
|
requirements: []
|
142
|
-
|
143
|
-
rubygems_version: 2.4.8
|
82
|
+
rubygems_version: 3.4.18
|
144
83
|
signing_key:
|
145
84
|
specification_version: 4
|
146
85
|
summary: Sinatra permissions plugin
|