padlock_auth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +275 -0
- data/Rakefile +18 -0
- data/config/locales/padlock_auth.en.yml +14 -0
- data/lib/padlock_auth/abstract_access_token.rb +77 -0
- data/lib/padlock_auth/abstract_strategy.rb +52 -0
- data/lib/padlock_auth/config/option.rb +110 -0
- data/lib/padlock_auth/config/scopes.rb +124 -0
- data/lib/padlock_auth/config.rb +211 -0
- data/lib/padlock_auth/errors.rb +36 -0
- data/lib/padlock_auth/http/error_response.rb +75 -0
- data/lib/padlock_auth/http/forbidden_token_response.rb +67 -0
- data/lib/padlock_auth/http/invalid_token_response.rb +70 -0
- data/lib/padlock_auth/mixins/build_with.rb +49 -0
- data/lib/padlock_auth/mixins/hide_attribute.rb +31 -0
- data/lib/padlock_auth/rails/helpers.rb +142 -0
- data/lib/padlock_auth/rails/token_factory.rb +95 -0
- data/lib/padlock_auth/railtie.rb +22 -0
- data/lib/padlock_auth/rspec_support.rb +46 -0
- data/lib/padlock_auth/token/access_token.rb +55 -0
- data/lib/padlock_auth/token/strategy.rb +80 -0
- data/lib/padlock_auth/utils/abstract_builder.rb +55 -0
- data/lib/padlock_auth/version.rb +4 -0
- data/lib/padlock_auth.rb +76 -0
- data/lib/tasks/padlock_auth_tasks.rake +4 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 295f953fb3b0a6a821cca2f2f18ac16a773d140612d337442bed3f5108400777
|
4
|
+
data.tar.gz: c50bbeb53da5386be9305875c842beae00583af59a5fe488dd271c057eaee943
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e1d4cf307d4d63bf7a44026b209fc26c4cfe839476560da5cab65224285da2f3966a68073a38b7fbab907d878f5725ea1c254a08477fcdc80f8272e0a77756b
|
7
|
+
data.tar.gz: 2689fe7dd1d69bff8933967ee33523235041aa34b5ec84714ec87f26ac6dc3ff22886ebf6c92eea34c5e3305831ca3192800415d7717173e9368b46e28b8a4f4
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Ben Morrall
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
# PadlockAuth
|
2
|
+
|
3
|
+
PadlockAuth allows you to secure your Rails application using access tokens provided by an external provider.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
PadlockAuth separates the __how__ of token verification from the __where__ authentication occurs. You configure an authentication strategy in an initializer and use callbacks in controllers to secure endpoints, allowing strategy changes without modifying your controllers.
|
8
|
+
|
9
|
+
Designed for lightweight use, PadlockAuth is ideal for microservices or high-volume APIs, with support ranging from simple token matching to more complex JWT-based authentication. Unlike [Devise](https://github.com/heartcombo/devise) or [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), PadlockAuth doesn't require a database, making it more suitable for microservices and lightweight scenarios.
|
10
|
+
|
11
|
+
### Configuring a Basic Token Strategy
|
12
|
+
|
13
|
+
The Basic Token Strategy is a simple authentication mechanism where the token received in the request is compared with a pre-configured secret key. This strategy is ideal for straightforward use cases where you only need to validate the presence of a valid token. It does not provide any scopes to be authenticated against.
|
14
|
+
|
15
|
+
#### Example Configuration
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# config/initializers/padlock_auth.rb
|
19
|
+
PadlockAuth.configure do
|
20
|
+
secure_with :token do
|
21
|
+
secret_key "MySecretKey"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
In this example:
|
27
|
+
|
28
|
+
- The `:token` strategy validates the request's token by comparing it with the configured `secret_key`.
|
29
|
+
|
30
|
+
### Configuring a Custom Strategy
|
31
|
+
|
32
|
+
A Custom Strategy in PadlockAuth allows you to implement your own authentication logic. This requires creating a custom Strategy class that generates an Access Token class.
|
33
|
+
|
34
|
+
#### 1. Define the Strategy Class
|
35
|
+
|
36
|
+
The Strategy class should inherit from `PadlockAuth::AbstractStrategy` and implement the following methods:
|
37
|
+
|
38
|
+
- `build_access_token`: Builds an access token from a provided String access token.
|
39
|
+
- `build_access_token_from_credentials`: Builds an access token from provided username and password.
|
40
|
+
|
41
|
+
Both methods should return an `AccessToken` object.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class MyCustomStrategy < PadlockAuth::AbstractStrategy
|
45
|
+
def build_access_token(token_string)
|
46
|
+
# Logic to build an access token from a string token
|
47
|
+
MyAccessToken.new(token_string)
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_access_token_from_credentials(username, password)
|
51
|
+
# Logic to build an access token from provided credentials
|
52
|
+
MyAccessToken.new(generate_token_from_credentials(username, password))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
#### 2. Define the AccessToken Class
|
58
|
+
|
59
|
+
The `AccessToken` class should inherit from `PadlockAuth::AbstractAccessToken` and implement the following methods:
|
60
|
+
|
61
|
+
- `accessible?`: Returns `true` if the access token is valid. This means the token:
|
62
|
+
- Matches the expected token value, and
|
63
|
+
- Contains any required attributes, and
|
64
|
+
- Has not expired.
|
65
|
+
- `includes_scope?`: Returns `true` if the access token matches at least one of the provided scope values.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class MyAccessToken < PadlockAuth::AbstractAccessToken
|
69
|
+
def accessible?
|
70
|
+
# Check token validity logic
|
71
|
+
valid_token? && required_attributes_present? && not_expired?
|
72
|
+
end
|
73
|
+
|
74
|
+
def includes_scope?(scopes)
|
75
|
+
# Check if the token includes at least one of the provided scopes
|
76
|
+
(scopes & token_scopes).any?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
#### 3. Configure the Custom Strategy
|
82
|
+
|
83
|
+
Finally, configure PadlockAuth to use your custom strategy within the initializer:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
# config/initializers/padlock_auth.rb
|
87
|
+
PadlockAuth.configure do
|
88
|
+
secure_with MyCustomStrategy.new
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
This configuration allows you to implement a fully custom authentication strategy that integrates with PadlockAuth.
|
93
|
+
|
94
|
+
### Securing a Rails Controller
|
95
|
+
|
96
|
+
The `padlock_authorize!` method secures your API endpoints and optionally enforces scope requirements. The verification of scopes is managed by the configured authentication strategy.
|
97
|
+
|
98
|
+
#### Example: Specifying Scopes
|
99
|
+
|
100
|
+
You can specify multiple scopes in a single call:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
before_action { padlock_authorize! :read, :write }
|
104
|
+
```
|
105
|
+
|
106
|
+
In this example:
|
107
|
+
|
108
|
+
- The action requires the access token to include either the :read **or** :write scope.
|
109
|
+
|
110
|
+
Alternatively, you can require multiple scopes by calling padlock_authorize! separately for each:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
before_action :require_read_and_write_scopes
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def require_read_and_write_scopes
|
118
|
+
padlock_authorize! :read
|
119
|
+
padlock_authorize! :write
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
In this case:
|
124
|
+
|
125
|
+
The action requires the access token to include **both** the :read and :write scopes.
|
126
|
+
|
127
|
+
#### Example: Using Default Scopes
|
128
|
+
|
129
|
+
When no scopes are provided to `padlock_authorize!`, the default_scopes configuration will be applied. You can configure the default_scopes value during setup:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
PadlockAuth.configure do
|
133
|
+
secure_with MyCustomStrategy
|
134
|
+
default_scopes [:read] # Optional, defines the default required scopes
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
In this case:
|
139
|
+
|
140
|
+
If `padlock_authorize!` is called without explicit scopes, the `:read` scope will be enforced by default.
|
141
|
+
|
142
|
+
For example:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
before_action :padlock_authorize!
|
146
|
+
```
|
147
|
+
|
148
|
+
- If the token includes the `:read` scope, the action will proceed.
|
149
|
+
- If `default_scopes` is not set, no scopes are enforced by default when padlock_authorize! is called without scopes..
|
150
|
+
|
151
|
+
### Providing Access Token Credentials
|
152
|
+
|
153
|
+
You can configure PadlockAuth to support different ways of extracting a single access token by specifying an array of access_token_methods:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
PadlockAuth.configure do
|
157
|
+
access_token_methods [
|
158
|
+
:from_bearer_authorization, # Extracts token from Authorization header with Bearer token
|
159
|
+
:from_access_token_param, # Extracts token from access_token param
|
160
|
+
:from_bearer_param # Extracts token from bearer param
|
161
|
+
]
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
#### Token Extraction Methods
|
166
|
+
|
167
|
+
- `from_bearer_authorization`: Extracts the token from an `Authorization` header with a Bearer token (i.e. Bearer VALID_ACCESS_TOKEN).
|
168
|
+
- `from_access_token_param`: Extracts the token from an `access_token` parameter in the query string or form data.
|
169
|
+
- `from_bearer_param`: Extracts the token from a `bearer` parameter in the query string or form data.
|
170
|
+
|
171
|
+
These methods will call `build_access_token` with the provided strategy to create an AccessToken object.
|
172
|
+
|
173
|
+
#### Example: Calling an Endpoint with a Bearer Token
|
174
|
+
|
175
|
+
Here’s an example of how to call an endpoint with an access token using the `from_bearer_authorization` method. The token will be extracted from the Authorization header:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
require 'net/http'
|
179
|
+
require 'uri'
|
180
|
+
|
181
|
+
uri = URI.parse("http://example.com/api/endpoint")
|
182
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
183
|
+
|
184
|
+
request = Net::HTTP::Get.new(uri)
|
185
|
+
request["Authorization"] = "Bearer VALID_ACCESS_TOKEN"
|
186
|
+
|
187
|
+
response = http.request(request)
|
188
|
+
puts response.body
|
189
|
+
```
|
190
|
+
|
191
|
+
In this example:
|
192
|
+
|
193
|
+
- The Bearer token `VALID_ACCESS_TOKEN` is passed in the Authorization header.
|
194
|
+
- PadlockAuth will extract the token using the `from_bearer_authorization` method and validate it
|
195
|
+
|
196
|
+
#### Example: Calling an Endpoint with a Token Parameter
|
197
|
+
|
198
|
+
You can also pass the token as a query parameter. Here’s an example of how to call the same endpoint with the token passed in the `access_token` parameter:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
uri = URI.parse("http://example.com/api/endpoint?access_token=VALID_ACCESS_TOKEN")
|
202
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
203
|
+
|
204
|
+
request = Net::HTTP::Get.new(uri)
|
205
|
+
response = http.request(request)
|
206
|
+
puts response.body
|
207
|
+
```
|
208
|
+
|
209
|
+
PadlockAuth will extract the token from the `access_token` parameter and validate it.
|
210
|
+
|
211
|
+
### Providing Credentials with Username and Password
|
212
|
+
|
213
|
+
You can also provide username and password credentials by adding the `from_basic_authorization` method:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
PadlockAuth.configure do
|
217
|
+
access_token_methods [
|
218
|
+
:from_basic_authorization # Extracts token from a HTTP BASIC AUTHORIZATION header
|
219
|
+
]
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
- `from_basic_authorization`: Extracts the Username and Password from a Basic Authorization header.
|
224
|
+
|
225
|
+
#### Example: Calling an Endpoint with Basic Authentication
|
226
|
+
|
227
|
+
If you need to authenticate with a username and password, you can send the credentials in the `Authorization` header using Basic Authentication:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
uri = URI.parse("http://example.com/api/endpoint")
|
231
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
232
|
+
|
233
|
+
request = Net::HTTP::Get.new(uri)
|
234
|
+
request.basic_auth 'username', 'secret'
|
235
|
+
|
236
|
+
response = http.request(request)
|
237
|
+
puts response.body
|
238
|
+
```
|
239
|
+
|
240
|
+
PadlockAuth will extract the username and password using the `from_basic_authorization` method and use them to generate an access token.
|
241
|
+
|
242
|
+
## Installation
|
243
|
+
|
244
|
+
Add this line to your application's Gemfile:
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
gem "padlock_auth"
|
248
|
+
```
|
249
|
+
|
250
|
+
And then execute:
|
251
|
+
```bash
|
252
|
+
$ bundle
|
253
|
+
```
|
254
|
+
|
255
|
+
Or install it yourself as:
|
256
|
+
```bash
|
257
|
+
$ gem install padlock_auth
|
258
|
+
```
|
259
|
+
|
260
|
+
## Development
|
261
|
+
|
262
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `rake` to run the tests and code quality checks.
|
263
|
+
|
264
|
+
Generate documentaion using `rake yard`, which can be found in the `/doc` directory.
|
265
|
+
|
266
|
+
## Contributing
|
267
|
+
|
268
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/bmorrall/padlock_auth. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/bmorrall/padlock_auth/blob/main/CODE_OF_CONDUCT.md).
|
269
|
+
|
270
|
+
## License
|
271
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
272
|
+
|
273
|
+
## Code of Conduct
|
274
|
+
|
275
|
+
Everyone interacting in the PadlockAuth project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bmorrall/padlock_auth/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
require "yard"
|
6
|
+
YARD::Rake::YardocTask.new do |t|
|
7
|
+
t.files = ["lib/**/*.rb"]
|
8
|
+
end
|
9
|
+
|
10
|
+
require "rspec/core/rake_task"
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
|
13
|
+
require "standard/rake"
|
14
|
+
|
15
|
+
task default: %i[
|
16
|
+
spec
|
17
|
+
standard
|
18
|
+
]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
en:
|
2
|
+
padlock_auth:
|
3
|
+
errors:
|
4
|
+
messages:
|
5
|
+
server_error: "An error occurred while processing your request."
|
6
|
+
|
7
|
+
invalid_token:
|
8
|
+
revoked: "The access token was revoked."
|
9
|
+
expired: "The access token has expired."
|
10
|
+
unknown: "The access token is invalid."
|
11
|
+
|
12
|
+
forbidden_token:
|
13
|
+
missing_scope: 'Access to this resource requires scope "%{oauth_scopes}".'
|
14
|
+
unknown: "The access token is forbidden."
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PadlockAuth
|
4
|
+
# @abstract
|
5
|
+
#
|
6
|
+
# AbstractAccessToken is a base class for all access token classes.
|
7
|
+
#
|
8
|
+
# It provides all methods that are required for an access token to be
|
9
|
+
# compatible with PadlockAuth.
|
10
|
+
#
|
11
|
+
# All implemented methods will default to returning false or nil, so that
|
12
|
+
# any authentication/authorisation attempt will fail unless the methods are implemented.
|
13
|
+
class AbstractAccessToken
|
14
|
+
# Indicates if token is acceptable for specific scopes.
|
15
|
+
#
|
16
|
+
# @param scopes [Array<String>] scopes
|
17
|
+
#
|
18
|
+
# @return [Boolean] true if record is accessible and includes scopes, or false in other cases
|
19
|
+
#
|
20
|
+
def acceptable?(scopes)
|
21
|
+
accessible? && includes_scope?(scopes)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Indicates the access token matches the specific criteria of the strategy to
|
25
|
+
# be considered a valid access token.
|
26
|
+
#
|
27
|
+
# Tokens failing to be accessible will be rejected as an invalid grant request,
|
28
|
+
# with a 401 Unauthorized response.
|
29
|
+
#
|
30
|
+
# @abstract Implement this method in your access token class
|
31
|
+
#
|
32
|
+
# @return [Boolean] true if the token is accessible, false otherwise
|
33
|
+
#
|
34
|
+
def accessible?
|
35
|
+
Kernel.warn "[PADLOCK_AUTH] #accessible? not implemented for #{self.class}"
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
# Provides a lookup key for the reason the token is invalid.
|
40
|
+
#
|
41
|
+
# Messages will use the i18n scope `padlock_auth.errors.messages.invalid_token`,
|
42
|
+
# with the default key of :unknown, providing a generic error message.
|
43
|
+
#
|
44
|
+
# @return [Symbol] the reason the token is invalid
|
45
|
+
#
|
46
|
+
def invalid_token_reason
|
47
|
+
:unknown
|
48
|
+
end
|
49
|
+
|
50
|
+
# Indicates if the token includes the required scopes/audience.
|
51
|
+
#
|
52
|
+
# Tokens failing to include the required scopes will be rejected as an invalid scope request,
|
53
|
+
# with a 403 Forbidden response.
|
54
|
+
#
|
55
|
+
# @abstract Implement this method in your access token class
|
56
|
+
#
|
57
|
+
# @param _required_scopes [Boolean] true if the token includes the required scopes, false otherwise
|
58
|
+
#
|
59
|
+
def includes_scope?(_required_scopes)
|
60
|
+
Kernel.warn "[PADLOCK_AUTH] #includes_scope? not implemented for #{self.class}"
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
# Provides a lookup key for the reason the token is forbidden.
|
65
|
+
#
|
66
|
+
# Messages will use the i18n scope `padlock_auth.errors.messages.forbidden_token`,
|
67
|
+
# with the default key of :missing_scope, providing a generic error message.
|
68
|
+
#
|
69
|
+
# The required scopes are passed as an argument to the i18n for some user feedback as required.
|
70
|
+
#
|
71
|
+
# @return [Symbol] the reason the token is forbidden
|
72
|
+
#
|
73
|
+
def forbidden_token_reason
|
74
|
+
:unknown
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module PadlockAuth
|
2
|
+
# @abstract
|
3
|
+
#
|
4
|
+
# Abstract strategy for building and authenticating access tokens.
|
5
|
+
#
|
6
|
+
# Strategies are responsible for building access tokens and authenticating them.
|
7
|
+
class AbstractStrategy
|
8
|
+
# Build an access token from a raw token.
|
9
|
+
#
|
10
|
+
# @param _raw_token [String] The raw token
|
11
|
+
#
|
12
|
+
# @return [PadlockAuth::AbstractAccessToken, nil] The access token
|
13
|
+
def build_access_token(_raw_token)
|
14
|
+
Kernel.warn "[PADLOCK_AUTH] #build_access_token not implemented for #{self.class}"
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Build an access token from credentials.
|
19
|
+
#
|
20
|
+
# @param _username [String] The username
|
21
|
+
# @param _password [String] The password
|
22
|
+
#
|
23
|
+
# @return [PadlockAuth::AbstractAccessToken, nil] The access token
|
24
|
+
def build_access_token_from_credentials(_username, _password)
|
25
|
+
Kernel.warn "[PADLOCK_AUTH] #build_access_token_from_credentials not implemented for #{self.class}"
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Build an invalid token response.
|
30
|
+
#
|
31
|
+
# Used to indicate that a token is invalid.
|
32
|
+
#
|
33
|
+
# @param access_token [PadlockAuth::AbstractAccessToken] The access token
|
34
|
+
#
|
35
|
+
# @return [PadlockAuth::Http::InvalidTokenResponse] The response
|
36
|
+
def build_invalid_token_response(access_token)
|
37
|
+
Http::InvalidTokenResponse.from_access_token(access_token)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Build a forbidden token response.
|
41
|
+
#
|
42
|
+
# Used to indicate that a token does not have the required scopes.
|
43
|
+
#
|
44
|
+
# @param access_token [PadlockAuth::AbstractAccessToken] The access token
|
45
|
+
# @param scopes [PadlockAuth::Config::Scopes] The required scopes
|
46
|
+
#
|
47
|
+
# @return [PadlockAuth::Http::ForbiddenTokenResponse] The response
|
48
|
+
def build_forbidden_token_response(access_token, scopes)
|
49
|
+
Http::ForbiddenTokenResponse.from_access_token(access_token, scopes)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PadlockAuth
|
4
|
+
class Config
|
5
|
+
##
|
6
|
+
# PadlockAuth configuration option DSL.
|
7
|
+
#
|
8
|
+
# Adds configuration methods to a builder class which will be used to configure the object.
|
9
|
+
#
|
10
|
+
# Adds accessor methods to the object being configured.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# class MyConfig
|
14
|
+
# class Builder < PadlockAuth::Config::AbstractBuilder; end
|
15
|
+
#
|
16
|
+
# mattr_reader(:builder_class) { Builder }
|
17
|
+
#
|
18
|
+
# extend PadlockAuth::Config::Option
|
19
|
+
#
|
20
|
+
# option :name
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# config = MyConfig.builder_class.build do
|
24
|
+
# name 'My Name'
|
25
|
+
# end
|
26
|
+
# config.name # => 'My Name'
|
27
|
+
#
|
28
|
+
module Option
|
29
|
+
# Defines configuration option
|
30
|
+
#
|
31
|
+
# When you call option, it defines two methods. One method will take place
|
32
|
+
# in the +Config+ class and the other method will take place in the
|
33
|
+
# +Builder+ class.
|
34
|
+
#
|
35
|
+
# The +name+ parameter will set both builder method and config attribute.
|
36
|
+
# If the +:as+ option is defined, the builder method will be the specified
|
37
|
+
# option while the config attribute will be the +name+ parameter.
|
38
|
+
#
|
39
|
+
# If you want to introduce another level of config DSL you can
|
40
|
+
# define +builder_class+ parameter.
|
41
|
+
# Builder should take a block as the initializer parameter and respond to function +build+
|
42
|
+
# that returns the value of the config attribute.
|
43
|
+
#
|
44
|
+
# @param name [Symbol] The name of the configuration option
|
45
|
+
# @param options [Hash] The options hash which can contain:
|
46
|
+
# - as [String] Set the builder method that goes inside +configure+ block
|
47
|
+
# - default [Object] The default value in case no option was set
|
48
|
+
# - builder_class [Class] Configuration option builder class
|
49
|
+
#
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# option :name
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# option :name, as: :set_name
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# option :name, default: 'My Name'
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# option :scopes, builder_class: ScopesBuilder
|
62
|
+
#
|
63
|
+
#
|
64
|
+
def option(name, options = {})
|
65
|
+
attribute = options[:as] || name
|
66
|
+
|
67
|
+
builder_class.instance_eval do
|
68
|
+
if method_defined?(name)
|
69
|
+
Kernel.warn "[PADLOCK_AUTH] Option #{self.name}##{name} already defined and will be overridden"
|
70
|
+
remove_method name
|
71
|
+
end
|
72
|
+
|
73
|
+
define_method name do |*args, &block|
|
74
|
+
if (deprecation_opts = options[:deprecated])
|
75
|
+
warning = "[PADLOCK_AUTH] #{self.class.name}##{name} has been deprecated and will soon be removed"
|
76
|
+
warning = "#{warning}\n#{deprecation_opts.fetch(:message)}" if deprecation_opts.is_a?(Hash)
|
77
|
+
|
78
|
+
Kernel.warn(warning)
|
79
|
+
end
|
80
|
+
|
81
|
+
value = block || args.first
|
82
|
+
|
83
|
+
config.instance_variable_set(:"@#{attribute}", value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
define_method attribute do |*_args|
|
88
|
+
if instance_variable_defined?(:"@#{attribute}")
|
89
|
+
instance_variable_get(:"@#{attribute}")
|
90
|
+
else
|
91
|
+
options[:default]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
public attribute
|
96
|
+
end
|
97
|
+
|
98
|
+
# Uses extended hook to ensure builder_class is defined.
|
99
|
+
#
|
100
|
+
# Implementing classes should define a +builder_class+ method that returns a builder class.
|
101
|
+
#
|
102
|
+
def self.extended(base)
|
103
|
+
return if base.respond_to?(:builder_class)
|
104
|
+
|
105
|
+
raise NotImplementedError,
|
106
|
+
"Define `self.builder_class` method for #{base} that returns your custom Builder class to use options DSL!"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PadlockAuth
|
4
|
+
class Config
|
5
|
+
# Represents a collection of scopes.
|
6
|
+
#
|
7
|
+
class Scopes
|
8
|
+
include Enumerable
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
# Create a new Scopes instance from a string.
|
12
|
+
#
|
13
|
+
# @param string [String] A space-separated string of scopes
|
14
|
+
#
|
15
|
+
# @return [PadlockAuth::Config::Scopes] A new Scopes instance
|
16
|
+
def self.from_string(string)
|
17
|
+
string ||= ""
|
18
|
+
new.tap do |scope|
|
19
|
+
scope.add(*string.split(/\s+/))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Create a new Scopes instance from an array.
|
24
|
+
#
|
25
|
+
# @param array [Array<String>] An array of scopes
|
26
|
+
#
|
27
|
+
# @return [PadlockAuth::Config::Scopes] A new Scopes instance
|
28
|
+
#
|
29
|
+
def self.from_array(*array)
|
30
|
+
new.tap do |scope|
|
31
|
+
scope.add(*array)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
delegate :each, :empty?, to: :@scopes
|
36
|
+
|
37
|
+
# Initialize a new Scopes instance.
|
38
|
+
def initialize
|
39
|
+
@scopes = []
|
40
|
+
end
|
41
|
+
|
42
|
+
# Check if a scope exists in the collection.
|
43
|
+
#
|
44
|
+
# @param scope [String] The scope to check
|
45
|
+
#
|
46
|
+
# @return [Boolean] True if the scope exists
|
47
|
+
#
|
48
|
+
def exists?(scope)
|
49
|
+
@scopes.include? scope.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add a scope to the collection.
|
53
|
+
#
|
54
|
+
# @param scopes [Array<String>] The scopes to add
|
55
|
+
#
|
56
|
+
def add(*scopes)
|
57
|
+
@scopes.push(*scopes.flatten.compact.map(&:to_s))
|
58
|
+
@scopes.uniq!
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns all scopes in the collection.
|
62
|
+
#
|
63
|
+
# @return [Array<String>] All scopes
|
64
|
+
def all
|
65
|
+
@scopes
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns all scopes as a string.
|
69
|
+
#
|
70
|
+
# @return [String] All scopes as a space-joined string
|
71
|
+
def to_s
|
72
|
+
@scopes.join(" ")
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns true if all scopes exist in the collection.
|
76
|
+
#
|
77
|
+
# @param scopes [Array<String>] The scopes to check
|
78
|
+
#
|
79
|
+
# @return [Boolean] True if all scopes exist
|
80
|
+
def scopes?(scopes)
|
81
|
+
scopes.all? { |scope| exists?(scope) }
|
82
|
+
end
|
83
|
+
|
84
|
+
alias_method :has_scopes?, :scopes?
|
85
|
+
|
86
|
+
# Adds two collections of scopes together.
|
87
|
+
#
|
88
|
+
def +(other)
|
89
|
+
self.class.from_array(all + to_array(other))
|
90
|
+
end
|
91
|
+
|
92
|
+
# Compares two collections of scopes.
|
93
|
+
#
|
94
|
+
# @param other [PadlockAuth::Config::Scopes, Array<String>] The other collection
|
95
|
+
#
|
96
|
+
def <=>(other)
|
97
|
+
if other.respond_to?(:map)
|
98
|
+
map(&:to_s).sort <=> other.map(&:to_s).sort
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns a scopes array with elements contained in both collections.
|
105
|
+
#
|
106
|
+
# @param other [PadlockAuth::Config::Scopes, Array<String>] The other collection
|
107
|
+
#
|
108
|
+
def &(other)
|
109
|
+
self.class.from_array(all & to_array(other))
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def to_array(other)
|
115
|
+
case other
|
116
|
+
when Scopes
|
117
|
+
other.all
|
118
|
+
else
|
119
|
+
other.to_a
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|