sinatra-bouncer 1.0.2 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
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