sinatra-bouncer 1.3.0 → 3.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b8592539c3824fa1985a4c02b877bd7d53e6ab2635f4227b173325f16484465
4
- data.tar.gz: 1aac0ef35f8584e83cbafd5843172798f8967ad217235f9048e476cf3cc8945f
3
+ metadata.gz: 849fed52904df15b1cc036685383f197e47aae52bef8283a81decc281fc95660
4
+ data.tar.gz: 3a3ba41d0d3c2ad70b1c4e48b81558213111d86a750717e2b8f44dea9072ca1f
5
5
  SHA512:
6
- metadata.gz: 83ed25a0acff1d864f978dbbd3667d20f53be0bea36fa5db5abd55eeb4353cad53e29ebeb424f1c462e4a8790444f4a5850cad33c579729aab684ff4f0d482aa
7
- data.tar.gz: a3caa0c02709faa60e5512f73f2b4c4f21066f519999e014ffac2545271198d8a98a7a73d430494a185a00e0ea255170e1693274c7fb8bca51b56c6108fcd8a6
6
+ metadata.gz: d63e6a5437ef4d71e7183dce458241a8efaebace1c6f95e77766412f2faf2d5d490107a4e925588509dfb8e73b0c2e3a21064e73aae4cb3f98ae0c8465f19dd5
7
+ data.tar.gz: ca6d627ae51d9ecf6c88ffa21621117adb3af2eee6c83d14846f8ac7a5aa314f9ce4d2d10c30baf192e4a7c435f750bfb5b9bc3c37ed308ee4a5255a8df0eacf
data/.gitignore CHANGED
@@ -4,9 +4,6 @@
4
4
  # or operating system, you probably want to add a global ignore instead:
5
5
  # git config --global core.excludesfile ~/.gitignore_global
6
6
 
7
- # Ignore bundler config
8
- /.bundle
9
-
10
7
  # Ignore all logfiles and tempfiles.
11
8
  *.log
12
9
  tmp/*
@@ -17,7 +14,5 @@ tmp/*
17
14
  # Ignore all gem files.
18
15
  *.gem
19
16
 
20
- core/config.yml
21
- persist/database.yml
22
-
23
- Gemfile.lock
17
+ # Ignore simplecov results
18
+ .coverage/
data/.rubocop.yml CHANGED
@@ -3,6 +3,7 @@ inherit_from: ~/.config/rubocop/config.yml
3
3
  AllCops:
4
4
  Exclude:
5
5
  - 'bin/*'
6
+ - 'benchmarks/*'
6
7
 
7
8
  Layout/LineLength:
8
9
  Exclude:
@@ -11,12 +12,3 @@ Layout/LineLength:
11
12
  # setting to 6 to match RubyMine autoformat
12
13
  Layout/FirstArrayElementIndentation:
13
14
  IndentationWidth: 6
14
-
15
- # rspec blocks are huge by design
16
- Metrics/BlockLength:
17
- Exclude:
18
- - 'tests/spec/**/*.rb'
19
-
20
- Metrics/ModuleLength:
21
- Exclude:
22
- - 'tests/spec/**/*.rb'
@@ -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
@@ -7,13 +7,14 @@ gemspec
7
7
 
8
8
  group :development do
9
9
  gem 'bundler', '~> 2.4'
10
- gem 'rake', '~> 13.0'
10
+ gem 'rake', '~> 13.1'
11
+ gem 'rubocop', '~> 1.57'
12
+ gem 'rubocop-performance', '~> 1.19'
11
13
  gem 'yard', '~> 0.9'
12
14
  end
13
15
 
14
16
  group :test do
15
- gem 'capybara', '~> 3.39'
16
- gem 'cucumber', '~> 8.0'
17
+ gem 'rack-test', '~> 2.1'
17
18
  gem 'rspec', '~> 3.12'
18
19
  gem 'simplecov', '~> 0.22'
19
20
  end
