sinatra-bouncer 1.0.2 → 1.1
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.
- data/README.md +53 -19
- data/lib/sinatra/bouncer.rb +60 -21
- metadata +2 -2
data/README.md
CHANGED
@@ -25,7 +25,7 @@ gem install sinatra-bouncer
|
|
25
25
|
require 'sinatra'
|
26
26
|
require 'sinatra/bouncer'
|
27
27
|
|
28
|
-
# ... routes and other config
|
28
|
+
# ... routes and other config
|
29
29
|
```
|
30
30
|
|
31
31
|
**Modular**
|
@@ -40,39 +40,73 @@ class MyApp < Sinatra::Base
|
|
40
40
|
end
|
41
41
|
```
|
42
42
|
|
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
|
+
|
43
47
|
###Step 2: Declare Bouncer Rules
|
48
|
+
Call `rules` with a block that uses `can` and `can_sometimes` to declare which paths legal.
|
49
|
+
The rules block is run in the context of the request, which means you will have access to sinatra helpers,
|
50
|
+
the `request` object, and `params`.
|
44
51
|
|
45
|
-
|
46
|
-
|
52
|
+
**Example**
|
53
|
+
```ruby
|
54
|
+
require 'sinatra'
|
55
|
+
require 'sinatra/bouncer'
|
47
56
|
|
48
|
-
|
49
|
-
|
50
|
-
|
57
|
+
rules do
|
58
|
+
can(:get, :all)
|
59
|
+
|
60
|
+
# logged in users can edit their account
|
61
|
+
if(current_user)
|
62
|
+
can(:post, '/user_edits_account')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# ... route declarations as normal below
|
67
|
+
```
|
51
68
|
|
52
|
-
|
69
|
+
#### can
|
70
|
+
Any route declared with #can will be accepted this request without further challenge.
|
53
71
|
|
54
72
|
```ruby
|
55
|
-
|
56
|
-
|
73
|
+
rules do
|
74
|
+
can(:post, '/user_posts_blog')
|
57
75
|
end
|
58
76
|
```
|
59
77
|
|
60
|
-
####
|
61
|
-
`
|
78
|
+
####can_sometimes
|
79
|
+
`can_sometimes` is for occasions that you to check further, but want to defer that choice until the path is actually attempted.
|
80
|
+
`can_sometimes` takes a block that will be run once the path is attempted. This block **must return an explicit boolean**
|
81
|
+
(ie. `true` or `false`) to avoid any accidental truthy values creating unwanted access.
|
62
82
|
|
83
|
+
**Example**
|
63
84
|
```ruby
|
64
|
-
|
65
|
-
#
|
66
|
-
current_user.admin?
|
85
|
+
rules do
|
86
|
+
can_sometimes('/login') # Anyone can access this path
|
67
87
|
end
|
68
88
|
```
|
69
89
|
|
70
|
-
####
|
71
|
-
`
|
90
|
+
#### :any and :all special parameters
|
91
|
+
Passing `can` or `can_sometimes`:
|
92
|
+
* `:any` to the first parameter will match any HTTP method.
|
93
|
+
* `:all` to the second parameter will match any path.
|
72
94
|
|
95
|
+
**Examples**
|
73
96
|
```ruby
|
74
|
-
|
97
|
+
# this allows get on all paths
|
98
|
+
can(:get, :all)
|
99
|
+
|
100
|
+
# this allows any method type to run on the /login path
|
101
|
+
can(:any, '/login')
|
75
102
|
```
|
76
103
|
|
77
|
-
###Customization
|
78
|
-
The default bounce
|
104
|
+
###Bounce Customization
|
105
|
+
The default bounce action is to `halt 401`. Call `bounce_with` with a block that takes the sinatra application to change that behaviour.
|
106
|
+
|
107
|
+
**Example**
|
108
|
+
```ruby
|
109
|
+
bounce_with do |application|
|
110
|
+
application.redirect '/login'
|
111
|
+
end
|
112
|
+
```
|
data/lib/sinatra/bouncer.rb
CHANGED
@@ -24,62 +24,101 @@
|
|
24
24
|
module Sinatra
|
25
25
|
module Bouncer
|
26
26
|
def self.registered(base_class)
|
27
|
-
base_class.
|
27
|
+
base_class.helpers HelperMethods
|
28
|
+
|
29
|
+
bouncer = BasicBouncer.new
|
30
|
+
|
31
|
+
# TODO: can we instead store it somehow on the actual temp request object?
|
32
|
+
base_class.set :bouncer, bouncer
|
28
33
|
|
29
34
|
base_class.before do
|
30
|
-
|
31
|
-
|
35
|
+
bouncer.reset! # must clear all rules otherwise will leave doors open
|
36
|
+
|
37
|
+
self.instance_exec &bouncer.rules_initializer
|
38
|
+
|
39
|
+
http_method = request.request_method.downcase.to_sym
|
40
|
+
path = request.path.downcase
|
41
|
+
|
42
|
+
unless bouncer.can?(http_method, path)
|
43
|
+
bouncer.bounce(self)
|
32
44
|
end
|
33
45
|
end
|
34
46
|
end
|
35
47
|
|
48
|
+
# Start ExtensionMethods
|
36
49
|
def bounce_with(&block)
|
37
50
|
bouncer.bounce_with = block
|
38
51
|
end
|
39
52
|
|
53
|
+
def rules(&block)
|
54
|
+
bouncer.rules_initializer = block
|
55
|
+
end
|
56
|
+
|
57
|
+
# End ExtensionMethods
|
58
|
+
|
59
|
+
module HelperMethods
|
60
|
+
def can(*args)
|
61
|
+
settings.bouncer.can(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def can_sometimes(*args, &block)
|
65
|
+
settings.bouncer.can_sometimes(*args, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Data class
|
40
70
|
class BasicBouncer
|
41
71
|
attr_accessor :bounce_with
|
72
|
+
attr_accessor :rules_initializer
|
42
73
|
|
43
74
|
def initialize
|
44
|
-
@rules = Hash.new do |
|
45
|
-
|
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
|
46
79
|
end
|
80
|
+
|
81
|
+
@rules_initializer = Proc.new {}
|
82
|
+
end
|
83
|
+
|
84
|
+
def reset!
|
85
|
+
@rules.clear
|
47
86
|
end
|
48
87
|
|
49
|
-
def
|
50
|
-
|
88
|
+
def can(method, *paths)
|
89
|
+
if block_given?
|
90
|
+
raise BouncerError.new('You cannot provide a block to #can. If you wish to conditionally allow, use #can_sometimes instead.')
|
91
|
+
end
|
92
|
+
|
93
|
+
can_sometimes(method, *paths) do
|
51
94
|
true
|
52
95
|
end
|
53
96
|
end
|
54
97
|
|
55
|
-
def
|
56
|
-
unless
|
57
|
-
raise
|
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.')
|
58
101
|
end
|
59
102
|
|
60
|
-
paths = [paths] unless paths.is_a? Array
|
61
|
-
|
62
103
|
paths.each do |path|
|
63
|
-
@rules[path] << block
|
104
|
+
@rules[method][path] << block
|
64
105
|
end
|
65
106
|
end
|
66
107
|
|
67
|
-
def
|
68
|
-
rules = @rules[:all] + @rules[path]
|
69
|
-
|
70
|
-
# rules = @rules[path]
|
108
|
+
def can?(method, path)
|
109
|
+
rules = @rules[:any_method][path] + @rules[method][:all] + @rules[method][path] #@rules[:all] + @rules[method]
|
71
110
|
|
72
111
|
rules.any? do |rule_block|
|
73
|
-
ruling = rule_block.call
|
112
|
+
ruling = rule_block.call #(app)
|
74
113
|
|
75
|
-
if ruling
|
76
|
-
ruling
|
77
|
-
else
|
114
|
+
if ruling != true && ruling != false
|
78
115
|
source = rule_block.source_location.join(':')
|
79
116
|
raise BouncerError.new("Rule block at does not return explicit true/false.\n\n"+
|
80
117
|
"Rules must return explicit true or false to prevent accidental truthy values.\n\n"+
|
81
118
|
"Source: #{source}\n")
|
82
119
|
end
|
120
|
+
|
121
|
+
ruling
|
83
122
|
end
|
84
123
|
end
|
85
124
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-bouncer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: '1.1'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-05-
|
13
|
+
date: 2015-05-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: sinatra
|