sinatra-bouncer 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|