sinatra-bouncer 1.2.0 → 2.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 +5 -5
- data/.gitignore +18 -0
- data/.rubocop.yml +14 -0
- data/.ruby-version +1 -0
- data/.simplecov +11 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +18 -2
- data/Gemfile.lock +92 -0
- data/README.md +212 -52
- data/RELEASE_NOTES.md +153 -0
- data/Rakefile +15 -0
- data/cucumber.yml +1 -0
- data/lib/sinatra/bouncer/basic_bouncer.rb +90 -0
- data/lib/sinatra/bouncer/rule.rb +62 -0
- data/lib/sinatra/bouncer/version.rb +8 -0
- data/lib/sinatra/bouncer.rb +11 -32
- data/sinatra_bouncer.gemspec +31 -0
- metadata +28 -98
- data/lib/sinatra/basic_bouncer.rb +0 -69
- data/lib/sinatra/rule.rb +0 -48
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f07c9c87e55563ac21610aecc8813d32ee3322d291382bb1bd5cca0ce0d55a12
|
|
4
|
+
data.tar.gz: fa55400409722c0f0f4ea83c6ee552654d0d8aee128d484f3a42c2c888e9b46b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
15
|
-
###Step 1: Require/Register Bouncer
|
|
31
|
+
## Features
|
|
16
32
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
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
|
-
|
|
24
|
-
|
|
65
|
+
gem 'sinatra-bouncer'
|
|
66
|
+
```
|
|
25
67
|
|
|
26
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
# ...
|
|
101
|
+
# ... routes and other config
|
|
60
102
|
```
|
|
61
103
|
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
`can_sometimes`
|
|
75
|
-
(
|
|
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
|
-
|
|
194
|
+
# Anyone can access this path over GET
|
|
195
|
+
can get: '/login'
|
|
80
196
|
end
|
|
81
197
|
```
|
|
82
198
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
208
|
+
rules do
|
|
209
|
+
can_sometimes get: '/login' # Anyone can access this path over GET
|
|
91
210
|
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/sinatra/bouncer.rb
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
73
|
-
|
|
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:
|
|
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:
|
|
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: '
|
|
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: '
|
|
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:
|
|
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
|
-
-
|
|
45
|
+
- RELEASE_NOTES.md
|
|
46
|
+
- Rakefile
|
|
47
|
+
- cucumber.yml
|
|
122
48
|
- lib/sinatra/bouncer.rb
|
|
123
|
-
- lib/sinatra/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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
|
-
|
|
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
|