sinatra-bouncer 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +22 -26
- data/lib/sinatra/basic_bouncer.rb +69 -0
- data/lib/sinatra/bouncer.rb +34 -101
- data/lib/sinatra/rule.rb +48 -0
- metadata +23 -37
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8c2cecc7324f61545be2136b73b66a0e3965ac64
|
4
|
+
data.tar.gz: e83e24a0f44fedf13b0caab14378007f80a16d8a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2a9cf4365f81e73630bc845767d29bad3ed3e08f1c033c961eb28d5f52e10235b1611a1634626ffc1e581a833504ddd4987678677e157d28a1b110931aa756b5
|
7
|
+
data.tar.gz: 7eb46c0a4567a20d9c20952bb6da6eba78fb18fd45637b444c64c1a2e5cf61866b835b1e5952be7ddd08d4e9424db266b598715121c03767456205924ac71224
|
data/README.md
CHANGED
@@ -1,25 +1,23 @@
|
|
1
|
-
#Sinatra
|
2
|
-
Simple authorization permissions extension for Sinatra.
|
3
|
-
|
4
|
-
## Installation
|
5
|
-
|
6
|
-
**Prerequisites**
|
7
|
-
|
8
|
-
Bouncer requires [Sinatra](http://www.sinatrarb.com/), and [ruby 1.9.3](https://www.ruby-lang.org/en/documentation/installation/).
|
1
|
+
#Sinatra-Bouncer
|
2
|
+
Simple authorization permissions extension for [Sinatra](http://www.sinatrarb.com/). Require the gem, then declare which routes are permitted based on your own logic.
|
9
3
|
|
10
4
|
**Gemfile**
|
11
5
|
```ruby
|
12
6
|
gem 'sinatra-bouncer'
|
13
7
|
```
|
14
8
|
|
15
|
-
**
|
9
|
+
**Terminal**
|
16
10
|
```sh
|
17
11
|
gem install sinatra-bouncer
|
18
12
|
```
|
19
13
|
|
20
|
-
##
|
14
|
+
##Quickstart
|
21
15
|
###Step 1: Require/Register Bouncer
|
22
16
|
|
17
|
+
After registration, Bouncer will reject any request that either:
|
18
|
+
* has no rule associated with it, or
|
19
|
+
* has no associated rule that returns `true`
|
20
|
+
|
23
21
|
**Sinatra Classic**
|
24
22
|
```ruby
|
25
23
|
require 'sinatra'
|
@@ -40,24 +38,19 @@ class MyApp < Sinatra::Base
|
|
40
38
|
end
|
41
39
|
```
|
42
40
|
|
43
|
-
After registration, Bouncer will reject any request that either:
|
44
|
-
* has no rule associated with it, or
|
45
|
-
* has no associated rule that returns `true`
|
46
|
-
|
47
41
|
###Step 2: Declare Bouncer Rules
|
48
|
-
Call `rules` with a block that uses `can` and `can_sometimes` to declare which paths
|
49
|
-
The rules block is run in the context of the request, which means you will have access to sinatra helpers,
|
42
|
+
Call `rules` with a block that uses `can` and `can_sometimes` to declare which paths are legalduring this request. The rules block is run in the context of the request, which means you will have access to sinatra helpers,
|
50
43
|
the `request` object, and `params`.
|
51
44
|
|
52
|
-
**Example**
|
53
45
|
```ruby
|
54
46
|
require 'sinatra'
|
55
47
|
require 'sinatra/bouncer'
|
56
48
|
|
57
49
|
rules do
|
50
|
+
# example: allow any GET request
|
58
51
|
can(:get, :all)
|
59
52
|
|
60
|
-
# logged in users can edit their account
|
53
|
+
# example: logged in users can edit their account
|
61
54
|
if(current_user)
|
62
55
|
can(:post, '/user_edits_account')
|
63
56
|
end
|
@@ -66,8 +59,9 @@ end
|
|
66
59
|
# ... route declarations as normal below
|
67
60
|
```
|
68
61
|
|
62
|
+
## API
|
69
63
|
#### can
|
70
|
-
Any route declared with #can will be accepted
|
64
|
+
Any route declared with #can will be accepted without further challenge.
|
71
65
|
|
72
66
|
```ruby
|
73
67
|
rules do
|
@@ -80,7 +74,6 @@ end
|
|
80
74
|
`can_sometimes` takes a block that will be run once the path is attempted. This block **must return an explicit boolean**
|
81
75
|
(ie. `true` or `false`) to avoid any accidental truthy values creating unwanted access.
|
82
76
|
|
83
|
-
**Example**
|
84
77
|
```ruby
|
85
78
|
rules do
|
86
79
|
can_sometimes('/login') # Anyone can access this path
|
@@ -92,7 +85,6 @@ Passing `can` or `can_sometimes`:
|
|
92
85
|
* `:any` to the first parameter will match any HTTP method.
|
93
86
|
* `:all` to the second parameter will match any path.
|
94
87
|
|
95
|
-
**Examples**
|
96
88
|
```ruby
|
97
89
|
# this allows get on all paths
|
98
90
|
can(:get, :all)
|
@@ -101,12 +93,16 @@ can(:get, :all)
|
|
101
93
|
can(:any, '/login')
|
102
94
|
```
|
103
95
|
|
104
|
-
###Bounce
|
105
|
-
The default bounce action is to `halt
|
96
|
+
### Custom Bounce Behaviour
|
97
|
+
The default bounce action is to `halt 403`. Call `bounce_with` with a block to specify your own behaviour. The block is also run in a sinatra request context, so you can use helpers here as well.
|
106
98
|
|
107
|
-
**Example**
|
108
99
|
```ruby
|
109
|
-
|
110
|
-
|
100
|
+
require 'sinatra'
|
101
|
+
require 'sinatra/bouncer'
|
102
|
+
|
103
|
+
bounce_with do
|
104
|
+
redirect '/login'
|
111
105
|
end
|
106
|
+
|
107
|
+
# bouncer rules, routes, etc...
|
112
108
|
```
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative 'rule'
|
2
|
+
|
3
|
+
module Sinatra
|
4
|
+
module Bouncer
|
5
|
+
class BasicBouncer
|
6
|
+
attr_accessor :bounce_with
|
7
|
+
attr_accessor :rules_initializer
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# @rules = Hash.new do |method_to_paths, method|
|
11
|
+
# method_to_paths[method] = Hash.new do |path_to_rules, path|
|
12
|
+
# path_to_rules[path] = []
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
|
16
|
+
@ruleset = Hash.new do
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
@rules_initializer = Proc.new {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset!
|
23
|
+
@ruleset.clear
|
24
|
+
end
|
25
|
+
|
26
|
+
def can(method, *paths)
|
27
|
+
if block_given?
|
28
|
+
raise BouncerError.new('You cannot provide a block to #can. If you wish to conditionally allow, use #can_sometimes instead.')
|
29
|
+
end
|
30
|
+
|
31
|
+
can_sometimes(method, *paths) do
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
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.')
|
39
|
+
end
|
40
|
+
|
41
|
+
paths.each do |path|
|
42
|
+
@ruleset[method] += [Rule.new(path, &block)]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def can?(method, path)
|
47
|
+
rules = (@ruleset[:any_method] + @ruleset[method]).select { |rule| rule.match_path?(path) }
|
48
|
+
|
49
|
+
rules.any? do |rule_block|
|
50
|
+
ruling = rule_block.rule_passes?
|
51
|
+
|
52
|
+
ruling
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def bounce(instance)
|
57
|
+
if bounce_with
|
58
|
+
instance.instance_exec &bounce_with
|
59
|
+
else
|
60
|
+
instance.halt 403
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class BouncerError < StandardError
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/sinatra/bouncer.rb
CHANGED
@@ -21,122 +21,55 @@
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
+
require_relative 'basic_bouncer'
|
25
|
+
|
24
26
|
module Sinatra
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
module Bouncer
|
28
|
+
def self.registered(base_class)
|
29
|
+
base_class.helpers HelperMethods
|
28
30
|
|
29
|
-
|
31
|
+
bouncer = BasicBouncer.new
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
# TODO: can we instead store it somehow on the actual temp request object?
|
34
|
+
base_class.set :bouncer, bouncer
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
+
base_class.before do
|
37
|
+
bouncer.reset! # must clear all rules otherwise will leave doors open
|
36
38
|
|
37
|
-
|
39
|
+
self.instance_exec &bouncer.rules_initializer
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
+
http_method = request.request_method.downcase.to_sym
|
42
|
+
path = request.path.downcase
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
unless bouncer.can?(http_method, path)
|
45
|
+
bouncer.bounce(self)
|
46
|
+
end
|
47
|
+
end
|
45
48
|
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# Start ExtensionMethods
|
49
|
-
def bounce_with(&block)
|
50
|
-
bouncer.bounce_with = block
|
51
|
-
end
|
52
|
-
|
53
|
-
def rules(&block)
|
54
|
-
bouncer.rules_initializer = block
|
55
|
-
end
|
56
49
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
def can(*args)
|
61
|
-
settings.bouncer.can(*args)
|
50
|
+
# Start ExtensionMethods
|
51
|
+
def bounce_with(&block)
|
52
|
+
bouncer.bounce_with = block
|
62
53
|
end
|
63
54
|
|
64
|
-
def
|
65
|
-
|
55
|
+
def rules(&block)
|
56
|
+
bouncer.rules_initializer = block
|
66
57
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Data class
|
70
|
-
class BasicBouncer
|
71
|
-
attr_accessor :bounce_with
|
72
|
-
attr_accessor :rules_initializer
|
73
58
|
|
74
|
-
|
75
|
-
@rules = Hash.new do |method_to_paths, method|
|
76
|
-
method_to_paths[method] = Hash.new do |path_to_rules, path|
|
77
|
-
path_to_rules[path] = []
|
78
|
-
end
|
79
|
-
end
|
59
|
+
# End ExtensionMethods
|
80
60
|
|
81
|
-
|
82
|
-
|
61
|
+
module HelperMethods
|
62
|
+
def can(*args)
|
63
|
+
settings.bouncer.can(*args)
|
64
|
+
end
|
83
65
|
|
84
|
-
|
85
|
-
|
66
|
+
def can_sometimes(*args, &block)
|
67
|
+
settings.bouncer.can_sometimes(*args, &block)
|
68
|
+
end
|
86
69
|
end
|
70
|
+
end
|
87
71
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
can_sometimes(method, *paths) do
|
94
|
-
true
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def can_sometimes(method, *paths, &block)
|
99
|
-
unless block_given?
|
100
|
-
raise BouncerError.new('You must provide a block to #can_sometimes. If you wish to always allow, use #can instead.')
|
101
|
-
end
|
102
|
-
|
103
|
-
paths.each do |path|
|
104
|
-
@rules[method][path] << block
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def can?(method, path)
|
109
|
-
rules = @rules[:any_method][path] + @rules[method][:all] + @rules[method][path] #@rules[:all] + @rules[method]
|
110
|
-
|
111
|
-
rules.any? do |rule_block|
|
112
|
-
ruling = rule_block.call #(app)
|
113
|
-
|
114
|
-
if ruling != true && ruling != false
|
115
|
-
source = rule_block.source_location.join(':')
|
116
|
-
raise BouncerError.new("Rule block at does not return explicit true/false.\n\n"+
|
117
|
-
"Rules must return explicit true or false to prevent accidental truthy values.\n\n"+
|
118
|
-
"Source: #{source}\n")
|
119
|
-
end
|
120
|
-
|
121
|
-
ruling
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def bounce(instance)
|
126
|
-
if bounce_with
|
127
|
-
instance.instance_exec &bounce_with
|
128
|
-
else
|
129
|
-
instance.halt 403
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
class BouncerError < StandardError
|
135
|
-
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
if defined? register
|
140
|
-
register Bouncer
|
141
|
-
end
|
72
|
+
if defined? register
|
73
|
+
register Bouncer
|
74
|
+
end
|
142
75
|
end
|
data/lib/sinatra/rule.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module Bouncer
|
3
|
+
class Rule
|
4
|
+
def initialize(path, &ruleblock)
|
5
|
+
if path == :all
|
6
|
+
@path = :all
|
7
|
+
else
|
8
|
+
path = '/' + path unless path.start_with?('/')
|
9
|
+
|
10
|
+
@path = path.split('/')
|
11
|
+
end
|
12
|
+
|
13
|
+
@rule = ruleblock
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_path?(path)
|
17
|
+
return true if @path == :all
|
18
|
+
|
19
|
+
path = '/' + path unless path.start_with?('/')
|
20
|
+
|
21
|
+
split_path = path.split('/')
|
22
|
+
matches = @path.length == split_path.length
|
23
|
+
|
24
|
+
@path.each_index do |i|
|
25
|
+
allowed_segment = @path[i]
|
26
|
+
given_segment = split_path[i]
|
27
|
+
|
28
|
+
matches &= given_segment == allowed_segment || allowed_segment == '*'
|
29
|
+
end
|
30
|
+
|
31
|
+
matches
|
32
|
+
end
|
33
|
+
|
34
|
+
def rule_passes?
|
35
|
+
ruling = @rule.call
|
36
|
+
|
37
|
+
unless ruling.is_a?(TrueClass)|| ruling.is_a?(FalseClass)
|
38
|
+
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")
|
42
|
+
end
|
43
|
+
|
44
|
+
ruling
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-bouncer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Tenjin
|
@@ -10,118 +9,104 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2016-10-02 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: sinatra
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - ">="
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '0'
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - ">="
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '0'
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: simplecov
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
|
-
- -
|
32
|
+
- - ">="
|
37
33
|
- !ruby/object:Gem::Version
|
38
34
|
version: '0'
|
39
35
|
type: :development
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
|
-
- -
|
39
|
+
- - ">="
|
45
40
|
- !ruby/object:Gem::Version
|
46
41
|
version: '0'
|
47
42
|
- !ruby/object:Gem::Dependency
|
48
43
|
name: rspec
|
49
44
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
45
|
requirements:
|
52
|
-
- - ~>
|
46
|
+
- - "~>"
|
53
47
|
- !ruby/object:Gem::Version
|
54
48
|
version: 2.14.1
|
55
49
|
type: :development
|
56
50
|
prerelease: false
|
57
51
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
52
|
requirements:
|
60
|
-
- - ~>
|
53
|
+
- - "~>"
|
61
54
|
- !ruby/object:Gem::Version
|
62
55
|
version: 2.14.1
|
63
56
|
- !ruby/object:Gem::Dependency
|
64
57
|
name: cucumber
|
65
58
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
59
|
requirements:
|
68
|
-
- - ~>
|
60
|
+
- - "~>"
|
69
61
|
- !ruby/object:Gem::Version
|
70
62
|
version: 1.3.19
|
71
63
|
type: :development
|
72
64
|
prerelease: false
|
73
65
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
66
|
requirements:
|
76
|
-
- - ~>
|
67
|
+
- - "~>"
|
77
68
|
- !ruby/object:Gem::Version
|
78
69
|
version: 1.3.19
|
79
70
|
- !ruby/object:Gem::Dependency
|
80
71
|
name: capybara
|
81
72
|
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
73
|
requirements:
|
84
|
-
- -
|
74
|
+
- - ">="
|
85
75
|
- !ruby/object:Gem::Version
|
86
76
|
version: '0'
|
87
77
|
type: :development
|
88
78
|
prerelease: false
|
89
79
|
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
80
|
requirements:
|
92
|
-
- -
|
81
|
+
- - ">="
|
93
82
|
- !ruby/object:Gem::Version
|
94
83
|
version: '0'
|
95
84
|
- !ruby/object:Gem::Dependency
|
96
85
|
name: launchy
|
97
86
|
requirement: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
87
|
requirements:
|
100
|
-
- -
|
88
|
+
- - ">="
|
101
89
|
- !ruby/object:Gem::Version
|
102
90
|
version: '0'
|
103
91
|
type: :development
|
104
92
|
prerelease: false
|
105
93
|
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
94
|
requirements:
|
108
|
-
- -
|
95
|
+
- - ">="
|
109
96
|
- !ruby/object:Gem::Version
|
110
97
|
version: '0'
|
111
98
|
- !ruby/object:Gem::Dependency
|
112
99
|
name: parallel_tests
|
113
100
|
requirement: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
101
|
requirements:
|
116
|
-
- -
|
102
|
+
- - ">="
|
117
103
|
- !ruby/object:Gem::Version
|
118
104
|
version: '0'
|
119
105
|
type: :development
|
120
106
|
prerelease: false
|
121
107
|
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
108
|
requirements:
|
124
|
-
- -
|
109
|
+
- - ">="
|
125
110
|
- !ruby/object:Gem::Version
|
126
111
|
version: '0'
|
127
112
|
description: Bouncer brings simple authorization to Sinatra.
|
@@ -133,29 +118,30 @@ files:
|
|
133
118
|
- Gemfile
|
134
119
|
- MIT-LICENSE
|
135
120
|
- README.md
|
121
|
+
- lib/sinatra/basic_bouncer.rb
|
136
122
|
- lib/sinatra/bouncer.rb
|
123
|
+
- lib/sinatra/rule.rb
|
137
124
|
homepage: http://www.tenjin.ca
|
138
125
|
licenses: []
|
126
|
+
metadata: {}
|
139
127
|
post_install_message:
|
140
128
|
rdoc_options: []
|
141
129
|
require_paths:
|
142
130
|
- lib
|
143
131
|
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
-
none: false
|
145
132
|
requirements:
|
146
|
-
- -
|
133
|
+
- - ">="
|
147
134
|
- !ruby/object:Gem::Version
|
148
135
|
version: 1.9.3
|
149
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
-
none: false
|
151
137
|
requirements:
|
152
|
-
- -
|
138
|
+
- - ">="
|
153
139
|
- !ruby/object:Gem::Version
|
154
140
|
version: '0'
|
155
141
|
requirements: []
|
156
142
|
rubyforge_project:
|
157
|
-
rubygems_version:
|
143
|
+
rubygems_version: 2.4.8
|
158
144
|
signing_key:
|
159
|
-
specification_version:
|
145
|
+
specification_version: 4
|
160
146
|
summary: Sinatra permissions plugin
|
161
147
|
test_files: []
|