doorkeeper-device_authorization_grant 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +320 -0
  4. data/Rakefile +34 -0
  5. data/app/controllers/doorkeeper/device_authorization_grant/device_authorizations_controller.rb +68 -0
  6. data/app/controllers/doorkeeper/device_authorization_grant/device_codes_controller.rb +28 -0
  7. data/app/views/doorkeeper/device_authorization_grant/device_authorizations/index.html.erb +19 -0
  8. data/config/locales/en.yml +15 -0
  9. data/db/migrate/20200629094624_create_doorkeeper_device_grants.rb +28 -0
  10. data/lib/doorkeeper/device_authorization_grant.rb +47 -0
  11. data/lib/doorkeeper/device_authorization_grant/config.rb +92 -0
  12. data/lib/doorkeeper/device_authorization_grant/engine.rb +12 -0
  13. data/lib/doorkeeper/device_authorization_grant/errors.rb +43 -0
  14. data/lib/doorkeeper/device_authorization_grant/oauth/device_authorization_request.rb +88 -0
  15. data/lib/doorkeeper/device_authorization_grant/oauth/device_authorization_response.rb +73 -0
  16. data/lib/doorkeeper/device_authorization_grant/oauth/device_code_request.rb +105 -0
  17. data/lib/doorkeeper/device_authorization_grant/oauth/helpers/user_code.rb +39 -0
  18. data/lib/doorkeeper/device_authorization_grant/orm/active_record.rb +27 -0
  19. data/lib/doorkeeper/device_authorization_grant/orm/active_record/device_grant.rb +150 -0
  20. data/lib/doorkeeper/device_authorization_grant/rails/routes.rb +65 -0
  21. data/lib/doorkeeper/device_authorization_grant/rails/routes/mapper.rb +40 -0
  22. data/lib/doorkeeper/device_authorization_grant/rails/routes/mapping.rb +49 -0
  23. data/lib/doorkeeper/device_authorization_grant/request/device_authorization.rb +38 -0
  24. data/lib/doorkeeper/device_authorization_grant/version.rb +8 -0
  25. data/lib/doorkeeper/request/device_code.rb +33 -0
  26. data/lib/generators/doorkeeper/device_authorization_grant/install_generator.rb +16 -0
  27. data/lib/generators/doorkeeper/device_authorization_grant/templates/initializer.rb +33 -0
  28. metadata +139 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 41a11fbc4e3f17a41ba597414e90d134d92f121ed56ee0ab3fbd3289d5a8c000
