padlock_auth 0.1.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 +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
|