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.
Files changed (3) hide show
  1. data/README.md +53 -19
  2. data/lib/sinatra/bouncer.rb +60 -21
  3. 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
- #### allow
46
- Bouncer is stored in Sinatra's `settings` object, under `settings.bouncer`.
52
+ **Example**
53
+ ```ruby
54
+ require 'sinatra'
55
+ require 'sinatra/bouncer'
47
56
 
48
- By default, Bouncer will reject any request that either:
49
- * has no rule associated with it, or
50
- * has no associated rule that returns `true`
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
- Declare rules by calling `bouncer.allow` and providing a rule block. Rule blocks **must return an explicit boolean** (ie. `true` or `false`) to avoid any accidental truthy values creating unwanted access.
69
+ #### can
70
+ Any route declared with #can will be accepted this request without further challenge.
53
71
 
54
72
  ```ruby
55
- bouncer.allow('/user_posts_blog') do
56
- # calculate and return some boolean result
73
+ rules do
74
+ can(:post, '/user_posts_blog')
57
75
  end
58
76
  ```
59
77
 
60
- ####allow(:all)
61
- `allow(:all)` will match any path.
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
- allow(:all) do
65
- # assuming a current_user helper to load the user object (like with warden)
66
- current_user.admin?
85
+ rules do
86
+ can_sometimes('/login') # Anyone can access this path
67
87
  end
68
88
  ```
69
89
 
70
- ####always_allow
71
- `always_allow(...)` is shorthand for `allow(..) { true }`.
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
- always_allow('/login') # Anyone can access this path
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 acion is to `halt 401`. Call `bounce_with` with a block to change that behaviour.
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
+ ```
@@ -24,62 +24,101 @@
24
24
  module Sinatra
25
25
  module Bouncer
26
26
  def self.registered(base_class)
27
- base_class.set :bouncer, BasicBouncer.new
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
- unless settings.bouncer.allows? request.path
31
- settings.bouncer.bounce(self)
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 |rules_hash, key|
45
- rules_hash[key] = []
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 always_allow(paths)
50
- self.allow(paths) do
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 allow(paths, &block)
56
- unless block
57
- raise Sinatra::Bouncer::BouncerError.new('You must provide a block to #allow. If you wish to always allow, either return true or use #always_allow instead')
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 allows?(path)
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 == true || ruling == false
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.0.2
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-24 00:00:00.000000000 Z
13
+ date: 2015-05-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: sinatra