data/Gemfile.lock ADDED
@@ -0,0 +1,92 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sinatra-bouncer (3.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,285 @@
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's syntax looks like:
3
9
 
4
- **Gemfile**
5
10
  ```ruby
6
- gem 'sinatra-bouncer'
11
+ # You can define roles to collect permissions together
12
+ bouncer.role :admins do
13
+ current_user&.admin?
14
+ end
15
+
16
+ bouncer.rules do
17
+ # Routes match based on one or more strings.
18
+ anyone.can get: '/',
19
+ post: ['/user/sign-in',
20
+ '/user/sign-out']
21
+
22
+ # Wildcards match anything directly under that path
23
+ anyone.can get: '/lib/js/*'
24
+
25
+ admins.can get: '/admin/*',
26
+ post: '/admin/actions/*'
27
+
28
+ # ... etc ...
29
+ end
7
30
  ```
8
31
 
9
- **Terminal**
10
- ```sh
11
- gem install sinatra-bouncer
32
+ ### Features
33
+
34
+ Here's what this Gem provides:
35
+
36
+ * **Block-by-default**
37
+ * Any route must be explicitly allowed
38
+ * **Role-Oriented**
39
+ * The [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) is constructed to be easily readable
40
+ * **Declarative Syntax**
41
+ * Straightforward syntax reduces complexity of layered permissions
42
+ * Keeps permissions together for clarity
43
+ * **Conditional Logic Via Blocks**
44
+ * Often additional checks must be performed to know if a route is allowed.
45
+ * **Grouping**
46
+ * Routes can be matched by wildcard
47
+ * **Forced Boolean Affirmation**
48
+ * Condition blocks must explicitly return `true`, avoiding accidental truthy values
49
+
50
+ ### Anti-Features
51
+
52
+ Bouncer intentionally does not support some concepts.
53
+
54
+ * **No User Identification** *(aka Authentication)*
55
+ * Bouncer is not a user identification gem. It assumes you already have an existing solution
56
+ like [Warden](https://github.com/wardencommunity/warden). Knowing *who* someone is is already a complex enough
57
+ problem and should be separate from what they may do.
58
+ * **No Rails Integration**
59
+ * Bouncer is not intended to work with Rails. Use one of the Rails-based permissions gems for these situations.
60
+ * **No Negation**
61
+ * There is intentionally no negation (eg. `cannot`) to preserve the default-deny frame of mind
62
+
63
+ ## Installation
64
+
65
+ Add this to your gemfile:
66
+
67
+ ```ruby
68
+ gem 'sinatra-bouncer'
12
69
  ```
13
70
 
14
- ##Quickstart
15
- ###Step 1: Require/Register Bouncer
71
+ Then run:
16
72
 
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`
73
+ ```shell
74
+ bundle install
75
+ ```
76
+
77
+ **Sinatra Modular Style**
20
78
 
21
- **Sinatra Classic**
22
79
  ```ruby
23
- require 'sinatra'
80
+ require 'sinatra/base'
24
81
  require 'sinatra/bouncer'
25
82
 
83
+ class MyApp < Sinatra::Base
84
+ register Sinatra::Bouncer
85
+
86
+ bouncer.rules do
87
+ # ... can statements ...
88
+ end
89
+
26
90
  # ... routes and other config
91
+ end
27
92
  ```
28
93
 
29
- **Modular**
94
+ **Sinatra Classic Style**
95
+
30
96
  ```ruby
31
- require 'sinatra/base'
97
+ require 'sinatra'
32
98
  require 'sinatra/bouncer'
33
99
 
34
- class MyApp < Sinatra::Base
35
- register Sinatra::Bouncer
36
-
37
- # ... routes and other config
100
+ bouncer.rules do
101
+ # ... can statements ...
38
102
  end
103
+
104
+ # ... routes and other config
39
105
  ```
40
106
 
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`.
107
+ ## Usage
108
+
109
+ Define roles using the `role` method and provide each one with a condition block. Then call `rules` with a block that
110
+ uses your defined roles with the `#can` or `#can_sometimes` DSL methods to declare which
111
+ paths are allowed.
112
+
113
+ The rules block is run once as part of the configuration phase but the condition blocks are evaluated in the context of
114
+ the request handler. This means they have access to Sinatra helpers, the `request` object, and `params`.
44
115
 
45
116
  ```ruby
46
117
  require 'sinatra'
47
118
  require 'sinatra/bouncer'
48
119
 
49
- 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
120
+ bouncer.role :members do
121
+ !current_user.nil?
122
+ end
123
+
124
+ bouncer.role :bots do
125
+ !request.get_header('X-CUSTOM-BOT').nil?
126
+ end
127
+
128
+ bouncer.rules do
129
+ # example: always allow GET requests to '/' and '/about'; and POST requests to '/sign-in'
130
+ anyone.can get: ['/', '/about'],
131
+ post: '/sign-in'
132
+
133
+ # example: logged in users can view (GET) member restricted paths and edit their account (POST)
134
+ members.can get: '/members/*'
135
+ members.can_sometimes post: '/members/edit-account' do
136
+ current_user && current_user.id == params[:id]
137
+ end
138
+
139
+ # example: require presence of arbitrary request header in the role condition
140
+ bots.can get: '/bots/*'
57
141
  end
58
142
 
59
- # ... route declarations as normal below
143
+ # ... Sinatra route declarations as normal ...
60
144
  ```
61
145
 
62
- ## API
63
- #### can
64
- Any route declared with #can will be accepted without further challenge.
146
+ ### Role Declarations
147
+
148
+ Roles are declared using the `#role` method in your Sinatra config. Each one must be provided a condition block that
149
+ must return exactly `true` when the role applies.
65
150
 
66
151
  ```ruby
67
- rules do
68
- can(:post, '/user_posts_blog')
152
+ # let's pretend that current_user is a helper that returns the user from Warden
153
+ bouncer.role :admins do
154
+ current_user&.admin?
69
155
  end
70
156
  ```
71
157
 
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.
158
+ > **Note:** There is a default role called `anyone` that is always declared for you.
159
+
160
+ ### HTTP Method and Route Matching
161
+
162
+ Both `#can` and `#can_sometimes` accept symbol
163
+ [HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) as keys
164
+ and each key is paired with one or more path strings.
165
+
166
+ ```ruby
167
+ # example: single method, single route
168
+ anyone.can get: '/'
169
+
170
+ # example: multiple methods, single route each
171
+ anyone.can get: '/',
172
+ post: '/blog/action/save'
173
+
174
+ # example: multiple methods, multiple routes (using string array syntax)
175
+ anyone.can get: %w[/
176
+ /sign-in
177
+ /blog/editor],
178
+ post: %w[/blog/action/save
179
+ /blog/action/delete]
180
+ ```
181
+
182
+ > **Note** Allowing GET implies allowing HEAD, since HEAD is by spec a GET without a response body. The reverse is not
183
+ > true, however; allowing HEAD will not also allow GET.
184
+
185
+ #### Wildcards and Special Symbols
186
+
187
+ Provide a wildcard `*` to match any string excluding slash. There is intentionally no syntax for matching wildcards
188
+ recursively, so nested paths will also need to be declared.
189
+
190
+ ```ruby
191
+ # example: match anything directly under the /members/ path
192
+ members.can get: '/members/*'
193
+ ```
194
+
195
+ There is also a special symbol, `:all` that matches all paths. It is intended for rare use
196
+ with superadmin-type accounts.
197
+
198
+ ```ruby
199
+ # this allows GET on all paths to those in the admin group
200
+ admins.can get: :all
201
+ ```
202
+
203
+ > **Warning** Always be cautious when using wildcards and special symbols to avoid accidentally opening up pathways that
204
+ > should remain private.
205
+
206
+ ### Always Allow: `can`
207
+
208
+ Any route declared with `#can` will be accepted without further challenge.
76
209
 
77
210
  ```ruby
78
- rules do
79
- can_sometimes('/login') # Anyone can access this path
211
+ bouncer.rules do
212
+ # Anyone can access this path over GET
213
+ anyone.can get: '/login'
80
214
  end
81
215
  ```
82
216
 
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.
217
+ ### Conditionally Allow: `can_sometimes`
218
+
219
+ `can_sometimes` takes a condition block that will be run once the path is attempted. This block **must return an
220
+ explicit boolean** (ie. `true` or `false`) to avoid any accidental truthy values creating unwanted access.
87
221
 
88
222
  ```ruby
89
- # this allows get on all paths
90
- can(:get, :all)
223
+ bouncer.role :users do
224
+ !current_user.nil?
225
+ end
91
226
 
92
- # this allows any method type to run on the /login path
93
- can(:any, '/login')
227
+ bouncer.rules do
228
+ users.can_sometimes post: '/user/save' do
229
+ current_user.id == params[:id]
230
+ end
231
+ end
94
232
  ```
95
233
 
96
234
  ### 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.
235
+
236
+ The default bounce action is to `halt 403`. Call `bounce_with` with a block to specify your own behaviour. The block is
237
+ also run in a Sinatra request handler context, so you can use helpers here as well.
98
238
 
99
239
  ```ruby
100
240
  require 'sinatra'
101
241
  require 'sinatra/bouncer'
102
242
 
103
- bounce_with do
104
- redirect '/login'
243
+ bounce_with do
244
+ redirect '/login'
105
245
  end
106
246
 
107
247
  # bouncer rules, routes, etc...
108
248
  ```
249
+
250
+ ## Alternatives
251
+
252
+ The syntax for Bouncer is largely influenced by the now-deprecated [CanCan](https://github.com/ryanb/cancan) gem.
253
+
254
+ ## Contributing
255
+
256
+ Bug reports and pull requests are welcome on GitHub at https://github.com/TenjinInc/Sinatra-Bouncer.
257
+
258
+ Valued topics:
259
+
260
+ * Error messages (clarity, hinting)
261
+ * Documentation
262
+ * API
263
+ * Security correctness
264
+
265
+ This project is intended to be a friendly space for collaboration, and contributors are expected to adhere to the
266
+ [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct. Play nice.
267
+
268
+ ### Core Developers
269
+
270
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests. You
271
+ can also run `bin/console` for an interactive prompt that will allow you to experiment.
272
+
273
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
274
+ version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
275
+ push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
276
+
277
+ Documentation is produced by Yard. Run `bundle exec rake yard`. The goal is to have 100% documentation coverage and 100%
278
+ test coverage.
279
+
280
+ Release notes are provided in `RELEASE_NOTES.md`, and should vaguely
281
+ follow [Keep A Changelog](https://keepachangelog.com/en/1.0.0/) recommendations.
282
+
283
+ ## License
284
+
285
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/license/mit/).