sinatra-bouncer 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 254ec098fccec946d8f3891767aec06c0b176ba1
4
- data.tar.gz: e9289acabf308082675f3176367c3016f3af791b
2
+ SHA256:
3
+ metadata.gz: f07c9c87e55563ac21610aecc8813d32ee3322d291382bb1bd5cca0ce0d55a12
4
+ data.tar.gz: fa55400409722c0f0f4ea83c6ee552654d0d8aee128d484f3a42c2c888e9b46b
5
5
  SHA512:
6
- metadata.gz: b42b9091612bc206cc384f346108eee042de851c8793b09772d8c9fc818df7595c4a9cd2dfe5443b9f61ab045748d49082677e77f0e901abd1c78d4fa8d232a8
7
- data.tar.gz: 34231f5cbb55f638c6d6b977c080dd9a77ed5f4a3e561d90221b85c413e3717330d433eb06c2564d6613e5b325dc92b02e0ad34ace65ebbb5502748e15abac3b
6
+ metadata.gz: 4fc6e73dcb5867a418dea6b555f74fee57f41b58d75ab76657925a71b1b2ff059295135c30646f4de83fe82a00bc816a6d668d54ecd64f035b63f27d68b80ef1
7
+ data.tar.gz: ca808e79a1f59743c17afc6e7475dd03a9a3186d8ed1ec3c6b4412ec96af2c38c0e454a0fada15c513e83c1070eb7b6f72c2a116be9e2ed7135592ce7f04ce70
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile ~/.gitignore_global
6
+
7
+ # Ignore all logfiles and tempfiles.
8
+ *.log
9
+ tmp/*
10
+
11
+ # Rubymine stuff
12
+ .idea
13
+
14
+ # Ignore all gem files.
15
+ *.gem
16
+
17
+ # Ignore simplecov results
18
+ .coverage/
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ inherit_from: ~/.config/rubocop/config.yml
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - 'bin/*'
6
+ - 'benchmarks/*'
7
+
8
+ Layout/LineLength:
9
+ Exclude:
10
+ - 'spec/**/*.rb'
11
+
12
+ # setting to 6 to match RubyMine autoformat
13
+ Layout/FirstArrayElementIndentation:
14
+ IndentationWidth: 6
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.1.4
data/.simplecov ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ SimpleCov.start do
4
+ coverage_dir '.coverage'
5
+ enable_coverage :branch
6
+
7
+ root __dir__
8
+
9
+ add_filter 'tests/'
10
+ # add_filter 'spec/'
11
+ end
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at robin@tenjin.ca. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile CHANGED
@@ -1,4 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
- # Specify your gem's dependencies in dirt_core.gemspec
4
- gemspec
5
+ # Gem dependencies in dirt_core.gemspec
6
+ gemspec
7
+
8
+ group :development do
9
+ gem 'bundler', '~> 2.4'
10
+ gem 'rake', '~> 13.1'
11
+ gem 'rubocop', '~> 1.57'
12
+ gem 'rubocop-performance', '~> 1.19'
13
+ gem 'yard', '~> 0.9'
14
+ end
15
+
16
+ group :test do
17
+ gem 'rack-test', '~> 2.1'
18
+ gem 'rspec', '~> 3.12'
19
+ gem 'simplecov', '~> 0.22'
20
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,92 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sinatra-bouncer (2.0.0)
5
+ sinatra (>= 2.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ diff-lcs (1.5.0)
12
+ docile (1.4.0)
13
+ json (2.6.3)
14
+ language_server-protocol (3.17.0.3)
15
+ mustermann (3.0.0)
16
+ ruby2_keywords (~> 0.0.1)
17
+ parallel (1.23.0)
18
+ parser (3.2.2.4)
19
+ ast (~> 2.4.1)
20
+ racc
21
+ racc (1.7.3)
22
+ rack (2.2.8)
23
+ rack-protection (3.1.0)
24
+ rack (~> 2.2, >= 2.2.4)
25
+ rack-test (2.1.0)
26
+ rack (>= 1.3)
27
+ rainbow (3.1.1)
28
+ rake (13.1.0)
29
+ regexp_parser (2.8.2)
30
+ rexml (3.2.6)
31
+ rspec (3.12.0)
32
+ rspec-core (~> 3.12.0)
33
+ rspec-expectations (~> 3.12.0)
34
+ rspec-mocks (~> 3.12.0)
35
+ rspec-core (3.12.2)
36
+ rspec-support (~> 3.12.0)
37
+ rspec-expectations (3.12.3)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.12.0)
40
+ rspec-mocks (3.12.6)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.12.0)
43
+ rspec-support (3.12.1)
44
+ rubocop (1.57.2)
45
+ json (~> 2.3)
46
+ language_server-protocol (>= 3.17.0)
47
+ parallel (~> 1.10)
48
+ parser (>= 3.2.2.4)
49
+ rainbow (>= 2.2.2, < 4.0)
50
+ regexp_parser (>= 1.8, < 3.0)
51
+ rexml (>= 3.2.5, < 4.0)
52
+ rubocop-ast (>= 1.28.1, < 2.0)
53
+ ruby-progressbar (~> 1.7)
54
+ unicode-display_width (>= 2.4.0, < 3.0)
55
+ rubocop-ast (1.30.0)
56
+ parser (>= 3.2.1.0)
57
+ rubocop-performance (1.19.1)
58
+ rubocop (>= 1.7.0, < 2.0)
59
+ rubocop-ast (>= 0.4.0)
60
+ ruby-progressbar (1.13.0)
61
+ ruby2_keywords (0.0.5)
62
+ simplecov (0.22.0)
63
+ docile (~> 1.1)
64
+ simplecov-html (~> 0.11)
65
+ simplecov_json_formatter (~> 0.1)
66
+ simplecov-html (0.12.3)
67
+ simplecov_json_formatter (0.1.4)
68
+ sinatra (3.1.0)
69
+ mustermann (~> 3.0)
70
+ rack (~> 2.2, >= 2.2.4)
71
+ rack-protection (= 3.1.0)
72
+ tilt (~> 2.0)
73
+ tilt (2.2.0)
74
+ unicode-display_width (2.5.0)
75
+ yard (0.9.34)
76
+
77
+ PLATFORMS
78
+ x86_64-linux
79
+
80
+ DEPENDENCIES
81
+ bundler (~> 2.4)
82
+ rack-test (~> 2.1)
83
+ rake (~> 13.1)
84
+ rspec (~> 3.12)
85
+ rubocop (~> 1.57)
86
+ rubocop-performance (~> 1.19)
87
+ simplecov (~> 0.22)
88
+ sinatra-bouncer!
89
+ yard (~> 0.9)
90
+
91
+ BUNDLED WITH
92
+ 2.4.19
data/README.md CHANGED
@@ -1,108 +1,268 @@
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.
1
+ # Sinatra-Bouncer
2
+
3
+ Simple permissions extension for [Sinatra](http://www.sinatrarb.com/). Require the gem, then declare which
4
+ routes are permitted based on your own logic.
5
+
6
+ ## Big Picture
7
+
8
+ Bouncer rules look like:
3
9
 
4
- **Gemfile**
5
10
  ```ruby
6
- gem 'sinatra-bouncer'
7
- ```
11
+ rules do
12
+ # Routes match based on one or more strings.
13
+ can get: '/',
14
+ post: %w[/user/sign-in
15
+ /user/sign-out]
16
+
17
+ # Wildcards match anything directly under that path
18
+ can get: '/lib/js/*'
19
+
20
+ # Use a conditional rule block for additional logic
21
+ can_sometimes get: '/admin/*',
22
+ post: '/admin/actions/*' do
23
+ current_user.admin?
24
+ end
25
+
26
+ # ... etc ...
27
+ end
8
28
 
9
- **Terminal**
10
- ```sh
11
- gem install sinatra-bouncer
12
29
  ```
13
30
 
14
- ##Quickstart
15
- ###Step 1: Require/Register Bouncer
31
+ ## Features
16
32
 
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`
33
+ Here's what this Gem provides:
34
+
35
+ * **Block-by-default**
36
+ * Any route must be explicitly allowed
37
+ * **Declarative Syntax**
38
+ * Straightforward syntax reduces complexity of layered permissions
39
+ * Keeps permissions together for clarity
40
+ * **Conditional Logic Via Blocks**
41
+ * Often additional checks must be performed to know if a route is allowed.
42
+ * **Grouping**
43
+ * Routes can be matched by wildcard
44
+ * **Forced Boolean Affirmation**
45
+ * Condition blocks must explicitly return `true`, avoiding accidental truthy values
46
+
47
+ ## Anti-Features
48
+
49
+ Bouncer intentionally does not support some concepts.
50
+
51
+ * **No User Identification** *(aka Authentication)*
52
+ * Bouncer is not a user identification gem. It assumes you already have an existing solution
53
+ like [Warden](https://github.com/wardencommunity/warden). Knowing *who* someone is is already a complex enough
54
+ problem and should be separate from what they may do.
55
+ * **No Rails Integration**
56
+ * Bouncer is not intended to work with Rails. Use one of the Rails-based permissions gems for these situations.
57
+ * **No Negation**
58
+ * There is intentionally no negation (eg. `cannot`) to preserve the default-deny frame of mind
59
+
60
+ ## Installation
61
+
62
+ Add this to your gemfile:
20
63
 
21
- **Sinatra Classic**
22
64
  ```ruby
23
- require 'sinatra'
24
- require 'sinatra/bouncer'
65
+ gem 'sinatra-bouncer'
66
+ ```
25
67
 
26
- # ... routes and other config
68
+ Then run:
69
+
70
+ ```shell
71
+ bundle install
27
72
  ```
28
73
 
29
- **Modular**
74
+ **Sinatra Modular Style**
75
+
30
76
  ```ruby
31
77
  require 'sinatra/base'
32
78
  require 'sinatra/bouncer'
33
79
 
34
80
  class MyApp < Sinatra::Base
35
- register Sinatra::Bouncer
36
-
37
- # ... routes and other config
81
+ register Sinatra::Bouncer
82
+
83
+ rules do
84
+ # ... can statements ...
85
+ end
86
+
87
+ # ... routes and other config
38
88
  end
39
89
  ```
40
90
 
41
- ###Step 2: Declare Bouncer Rules
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,
43
- the `request` object, and `params`.
91
+ **Sinatra Classic Style**
44
92
 
45
93
  ```ruby
46
94
  require 'sinatra'
47
95
  require 'sinatra/bouncer'
48
96
 
49
97
  rules do
50
- # example: allow any GET request
51
- can(:get, :all)
52
-
53
- # example: logged in users can edit their account
54
- if(current_user)
55
- can(:post, '/user_edits_account')
56
- end
98
+ # ... can statements ...
57
99
  end
58
100
 
59
- # ... route declarations as normal below
101
+ # ... routes and other config
60
102
  ```
61
103
 
62
- ## API
63
- #### can
64
- Any route declared with #can will be accepted without further challenge.
104
+ ## Usage
105
+
106
+ Call `rules` with a block that uses the `#can` and `#can_sometimes` DSL methods to declare rules for paths.
107
+
108
+ The rules block is run once as part of the configuration phase but the condition blocks are evaluated in the context of
109
+ the
110
+ request, which means you will have access to Sinatra helpers,
111
+ the `request` object, and `params`.
65
112
 
66
113
  ```ruby
114
+ require 'sinatra'
115
+ require 'sinatra/bouncer'
116
+
67
117
  rules do
68
- can(:post, '/user_posts_blog')
118
+ # example: always allow GET requests to root path or /sign-in
119
+ can get: %w[/
120
+ /sign-in]
121
+
122
+ # example: logged in users can view (GET) member restricted paths and edit their account (POST)
123
+ can_sometimes get: '/members/*',
124
+ post: '/members/edit-account' do
125
+ !current_user.nil?
126
+ end
127
+
128
+ # example: check an arbitrary request header is present
129
+ can_sometimes get: '/bots/*' do
130
+ !request.get_header('X-CUSTOM_PROP').nil?
131
+ end
69
132
  end
133
+
134
+ # ... Sinatra route declarations as normal ...
70
135
  ```
71
136
 
72
- ####can_sometimes
73
- `can_sometimes` is for occasions that you to check further, but want to defer that choice until the path is actually attempted.
74
- `can_sometimes` takes a block that will be run once the path is attempted. This block **must return an explicit boolean**
75
- (ie. `true` or `false`) to avoid any accidental truthy values creating unwanted access.
137
+ ### HTTP Method and Route Matching
138
+
139
+ Both `#can` and `#can_sometimes` accept multiple
140
+ [HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) as symbols
141
+ and each key is paired with one or more path strings.
142
+
143
+ ```ruby
144
+ # example: single method, single route
145
+ can get: '/'
146
+
147
+ # example: multiple methods, single route each
148
+ can get: '/',
149
+ post: '/blog/action/save'
150
+
151
+ # example: multiple methods, multiple routes (using string array syntax)
152
+ can get: %w[/
153
+ /sign-in
154
+ /blog/editor],
155
+ post: %w[/blog/action/save
156
+ /blog/action/delete]
157
+ ```
158
+
159
+ > **Note** Allowing GET implies allowing HEAD, since HEAD is by spec a GET without a response body. The reverse is not
160
+ > true, however; allowing HEAD will not also allow GET.
161
+
162
+ #### Wildcards and Special Symbols
163
+
164
+ > **Warning** Always be cautious when using wildcards and special symbols to not accidentally open up pathways that
165
+ > should remain private.
166
+
167
+ Provide a wildcard `*` to match any string excluding slash. There is intentionally no syntax for matching wildcards
168
+ recursively, so nested paths will also need to be declared.
169
+
170
+ ```ruby
171
+ # example: match anything directly under the /members/ path
172
+ can get: '/members/*'
173
+ ```
174
+
175
+ There are also 2 special symbols:
176
+
177
+ 1. `:any` will match any HTTP method.
178
+ 2. `:all` will match all paths.
179
+
180
+ ```ruby
181
+ # this allows any method type on the / path
182
+ can any: '/'
183
+
184
+ # this allows GET on all paths
185
+ can get: :all
186
+ ```
187
+
188
+ ### Always Allow: `can`
189
+
190
+ Any route declared with `#can` will be accepted without further challenge.
76
191
 
77
192
  ```ruby
78
193
  rules do
79
- can_sometimes('/login') # Anyone can access this path
194
+ # Anyone can access this path over GET
195
+ can get: '/login'
80
196
  end
81
197
  ```
82
198
 
83
- #### :any and :all special parameters
84
- Passing `can` or `can_sometimes`:
85
- * `:any` to the first parameter will match any HTTP method.
86
- * `:all` to the second parameter will match any path.
199
+ ### Conditionally Allow: `can_sometimes`
200
+
201
+ `can_sometimes` is for occasions that you to check further, but want to defer that choice until the path is actually
202
+ attempted.
203
+ `can_sometimes` takes a block that will be run once the path is attempted. This block **must return an explicit boolean
204
+ **
205
+ (ie. `true` or `false`) to avoid any accidental truthy values creating unwanted access.
87
206
 
88
207
  ```ruby
89
- # this allows get on all paths
90
- can(:get, :all)
208
+ rules do
209
+ can_sometimes get: '/login' # Anyone can access this path over GET
91
210
 
92
- # this allows any method type to run on the /login path
93
- can(:any, '/login')
211
+ can_sometimes post: '/user/blog/actions/save' do
212
+ !current_user.nil?
213
+ end
214
+ end
94
215
  ```
95
216
 
96
217
  ### 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.
218
+
219
+ The default bounce action is to `halt 403`. Call `bounce_with` with a block to specify your own behaviour. The block is
220
+ also run in a sinatra request context, so you can use helpers here as well.
98
221
 
99
222
  ```ruby
100
223
  require 'sinatra'
101
224
  require 'sinatra/bouncer'
102
225
 
103
- bounce_with do
104
- redirect '/login'
226
+ bounce_with do
227
+ redirect '/login'
105
228
  end
106
229
 
107
230
  # bouncer rules, routes, etc...
108
231
  ```
232
+
233
+ ## Alternatives
234
+
235
+ The syntax for Bouncer is largely influenced by the now-deprecated [CanCan](https://github.com/ryanb/cancan) gem.
236
+
237
+ ## Contributing
238
+
239
+ Bug reports and pull requests are welcome on GitHub at https://github.com/TenjinInc/Sinatra-Bouncer.
240
+
241
+ Valued topics:
242
+
243
+ * Error messages (clarity, hinting)
244
+ * Documentation
245
+ * API
246
+ * Security correctness
247
+
248
+ This project is intended to be a friendly space for collaboration, and contributors are expected to adhere to the
249
+ [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct. Play nice.
250
+
251
+ ### Core Developers
252
+
253
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests. You
254
+ can also run `bin/console` for an interactive prompt that will allow you to experiment.
255
+
256
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
257
+ version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
258
+ push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
259
+
260
+ Documentation is produced by Yard. Run `bundle exec rake yard`. The goal is to have 100% documentation coverage and 100%
261
+ test coverage.
262
+
263
+ Release notes are provided in `RELEASE_NOTES.md`, and should vaguely
264
+ follow [Keep A Changelog](https://keepachangelog.com/en/1.0.0/) recommendations.
265
+
266
+ ## License
267
+
268
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/license/mit/).
data/RELEASE_NOTES.md ADDED
@@ -0,0 +1,153 @@
1
+ # Release Notes
2
+
3
+ All notable changes to this project will be documented below.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project loosely follows
6
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Major Changes
11
+
12
+ * none
13
+
14
+ ### Minor Changes
15
+
16
+ * none
17
+
18
+ ### Bugfixes
19
+
20
+ * none
21
+
22
+ ## [2.0.0] - 2023-11-13
23
+
24
+ ### Major Changes
25
+
26
+ * Converted to hash syntax in `can` and `can_sometimes` statements
27
+
28
+ ### Minor Changes
29
+
30
+ * Converted Cucumber tests to Rspec integration tests for consistency
31
+ * HEAD requests are evaluated like GET requests due to semantic equivalence
32
+ * Falsey values are now acceptable rule block results
33
+
34
+ ### Bugfixes
35
+
36
+ * none
37
+
38
+ ## [1.3.0] - 2023-09-13
39
+
40
+ ### Major Changes
41
+
42
+ * none
43
+
44
+ ### Minor Changes
45
+
46
+ * Increased minimum Ruby to 3.1
47
+ * Cleaned up development dependencies
48
+ * Rubocop cleanups
49
+ * Installed Simplecov
50
+ * Created Rakefile
51
+
52
+ ### Bugfixes
53
+
54
+ * none
55
+
56
+ ## [1.2.0] - 2016-10-02
57
+
58
+ ### Major Changes
59
+
60
+ * none
61
+
62
+ ### Minor Changes
63
+
64
+ * Supports wildcard matches in route strings
65
+
66
+ ### Bugfixes
67
+
68
+ * none
69
+
70
+ ## [1.1.1] - 2015-06-02
71
+
72
+ ### Major Changes
73
+
74
+ * none
75
+
76
+ ### Minor Changes
77
+
78
+ * none
79
+
80
+ ### Bugfixes
81
+
82
+ * Runs `bounce_with` in context of web request
83
+
84
+ ## [1.1.0] - 2015-05-28
85
+
86
+ ### Major Changes
87
+
88
+ * none
89
+
90
+ ### Minor Changes
91
+
92
+ * none
93
+
94
+ ### Bugfixes
95
+
96
+ * Correctly forgets rules between routes
97
+
98
+ ## [1.0.2] - 2015-05-24
99
+
100
+ ### Major Changes
101
+
102
+ * none
103
+
104
+ ### Minor Changes
105
+
106
+ * Changed default halt to 403
107
+ * Application available in rule block
108
+
109
+ ### Bugfixes
110
+
111
+ * Fixed sinatra module registration
112
+
113
+ ## [1.0.1] - 2015-05-21
114
+
115
+ ### Major Changes
116
+
117
+ * none
118
+
119
+ ### Minor Changes
120
+
121
+ * none
122
+
123
+ ### Bugfixes
124
+
125
+ * none
126
+
127
+ ## [1.0.0] - Unreleased Prototype
128
+
129
+ ### Major Changes
130
+
131
+ * none
132
+
133
+ ### Minor Changes
134
+
135
+ * none
136
+
137
+ ### Bugfixes
138
+
139
+ * none
140
+
141
+ ## [0.1.0] - Unreleased Prototype
142
+
143
+ ### Major Changes
144
+
145
+ * Initial prototype
146
+
147
+ ### Minor Changes
148
+
149
+ * none
150
+
151
+ ### Bugfixes
152
+
153
+ * none
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'yard'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task default: :spec
10
+
11
+ YARD::Rake::YardocTask.new do |t|
12
+ t.files = %w[lib/**/*.rb]
13
+ # t.options = %w[--some-option]
14
+ t.stats_options = ['--list-undoc']
15
+ end
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: --publish-quiet
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rule'
4
+
5
+ module Sinatra
6
+ module Bouncer
7
+ # Core implementation of Bouncer logic
8
+ class BasicBouncer
9
+ attr_accessor :bounce_with, :rules_initializer
10
+
11
+ # Enumeration of HTTP method strings based on:
12
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
13
+ # Ignoring CONNECT and TRACE due to rarity
14
+ HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH].freeze
15
+
16
+ # Symbol versions of HTTP_METHODS constant
17
+ #
18
+ # @see HTTP_METHODS
19
+ HTTP_METHOD_SYMBOLS = HTTP_METHODS.collect do |http_method|
20
+ http_method.downcase.to_sym
21
+ end.freeze
22
+
23
+ # Method symbol used to match any method
24
+ WILDCARD_METHOD = :any
25
+
26
+ def initialize
27
+ @ruleset = Hash.new do
28
+ []
29
+ end
30
+
31
+ @rules_initializer = proc {}
32
+ end
33
+
34
+ def can(**method_routes)
35
+ if block_given?
36
+ hint = 'If you wish to conditionally allow, use #can_sometimes instead.'
37
+ raise BouncerError, "You cannot provide a block to #can. #{ hint }"
38
+ end
39
+
40
+ can_sometimes(**method_routes) do
41
+ true
42
+ end
43
+ end
44
+
45
+ def can_sometimes(**method_routes, &block)
46
+ unless block
47
+ hint = 'If you wish to always allow, use #can instead.'
48
+ raise BouncerError, "You must provide a block to #can_sometimes. #{ hint }"
49
+ end
50
+
51
+ method_routes.each do |method, paths|
52
+ unless HTTP_METHOD_SYMBOLS.include?(method) || method == WILDCARD_METHOD
53
+ hint = "Must be one of: #{ HTTP_METHOD_SYMBOLS } or :#{ WILDCARD_METHOD }"
54
+ raise BouncerError, "'#{ method }' is not a known HTTP method key. #{ hint }"
55
+ end
56
+
57
+ paths = [paths] unless paths.respond_to? :collect
58
+
59
+ @ruleset[method] += paths.collect { |path| Rule.new(path, &block) }
60
+ end
61
+ end
62
+
63
+ def can?(method, path, context)
64
+ rulesets = @ruleset[WILDCARD_METHOD] + @ruleset[method]
65
+
66
+ # HEAD requests are equivalent to GET requests without response
67
+ rulesets += @ruleset[:get] if method == :head
68
+
69
+ rules = rulesets.select { |rule| rule.match_path?(path) }
70
+
71
+ rules.any? do |rule_block|
72
+ ruling = rule_block.rule_passes? context
73
+
74
+ ruling
75
+ end
76
+ end
77
+
78
+ def bounce(instance)
79
+ if bounce_with
80
+ instance.instance_exec(&bounce_with)
81
+ else
82
+ instance.halt 403
83
+ end
84
+ end
85
+ end
86
+
87
+ class BouncerError < StandardError
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Bouncer
5
+ # Defines a Rule to be evaluated with each request
6
+ class Rule
7
+ def initialize(path, &rule_block)
8
+ if path == :all
9
+ @path = :all
10
+ else
11
+ path = "/#{ path }" unless path.start_with?('/')
12
+
13
+ @path = path.split('/')
14
+ end
15
+
16
+ @rule = rule_block
17
+ end
18
+
19
+ # Determines if the path matches the exact path or wildcard.
20
+ #
21
+ # @return `true` if the path matches
22
+ def match_path?(path)
23
+ return true if @path == :all
24
+
25
+ path = "/#{ path }" unless path.start_with?('/')
26
+
27
+ split_path = path.split('/')
28
+ matches = @path.length == split_path.length
29
+
30
+ @path.each_index do |i|
31
+ allowed_segment = @path[i]
32
+ given_segment = split_path[i]
33
+
34
+ matches &= given_segment == allowed_segment || allowed_segment == '*'
35
+ end
36
+
37
+ matches
38
+ end
39
+
40
+ # Evaluates the rule's block. Defensively prevents truthy values from the block from allowing a route.
41
+ #
42
+ # @raise BouncerError when the rule block is a truthy value but not exactly `true`
43
+ # @return Exactly `true` or `false`, depending on the result of the rule block
44
+ def rule_passes?(context)
45
+ ruling = context.instance_exec(&@rule)
46
+
47
+ unless !ruling || ruling.is_a?(TrueClass)
48
+ source = @rule.source_location.join(':')
49
+ msg = <<~ERR
50
+ Rule block at does not return explicit true/false.
51
+ Rules must return explicit true or false to prevent accidental truthy values.
52
+ Source: #{ source }
53
+ ERR
54
+
55
+ raise BouncerError, msg
56
+ end
57
+
58
+ !!ruling
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Bouncer
5
+ # Current version of the gem
6
+ VERSION = '2.0.0'
7
+ end
8
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #--
2
4
  # Copyright (c) 2014 Tenjin Inc.
3
5
  #
@@ -21,55 +23,32 @@
21
23
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
24
  #++
23
25
 
24
- require_relative 'basic_bouncer'
26
+ require 'sinatra/base'
27
+ require_relative 'bouncer/basic_bouncer'
25
28
 
29
+ # Namespace module
26
30
  module Sinatra
31
+ # Namespace module
27
32
  module Bouncer
28
33
  def self.registered(base_class)
29
- base_class.helpers HelperMethods
30
-
31
- bouncer = BasicBouncer.new
32
-
33
- # TODO: can we instead store it somehow on the actual temp request object?
34
- base_class.set :bouncer, bouncer
34
+ base_class.set :bouncer, BasicBouncer.new
35
35
 
36
36
  base_class.before do
37
- bouncer.reset! # must clear all rules otherwise will leave doors open
38
-
39
- self.instance_exec &bouncer.rules_initializer
40
-
41
37
  http_method = request.request_method.downcase.to_sym
42
38
  path = request.path.downcase
43
39
 
44
- unless bouncer.can?(http_method, path)
45
- bouncer.bounce(self)
46
- end
40
+ settings.bouncer.bounce(self) unless settings.bouncer.can?(http_method, path, self)
47
41
  end
48
42
  end
49
43
 
50
- # Start ExtensionMethods
51
44
  def bounce_with(&block)
52
45
  bouncer.bounce_with = block
53
46
  end
54
47
 
55
48
  def rules(&block)
56
- bouncer.rules_initializer = block
57
- end
58
-
59
- # End ExtensionMethods
60
-
61
- module HelperMethods
62
- def can(*args)
63
- settings.bouncer.can(*args)
64
- end
65
-
66
- def can_sometimes(*args, &block)
67
- settings.bouncer.can_sometimes(*args, &block)
68
- end
49
+ settings.bouncer.instance_exec(&block)
69
50
  end
70
51
  end
71
52
 
72
- if defined? register
73
- register Bouncer
74
- end
75
- end
53
+ register Sinatra::Bouncer
54
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'sinatra/bouncer/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'sinatra-bouncer'
10
+ spec.version = Sinatra::Bouncer::VERSION
11
+ spec.authors = ['Tenjin Inc', 'Robin Miller']
12
+ spec.email = %w[contact@tenjin.ca robin@tenjin.ca]
13
+
14
+ spec.summary = 'Sinatra permissions plugin'
15
+ spec.description = 'Bouncer brings simple authorization to Sinatra.'
16
+ spec.homepage = 'https://github.com/TenjinInc/Sinatra-Bouncer'
17
+ spec.license = 'MIT'
18
+ spec.metadata = {
19
+ 'rubygems_mfa_required' => 'true'
20
+ }
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features|integrations|benchmarks)/})
24
+ end
25
+
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.required_ruby_version = '>= 3.1'
29
+
30
+ spec.add_dependency 'sinatra', '>= 2.2'
31
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-bouncer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Tenjin
7
+ - Tenjin Inc
8
8
  - Robin Miller
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-10-02 00:00:00.000000000 Z
12
+ date: 2023-11-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -17,113 +17,44 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
20
+ version: '2.2'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: simplecov
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: '0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: '0'
42
- - !ruby/object:Gem::Dependency
43
- name: rspec
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: 2.14.1
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: 2.14.1
56
- - !ruby/object:Gem::Dependency
57
- name: cucumber
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - "~>"
61
- - !ruby/object:Gem::Version
62
- version: 1.3.19
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - "~>"
68
- - !ruby/object:Gem::Version
69
- version: 1.3.19
70
- - !ruby/object:Gem::Dependency
71
- name: capybara
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '0'
77
- type: :development
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: '0'
84
- - !ruby/object:Gem::Dependency
85
- name: launchy
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: '0'
91
- type: :development
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- version: '0'
98
- - !ruby/object:Gem::Dependency
99
- name: parallel_tests
100
- requirement: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- version: '0'
105
- type: :development
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - ">="
110
- - !ruby/object:Gem::Version
111
- version: '0'
27
+ version: '2.2'
112
28
  description: Bouncer brings simple authorization to Sinatra.
