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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8c2cecc7324f61545be2136b73b66a0e3965ac64
4
- data.tar.gz: e83e24a0f44fedf13b0caab14378007f80a16d8a
2
+ SHA256:
3
+ metadata.gz: 6b8592539c3824fa1985a4c02b877bd7d53e6ab2635f4227b173325f16484465
4
+ data.tar.gz: 1aac0ef35f8584e83cbafd5843172798f8967ad217235f9048e476cf3cc8945f
5
5
  SHA512:
6
- metadata.gz: 2a9cf4365f81e73630bc845767d29bad3ed3e08f1c033c961eb28d5f52e10235b1611a1634626ffc1e581a833504ddd4987678677e157d28a1b110931aa756b5
7
- data.tar.gz: 7eb46c0a4567a20d9c20952bb6da6eba78fb18fd45637b444c64c1a2e5cf61866b835b1e5952be7ddd08d4e9424db266b598715121c03767456205924ac71224
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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ SimpleCov.start do
4
+ coverage_dir '.coverage'
5
+ enable_coverage :branch
6
+
7
+ root __dir__
8
+
9
+ add_filter 'tests/'
10
+ # add_filter 'spec/'
11
+ end
data/Gemfile CHANGED
@@ -1,4 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
- # Specify your gem's dependencies in dirt_core.gemspec
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 = Hash.new do
17
+ @ruleset = Hash.new do
17
18
  []
18
19
  end
19
- @rules_initializer = Proc.new {}
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
- raise BouncerError.new('You cannot provide a block to #can. If you wish to conditionally allow, use #can_sometimes instead.')
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 block_given?
38
- raise BouncerError.new('You must provide a block to #can_sometimes. If you wish to always allow, use #can instead.')
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 &bounce_with
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, &ruleblock)
6
+ def initialize(path, &rule_block)
5
7
  if path == :all
6
8
  @path = :all
7
9
  else
8
- path = '/' + path unless path.start_with?('/')
10
+ path = "/#{ path }" unless path.start_with?('/')
9
11
 
10
12
  @path = path.split('/')
11
13
  end
12
14
 
13
- @rule = ruleblock
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 = '/' + path unless path.start_with?('/')
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
- raise BouncerError.new("Rule block at does not return explicit true/false.\n\n"+
40
- "Rules must return explicit true or false to prevent accidental truthy values.\n\n"+
41
- "Source: #{source}\n")
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Bouncer
5
+ # Current version of the gem
6
+ VERSION = '1.3.0'
7
+ end
8
+ end
@@ -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
- self.instance_exec &bouncer.rules_initializer
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(*args, &block)
67
- settings.bouncer.can_sometimes(*args, &block)
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Then 'it should have status code {int}' do |status|
4
+ expect(page.driver.status_code).to eq status
5
+ end
6
+
7
+ Then 'it should be at {path}' do |path|
8
+ expect(page).to have_current_path path.to_s
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ When 'he/she/they/I double visit(s) {path}' do |path|
4
+ visit path
5
+ visit '/'
6
+ visit path
7
+ end
8
+
9
+ When 'he/she/they/I visit(s) {path}' do |path|
10
+ visit path
11
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra'
4
+ require 'lib/sinatra/bouncer'
5
+
6
+ # class TestApp < Sinatra::Base
7
+ # register Sinatra::Bouncer
8
+
9
+ # end
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.1.2
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: 2016-10-02 00:00:00.000000000 Z
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: '0'
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: '0'
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: contact@tenjin.ca
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
- - lib/sinatra/basic_bouncer.rb
43
+ - Rakefile
44
+ - cucumber.yml
122
45
  - lib/sinatra/bouncer.rb
123
- - lib/sinatra/rule.rb
124
- homepage: http://www.tenjin.ca
125
- licenses: []
126
- metadata: {}
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.9.3
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
- rubyforge_project:
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