4
+ data.tar.gz: 7662d33b032f9293c09ebdcfeef4afc2e6fabdbb5da6adf36baf2ac48d2d1ab6
5
+ SHA512:
6
+ metadata.gz: c7c692bfa261c432498e68a300f657ed532471f6c3414b8d7def3c1c27c1f0e3424a3ddba99c19d6eb274d6aecd8a5f6d14d5a593e651bdb659aa455498a8ccb
7
+ data.tar.gz: b7de6fc069d86e5c2ec3dd3e2f94dc645ffef57934747dd2d9d3ee30bc3a0c7b529632c57e215de3302988db49ef88e994f08eafdc161327c87268bbec205ddf
@@ -0,0 +1,20 @@
1
+ Copyright 2020 EXOP Group
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.
@@ -0,0 +1,320 @@
1
+ # Doorkeeper::DeviceAuthorizationGrant
2
+
3
+ OAuth 2.0 device authorization grant extension for Doorkeeper.
4
+
5
+ This library implements the OAuth 2.0 device authorization grant
6
+ ([RFC 8628](https://tools.ietf.org/html/rfc8628)) for
7
+ [Ruby on Rails](https://rubyonrails.org/) applications on top of the
8
+ [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) OAuth 2.0 framework.
9
+
10
+ ## Status
11
+
12
+ This extension currently works with Doorkeeper version `>= 5.4.0`.
13
+
14
+ As of June 25 2020, due to some limitations of Doorkeeper, it is currently
15
+ inconvenient for this Gem to use the official OAuth grant type
16
+ `urn:ietf:params:oauth:grant-type:device_code`. Instead, it has been renamed
17
+ to simply `device_code`, which is non-standard. This is going to be corrected
18
+ soon: the next Doorkeeper release will include the ability to cleanly
19
+ register custom Grant Flows - see [Doorkeeper Pull Request #1418](https://github.com/doorkeeper-gem/doorkeeper/pull/1418).
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'doorkeeper-device_authorization_grant'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ ```bash
32
+ $ bundle
33
+ ```
34
+
35
+ Or install it yourself as:
36
+
37
+ ```bash
38
+ $ gem install doorkeeper-device_authorization_grant
39
+ ```
40
+
41
+ Run the installation generator to update routes and create a dedicated initializer:
42
+
43
+ ```bash
44
+ $ rails generate doorkeeper:device_authorization_grant:install
45
+ ```
46
+
47
+ Generate a migration for Active Record (other ORMs are currently not supported):
48
+
49
+ ```bash
50
+ $ rails doorkeeper_device_authorization_grant_engine:install:migrations
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ ### Doorkeeper configuration
56
+
57
+ In your Doorkeeper initializer (usually `config/initializers/doorkeeper.rb`), enable
58
+ the device flow extension grant type, adding to the `grant_flows` option the `device_code`
59
+ string. For example:
60
+
61
+ ```ruby
62
+ # config/initializers/doorkeeper.rb
63
+
64
+ Doorkeeper.configure do
65
+ # ...
66
+
67
+ grant_flows [
68
+ # Note: this is a non-standard grant flow, used instead of the
69
+ # official `urn:ietf:params:oauth:grant-type:device_code` due to
70
+ # current Doorkeeper limitations.
71
+ 'device_code',
72
+
73
+ # together with all the other grant flows you already enabled, for example:
74
+ 'authorization_code',
75
+ 'client_credentials'
76
+ # ...
77
+ ]
78
+
79
+ # ...
80
+ end
81
+ ```
82
+
83
+ Please note that **this is not the official grant flow**. The real one should be
84
+ the IANA URN `urn:ietf:params:oauth:grant-type:device_code`, however this is hard
85
+ to support with the current version of Doorkeeper, due to how strategy classes are
86
+ looked up by grant type value.
87
+
88
+ ### Device Authorization Grant configuration
89
+
90
+ The gem's installation scripts automatically creates a new initializer file:
91
+ `config/initializers/doorkeeper_device_authorization_grant.rb`. Here you can
92
+ adjust the configuration parameters according to your needs.
93
+
94
+ ### Routes
95
+
96
+ The gem's installation scripts automatically modify your `config/routes.rb`
97
+ file, adding the default routes to the controllers described above. The
98
+ routes file should then look like this:
99
+
100
+ ```ruby
101
+ Rails.application.routes.draw do
102
+ use_doorkeeper_device_authorization_grant
103
+ # your routes ...
104
+ end
105
+ ```
106
+
107
+ This is enough to add to your app the following default routes:
108
+
109
+ ```
110
+ Prefix Verb URI Controller#Action
111
+ oauth_device_codes_create POST /oauth/authorize_device doorkeeper/device_authorization_grant/device_codes#create
112
+ oauth_device_authorizations_index GET /oauth/device doorkeeper/device_authorization_grant/device_authorizations#index
113
+ oauth_device_authorizations_authorize POST /oauth/device doorkeeper/device_authorization_grant/device_authorizations#authorize
114
+ ```
115
+
116
+ The routing method `use_doorkeeper_device_authorization_grant` allows extra customization,
117
+ just like `use_doorkeeper` (see [Doorkeeper Wiki - Customizing routes](https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes)).
118
+
119
+ This Gem defines two Rails controllers:
120
+
121
+ - `DeviceCodesController` serves Device Authorization requests, as described
122
+ by [RFC 8628](https://tools.ietf.org/html/rfc8628), sections 3.1
123
+ and 3.2.
124
+ - `DeviceAuthorizationsController` provides a bare-bones implementation of a
125
+ verification web page which allows an authenticated resource-owner to
126
+ authorize a device, by providing an end-user code.
127
+
128
+ You can change the controllers to your **custom controllers** with the `controller` option:
129
+
130
+ ```ruby
131
+ Rails.application.routes.draw do
132
+ use_doorkeeper_device_authorization_grant do
133
+ # it accepts :device_authorizations and :device_codes
134
+ controller device_authorizations: 'custom_device_authorizations'
135
+ end
136
+ end
137
+ ```
138
+
139
+ Be sure to use the same superclasses of the original controllers (or something compatible).
140
+
141
+ You can set **custom aliases** with `as`:
142
+
143
+ ```ruby
144
+ Rails.application.routes.draw do
145
+ use_doorkeeper_device_authorization_grant do
146
+ # it accepts :device_authorizations and :device_codes
147
+ as device_codes: :custom_device
148
+ end
149
+ end
150
+ ```
151
+
152
+ You can **skip routes** with `skip_controllers`:
153
+
154
+ ```ruby
155
+ Rails.application.routes.draw do
156
+ use_doorkeeper_device_authorization_grant do
157
+ # it accepts :device_authorizations and :device_codes
158
+ skip_controllers :device_authorizations
159
+ end
160
+ end
161
+ ```
162
+
163
+ The default scope is `oauth`. You can provide a **custom scope** like this:
164
+
165
+ ```ruby
166
+ Rails.application.routes.draw do
167
+ use_doorkeeper_device_authorization_grant scope: 'oauth2'
168
+ end
169
+ ```
170
+
171
+ ## Usage
172
+
173
+ The following sections show the typical steps of a device authorization flow.
174
+ Default configuration and routes are assumed.
175
+
176
+ ### Device Authorization Request
177
+
178
+ Reference: [RFC 8628, section 3.1 - Device Authorization Request](https://tools.ietf.org/html/rfc8628#section-3.1).
179
+
180
+ First of all, a *Device Client* can perform a *Device Authorization Request* to
181
+ the *Authorization Server* (your Rails application, with Doorkeeper and this
182
+ gem extension) like this:
183
+
184
+ ```http request
185
+ POST /oauth/authorize_device HTTP/1.1
186
+ Content-Type: application/x-www-form-urlencoded
187
+
188
+ client_id=1406020730&scope=example_scope
189
+ ```
190
+
191
+ ### Device Authorization Response
192
+
193
+ Reference: [RFC 8628, section 3.2 - Device Authorization Response](https://tools.ietf.org/html/rfc8628#section-3.2).
194
+
195
+ The *Authorization Server* responds with a *Device Authorization Response*:
196
+
197
+ ```
198
+ HTTP/1.1 200 OK
199
+ Content-Type: application/json
200
+
201
+ {
202
+ "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
203
+ "user_code": "0A44L90H",
204
+ "verification_uri": "https://example.com/oauth/device",
205
+ "verification_uri_complete": "https://example.com/oauth/device?user_code=0A44L90H",
206
+ "expires_in": 300,
207
+ "interval": 5
208
+ }
209
+ ```
210
+
211
+ ### User interaction
212
+
213
+ Reference: [RFC 8628, section 3.3 - User Interaction](https://tools.ietf.org/html/rfc8628#section-3.3).
214
+
215
+ The *Device Client* can now display to the end user the `user_code` and the
216
+ `verification_uri` (or somehow make use of `verification_uri_complete`, in special cases).
217
+
218
+ The user should visit URI in a user agent on a secondary device (for example, in a browser
219
+ on their mobile phone) and enter the user code.
220
+
221
+ During the user interaction, the device continuously polls the token endpoint with the
222
+ `device_code`, as detailed in the next section, until the user completes the interaction,
223
+ the code expires, or another error occurs.
224
+
225
+ The default Rails route provided by this Gem, `/oauth/device`, allows an authenticated
226
+ request owner (for example, a user) to manually verify the user code.
227
+
228
+ ### Device Access Token Request / polling
229
+
230
+ Reference: [RFC 8628, section 3.4 - Device Access Token Request](https://tools.ietf.org/html/rfc8628#section-3.4).
231
+
232
+ After displaying instructions to the user, the *Device Client* should create a
233
+ *Device Access Token Request* and send it to the token endpoint (provided
234
+ by Dorkeeper), for example:
235
+
236
+ ```http request
237
+ POST /oauth/token HTTP/1.1
238
+ Content-Type: application/x-www-form-urlencoded
239
+
240
+ grant_type=device_code
241
+ &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
242
+ &client_id=1406020730
243
+ ```
244
+
245
+ **Note:** this is a non-standard `grant_type`, used instead of the official
246
+ `urn:ietf:params:oauth:grant-type:device_code` due to current limitations of
247
+ the Doorkeeper gem.
248
+
249
+ The response to this request is defined in the next section. It is expected for
250
+ the *Device Client* to try the access token request repeatedly in a polling
251
+ fashion, based on the error code in the response. The polling time interval
252
+ was possibly included in the *Device Authorization Response*, but it is
253
+ optional; if no value was provided, the client MUST use 5 seconds as the default.
254
+
255
+ ### Device Access Token Response
256
+
257
+ Reference: [RFC 8628, section 3.5 - Device Access Token Response](https://tools.ietf.org/html/rfc8628#section-3.5).
258
+
259
+ Please refer to the RFC document for exhaustive documentation. Here we show just
260
+ some possible responses.
261
+
262
+ While the authorization request is still pending, and the device-code token is
263
+ not expired, the response contains an `authorization_pending` error:
264
+
265
+ ```
266
+ HTTP/1.1 400 Bad Request
267
+ Content-Type: application/json
268
+
269
+ { "error": "authorization_pending", "error_description": "..." }
270
+ ```
271
+
272
+ The client should simply continue with further polling requests.
273
+
274
+ If the client requests are too close in time, a `slow_down` error is returned:
275
+
276
+ ```
277
+ HTTP/1.1 400 Bad Request
278
+ Content-Type: application/json
279
+
280
+ { "error": "slow_down", "error_description": "..." }
281
+ ```
282
+
283
+ The client can still continue with polling requests, but the polling time interval
284
+ MUST be increased by 5 seconds for all subsequent requests.
285
+
286
+ If the `device_code` has expired, the response contains the `expired_token` error:
287
+
288
+ ```
289
+ HTTP/1.1 400 Bad Request
290
+ Content-Type: application/json
291
+
292
+ { "error": "expired_token", "error_description": "..." }
293
+ ```
294
+
295
+ The client should stop polling, and may commence a new device authorization
296
+ request (possibly upon waiting for further user interaction).
297
+
298
+ Once the user has successfully authorized the device, a successful response will
299
+ be eventually returned. This is a standard OAuth 2.0 response, described in
300
+ [Section 5.1 of [RFC6749]](https://tools.ietf.org/html/rfc6749#section-5.1). Here
301
+ is a typical bearer token response:
302
+
303
+ ```
304
+ HTTP/1.1 200 OK
305
+ Content-Type: application/json
306
+
307
+ {
308
+ "access_token": "FkPeBMF8Ab0zkYj6vQLZCxZ5OP0Hrd7ST3RS99x7nRM",
309
+ "token_type": "Bearer",
310
+ "expires_in": 7200,
311
+ "scope": "read",
312
+ "created_at": 1593096829
313
+ }
314
+ ```
315
+
316
+ The device authentication flow is now complete, and the token data can be used to
317
+ authenticate requests against the authorization and/or resource server.
318
+
319
+ ## License
320
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Doorkeeper::DeviceAuthorizationGrant'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+
26
+ require 'rake/testtask'
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ task default: :test
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module DeviceAuthorizationGrant
5
+ # The Device Authorizations controller provides a simple interface which
6
+ # allows authenticated resource owners to authorize devices, by providing
7
+ # a user code.
8
+ class DeviceAuthorizationsController < Doorkeeper::ApplicationController
9
+ before_action :authenticate_resource_owner!
10
+
11
+ def index
12
+ respond_to do |format|
13
+ format.html
14
+ format.json { head :no_content }
15
+ end
16
+ end
17
+
18
+ def authorize
19
+ device_grant_model.transaction do
20
+ device_grant = device_grant_model.lock.find_by(user_code: user_code)
21
+ return authorization_error_response(:invalid_user_code) if device_grant.nil?
22
+ return authorization_error_response(:expired_user_code) if device_grant.expired?
23
+
24
+ device_grant.update!(user_code: nil, resource_owner_id: current_resource_owner.id)
25
+ end
26
+
27
+ authorization_success_response
28
+ end
29
+
30
+ private
31
+
32
+ def authorization_success_response
33
+ respond_to do |format|
34
+ notice = I18n.t(:success, scope: i18n_flash_scope(:authorize))
35
+ format.html { redirect_to oauth_device_authorizations_index_url, notice: notice }
36
+ format.json { head :no_content }
37
+ end
38
+ end
39
+
40
+ # @param error_message_key [Symbol]
41
+ def authorization_error_response(error_message_key)
42
+ respond_to do |format|
43
+ notice = I18n.t(error_message_key, scope: i18n_flash_scope(:authorize))
44
+ format.html { redirect_to oauth_device_authorizations_index_url, notice: notice }
45
+ format.json do
46
+ render json: { errors: [notice] }, status: :unprocessable_entity
47
+ end
48
+ end
49
+ end
50
+
51
+ # @return [Class]
52
+ def device_grant_model
53
+ @device_grant_model ||= Doorkeeper::DeviceAuthorizationGrant.configuration.device_grant_model
54
+ end
55
+
56
+ # @return [String, nil]
57
+ def user_code
58
+ params[:user_code]
59
+ end
60
+
61
+ # @param action [Symbol]
62
+ # @return [Array<Symbol>]
63
+ def i18n_flash_scope(action)
64
+ %I[doorkeeper flash device_codes #{action}]
65
+ end
66
+ end
67
+ end
68
+ end