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.
@@ -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::Bouncer
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
- **Command Line**
9
+ **Terminal**
16
10
  ```sh
17
11
  gem install sinatra-bouncer
18
12
  ```
19
13
 
20
- ##Usage
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 legal.
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 this request without further challenge.
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 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.
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
- bounce_with do |application|
110
- application.redirect '/login'
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
@@ -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
- module Bouncer
26
- def self.registered(base_class)
27
- base_class.helpers HelperMethods
27
+ module Bouncer
28
+ def self.registered(base_class)
29
+ base_class.helpers HelperMethods
28
30
 
29
- bouncer = BasicBouncer.new
31
+ bouncer = BasicBouncer.new
30
32
 
31
- # TODO: can we instead store it somehow on the actual temp request object?
32
- base_class.set :bouncer, bouncer
33
+ # TODO: can we instead store it somehow on the actual temp request object?
34
+ base_class.set :bouncer, bouncer
33
35
 
34
- base_class.before do
35
- bouncer.reset! # must clear all rules otherwise will leave doors open
36
+ base_class.before do
37
+ bouncer.reset! # must clear all rules otherwise will leave doors open
36
38
 
37
- self.instance_exec &bouncer.rules_initializer
39
+ self.instance_exec &bouncer.rules_initializer
38
40
 
39
- http_method = request.request_method.downcase.to_sym
40
- path = request.path.downcase
41
+ http_method = request.request_method.downcase.to_sym
42
+ path = request.path.downcase
41
43
 
42
- unless bouncer.can?(http_method, path)
43
- bouncer.bounce(self)
44
- end
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
- # End ExtensionMethods
58
-
59
- module HelperMethods
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 can_sometimes(*args, &block)
65
- settings.bouncer.can_sometimes(*args, &block)
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
- def initialize
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
- @rules_initializer = Proc.new {}
82
- end
61
+ module HelperMethods
62
+ def can(*args)
63
+ settings.bouncer.can(*args)
64
+ end
83
65
 
84
- def reset!
85
- @rules.clear
66
+ def can_sometimes(*args, &block)
67
+ settings.bouncer.can_sometimes(*args, &block)
68
+ end
86
69
  end
70
+ end
87
71
 
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
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
@@ -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.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: 2015-06-01 00:00:00.000000000 Z
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: 1.8.24
143
+ rubygems_version: 2.4.8
158
144
  signing_key:
159
- specification_version: 3
145
+ specification_version: 4
160
146
  summary: Sinatra permissions plugin
161
147
  test_files: []