113
- email: contact@tenjin.ca
29
+ email:
30
+ - contact@tenjin.ca
31
+ - robin@tenjin.ca
114
32
  executables: []
115
33
  extensions: []
116
34
  extra_rdoc_files: []
117
35
  files:
36
+ - ".gitignore"
37
+ - ".rubocop.yml"
38
+ - ".ruby-version"
39
+ - ".simplecov"
40
+ - CODE_OF_CONDUCT.md
118
41
  - Gemfile
42
+ - Gemfile.lock
119
43
  - MIT-LICENSE
120
44
  - README.md
121
- - lib/sinatra/basic_bouncer.rb
45
+ - RELEASE_NOTES.md
46
+ - Rakefile
47
+ - cucumber.yml
122
48
  - lib/sinatra/bouncer.rb
123
- - lib/sinatra/rule.rb
124
- homepage: http://www.tenjin.ca
125
- licenses: []
126
- metadata: {}
49
+ - lib/sinatra/bouncer/basic_bouncer.rb
50
+ - lib/sinatra/bouncer/rule.rb
51
+ - lib/sinatra/bouncer/version.rb
52
+ - sinatra_bouncer.gemspec
53
+ homepage: https://github.com/TenjinInc/Sinatra-Bouncer
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ rubygems_mfa_required: 'true'
127
58
  post_install_message:
128
59
  rdoc_options: []
129
60
  require_paths:
@@ -132,15 +63,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
63
  requirements:
133
64
  - - ">="
134
65
  - !ruby/object:Gem::Version
135
- version: 1.9.3
66
+ version: '3.1'
136
67
  required_rubygems_version: !ruby/object:Gem::Requirement
137
68
  requirements:
138
69
  - - ">="
139
70
  - !ruby/object:Gem::Version
140
71
  version: '0'
141
72
  requirements: []
142
- rubyforge_project:
143
- rubygems_version: 2.4.8
73
+ rubygems_version: 3.4.18
144
74
  signing_key:
145
75
  specification_version: 4
146
76
  summary: Sinatra permissions plugin
@@ -1,69 +0,0 @@
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/rule.rb DELETED
@@ -1,48 +0,0 @@
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