sinatra-bouncer 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 254ec098fccec946d8f3891767aec06c0b176ba1
4
- data.tar.gz: e9289acabf308082675f3176367c3016f3af791b
2
+ SHA256:
3
+ metadata.gz: 6b8592539c3824fa1985a4c02b877bd7d53e6ab2635f4227b173325f16484465
4
+ data.tar.gz: 1aac0ef35f8584e83cbafd5843172798f8967ad217235f9048e476cf3cc8945f
5
5
  SHA512:
6
- metadata.gz: b42b9091612bc206cc384f346108eee042de851c8793b09772d8c9fc818df7595c4a9cd2dfe5443b9f61ab045748d49082677e77f0e901abd1c78d4fa8d232a8
7
- data.tar.gz: 34231f5cbb55f638c6d6b977c080dd9a77ed5f4a3e561d90221b85c413e3717330d433eb06c2564d6613e5b325dc92b02e0ad34ace65ebbb5502748e15abac3b
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.2.0
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