rodauth-oauth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Rodauth::OAuth
6
+ module Rails
7
+ module Generators
8
+ class ViewsGenerator < ::Rails::Generators::Base
9
+ source_root "#{__dir__}/templates"
10
+ namespace "roda:oauth:views"
11
+
12
+ DEFAULT = %w[oauth_authorize].freeze
13
+ VIEWS = {
14
+ oauth_authorize: DEFAULT,
15
+ oauth_applications: %w[oauth_applications oauth_application new_oauth_application]
16
+ }.freeze
17
+
18
+ DEPENDENCIES = {
19
+ active_sessions: :logout,
20
+ otp: :two_factor_base,
21
+ sms_codes: :two_factor_base,
22
+ recovery_codes: :two_factor_base,
23
+ webauthn: :two_factor_base
24
+ }.freeze
25
+
26
+ class_option :features, type: :array,
27
+ desc: "Roda OAuth features to generate views for (oauth_applications etc.)",
28
+ default: DEFAULT
29
+
30
+ class_option :all, aliases: "-a", type: :boolean,
31
+ desc: "Generates views for all Roda OAuth features",
32
+ default: false
33
+
34
+ class_option :directory, aliases: "-d", type: :string,
35
+ desc: "The directory under app/views/* into which to create views",
36
+ default: "rodauth"
37
+
38
+ def create_views
39
+ features = options[:all] ? VIEWS.keys : (DEFAULT + options[:features]).map(&:to_sym)
40
+
41
+ views = features.inject([]) do |list, feature|
42
+ list |= VIEWS[feature] || []
43
+ list |= VIEWS[DEPENDENCIES[feature]] || []
44
+ end
45
+
46
+ views.each do |view|
47
+ template "app/views/rodauth/#{view}.html.erb",
48
+ "app/views/#{options[:directory].underscore}/#{view}.html.erb"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,802 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth) do
5
+ # RUBY EXTENSIONS
6
+ unless Regexp.method_defined?(:match?)
7
+ module RegexpExtensions
8
+ refine(Regexp) do
9
+ def match?(*args)
10
+ !match(*args).nil?
11
+ end
12
+ end
13
+ end
14
+ using(RegexpExtensions)
15
+ end
16
+
17
+ SCOPES = %w[profile.read].freeze
18
+
19
+ depends :login
20
+
21
+ before "authorize"
22
+ after "authorize"
23
+ after "authorize_failure"
24
+
25
+ before "token"
26
+ after "token"
27
+
28
+ before "revoke"
29
+ after "revoke"
30
+
31
+ before "create_oauth_application"
32
+ after "create_oauth_application"
33
+
34
+ error_flash "OAuth Authorization invalid parameters", "oauth_grant_valid_parameters"
35
+
36
+ error_flash "Please authorize to continue", "require_authorization"
37
+ error_flash "There was an error registering your oauth application", "create_oauth_application"
38
+ notice_flash "Your oauth application has been registered", "create_oauth_application"
39
+
40
+ notice_flash "The oauth token has been revoked", "revoke_oauth_token"
41
+
42
+ view "oauth_authorize", "Authorize", "authorize"
43
+ view "oauth_applications", "Oauth Applications", "oauth_applications"
44
+ view "oauth_application", "Oauth Application", "oauth_application"
45
+ view "new_oauth_application", "New Oauth Application", "new_oauth_application"
46
+ view "oauth_tokens", "Oauth Tokens", "oauth_tokens"
47
+
48
+ auth_value_method :json_response_content_type, "application/json"
49
+
50
+ auth_value_method :oauth_grant_expires_in, 60 * 5 # 5 minutes
51
+ auth_value_method :oauth_token_expires_in, 60 * 60 # 60 minutes
52
+ auth_value_method :use_oauth_implicit_grant_type, false
53
+
54
+ # URL PARAMS
55
+
56
+ # Authorize / token
57
+ %w[
58
+ grant_type code refresh_token client_id scope
59
+ state redirect_uri scopes token_type_hint token
60
+ access_type response_type
61
+ ].each do |param|
62
+ auth_value_method :"#{param}_param", param
63
+ end
64
+
65
+ # Application
66
+ APPLICATION_REQUIRED_PARAMS = %w[name description scopes homepage_url redirect_uri].freeze
67
+ auth_value_method :oauth_application_required_params, APPLICATION_REQUIRED_PARAMS
68
+
69
+ (APPLICATION_REQUIRED_PARAMS + %w[client_id client_secret]).each do |param|
70
+ auth_value_method :"oauth_application_#{param}_param", param
71
+ end
72
+
73
+ # OAuth Token
74
+ auth_value_method :oauth_tokens_path, "oauth-tokens"
75
+ auth_value_method :oauth_tokens_table, :oauth_tokens
76
+ auth_value_method :oauth_tokens_id_column, :id
77
+
78
+ %i[
79
+ oauth_application_id oauth_token_id oauth_grant_id account_id
80
+ token refresh_token scopes
81
+ expires_in revoked_at
82
+ ].each do |column|
83
+ auth_value_method :"oauth_tokens_#{column}_column", column
84
+ end
85
+
86
+ # OAuth Grants
87
+ auth_value_method :oauth_grants_table, :oauth_grants
88
+ auth_value_method :oauth_grants_id_column, :id
89
+ %i[
90
+ account_id oauth_application_id
91
+ redirect_uri code scopes access_type
92
+ expires_in revoked_at
93
+ ].each do |column|
94
+ auth_value_method :"oauth_grants_#{column}_column", column
95
+ end
96
+
97
+ auth_value_method :authorization_required_error_status, 401
98
+ auth_value_method :invalid_oauth_response_status, 400
99
+
100
+ # OAuth Applications
101
+ auth_value_method :oauth_applications_path, "oauth-applications"
102
+ auth_value_method :oauth_applications_table, :oauth_applications
103
+
104
+ auth_value_method :oauth_applications_id_column, :id
105
+ auth_value_method :oauth_applications_id_pattern, Integer
106
+
107
+ %i[
108
+ account_id
109
+ name description scopes
110
+ client_id client_secret
111
+ homepage_url redirect_uri
112
+ ].each do |column|
113
+ auth_value_method :"oauth_applications_#{column}_column", column
114
+ end
115
+
116
+ auth_value_method :oauth_application_default_scope, SCOPES.first
117
+ auth_value_method :oauth_application_scopes, SCOPES
118
+ auth_value_method :oauth_token_type, "Bearer"
119
+
120
+ auth_value_method :invalid_request, "Request is missing a required parameter"
121
+ auth_value_method :invalid_client, "Invalid client"
122
+ auth_value_method :unauthorized_client, "Unauthorized client"
123
+ auth_value_method :invalid_grant_type_message, "Invalid grant type"
124
+ auth_value_method :invalid_grant_message, "Invalid grant"
125
+ auth_value_method :invalid_scope_message, "Invalid scope"
126
+
127
+ auth_value_method :invalid_url_message, "Invalid URL"
128
+ auth_value_method :unsupported_token_type_message, "Invalid token type hint"
129
+
130
+ auth_value_method :unique_error_message, "is already in use"
131
+ auth_value_method :null_error_message, "is not filled"
132
+
133
+ auth_value_methods(
134
+ :oauth_unique_id_generator
135
+ )
136
+
137
+ redirect(:oauth_application) do |id|
138
+ "/#{oauth_applications_path}/#{id}"
139
+ end
140
+
141
+ redirect(:require_authorization) do
142
+ if logged_in?
143
+ oauth_authorize_path
144
+ else
145
+ login_redirect
146
+ end
147
+ end
148
+
149
+ auth_value_method :json_request_accept_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
150
+ auth_methods(:json_request?)
151
+
152
+ def check_csrf?
153
+ case request.path
154
+ when oauth_token_path
155
+ false
156
+ when oauth_revoke_path
157
+ !json_request?
158
+ else
159
+ super
160
+ end
161
+ end
162
+
163
+ # Overrides logged_in?, so that a valid authorization token also authnenticates a request
164
+ def logged_in?
165
+ super || authorization_token
166
+ end
167
+
168
+ def json_request?
169
+ return @json_request if defined?(@json_request)
170
+
171
+ @json_request = request.get_header("HTTP_ACCEPT") =~ json_request_accept_regexp
172
+ end
173
+
174
+ attr_reader :oauth_application
175
+
176
+ def initialize(scope)
177
+ @scope = scope
178
+ end
179
+
180
+ def state
181
+ state = param(state_param)
182
+
183
+ return unless state && !state.empty?
184
+
185
+ state
186
+ end
187
+
188
+ def scopes
189
+ scopes = param(scopes_param)
190
+
191
+ return [oauth_application_default_scope] unless scopes && !scopes.empty?
192
+
193
+ scopes.split(" ")
194
+ end
195
+
196
+ def client_id
197
+ client_id = param(client_id_param)
198
+
199
+ return unless client_id && !client_id.empty?
200
+
201
+ client_id
202
+ end
203
+
204
+ def redirect_uri
205
+ redirect_uri = param(redirect_uri_param)
206
+
207
+ return oauth_application[oauth_applications_redirect_uri_column] unless redirect_uri && !redirect_uri.empty?
208
+
209
+ redirect_uri
210
+ end
211
+
212
+ def token_type_hint
213
+ token_type_hint = param(token_type_hint_param)
214
+
215
+ return "access_token" unless token_type_hint && !token_type_hint.empty?
216
+
217
+ token_type_hint
218
+ end
219
+
220
+ def token
221
+ token = param(token_param)
222
+
223
+ return unless token && !token.empty?
224
+
225
+ token
226
+ end
227
+
228
+ def oauth_application
229
+ return @oauth_application if defined?(@oauth_application)
230
+
231
+ @oauth_application = begin
232
+ client_id = param(client_id_param)
233
+
234
+ return unless client_id
235
+
236
+ db[oauth_applications_table].filter(oauth_applications_client_id_column => client_id).first
237
+ end
238
+ end
239
+
240
+ def authorization_token
241
+ return @authorization_token if defined?(@authorization_token)
242
+
243
+ @authorization_token = begin
244
+ value = request.get_header("HTTP_AUTHORIZATION").to_s
245
+
246
+ scheme, token = value.split(" ", 2)
247
+
248
+ return unless scheme == "Bearer"
249
+
250
+ # check if there is a token
251
+ # check if token has not expired
252
+ # check if token has been revoked
253
+ db[oauth_tokens_table].where(oauth_tokens_token_column => token)
254
+ .where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
255
+ .where(oauth_tokens_revoked_at_column => nil)
256
+ .first
257
+ end
258
+ end
259
+
260
+ def require_oauth_authorization(*scopes)
261
+ authorization_required unless authorization_token
262
+
263
+ scopes << oauth_application_default_scope if scopes.empty?
264
+
265
+ token_scopes = authorization_token[:scopes].split(",")
266
+
267
+ authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
268
+ end
269
+
270
+ # /oauth-applications routes
271
+ def oauth_applications
272
+ request.on(oauth_applications_path) do
273
+ require_account
274
+
275
+ request.get "new" do
276
+ new_oauth_application_view
277
+ end
278
+ request.on(oauth_applications_id_pattern) do |id|
279
+ oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
280
+ scope.instance_variable_set(:@oauth_application, oauth_application)
281
+
282
+ request.is do
283
+ request.get do
284
+ oauth_application_view
285
+ end
286
+ end
287
+
288
+ request.on(oauth_tokens_path) do
289
+ oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
290
+ scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
291
+ oauth_tokens_view
292
+ end
293
+ end
294
+
295
+ request.get do
296
+ scope.instance_variable_set(:@oauth_applications, db[:oauth_applications])
297
+ oauth_applications_view
298
+ end
299
+
300
+ request.post do
301
+ catch_error do
302
+ validate_oauth_application_params
303
+
304
+ transaction do
305
+ before_create_oauth_application
306
+ id = create_oauth_application
307
+ after_create_oauth_application
308
+ set_notice_flash create_oauth_application_notice_flash
309
+ redirect oauth_application_redirect(id)
310
+ end
311
+ end
312
+ set_error_flash create_oauth_application_error_flash
313
+ new_oauth_application_view
314
+ end
315
+ end
316
+ end
317
+
318
+ private
319
+
320
+ def oauth_unique_id_generator
321
+ SecureRandom.uuid
322
+ end
323
+
324
+ # Oauth Application
325
+
326
+ def oauth_application_params
327
+ @oauth_application_params ||= oauth_application_required_params.each_with_object({}) do |param, params|
328
+ value = request.params[__send__(:"oauth_application_#{param}_param")]
329
+ if value && !value.empty?
330
+ params[param] = value
331
+ else
332
+ set_field_error(param, null_error_message)
333
+ end
334
+ end
335
+ end
336
+
337
+ def validate_oauth_application_params
338
+ oauth_application_params.each do |key, value|
339
+ if key == oauth_application_homepage_url_param ||
340
+ key == oauth_application_redirect_uri_param
341
+
342
+ set_field_error(key, invalid_url_message) unless URI::DEFAULT_PARSER.make_regexp(%w[http https]).match?(value)
343
+
344
+ elsif key == oauth_application_scopes_param
345
+
346
+ value.each do |scope|
347
+ set_field_error(key, invalid_scope_message) unless oauth_application_scopes.include?(scope)
348
+ end
349
+ end
350
+ end
351
+
352
+ throw :rodauth_error if @field_errors && !@field_errors.empty?
353
+ end
354
+
355
+ def create_oauth_application
356
+ create_params = {
357
+ oauth_applications_account_id_column => account_id,
358
+ oauth_applications_name_column => oauth_application_params[oauth_application_name_param],
359
+ oauth_applications_description_column => oauth_application_params[oauth_application_description_param],
360
+ oauth_applications_scopes_column => oauth_application_params[oauth_application_scopes_param],
361
+ oauth_applications_homepage_url_column => oauth_application_params[oauth_application_homepage_url_param],
362
+ oauth_applications_redirect_uri_column => oauth_application_params[oauth_application_redirect_uri_param]
363
+ }
364
+
365
+ # set client ID/secret pairs
366
+ create_params.merge! \
367
+ oauth_applications_client_id_column => oauth_unique_id_generator,
368
+ oauth_applications_client_secret_column => oauth_unique_id_generator
369
+
370
+ create_params[oauth_applications_scopes_column] = if create_params[oauth_applications_scopes_column]
371
+ create_params[oauth_applications_scopes_column].join(",")
372
+ else
373
+ oauth_application_default_scope
374
+ end
375
+
376
+ ds = db[oauth_applications_table]
377
+
378
+ id = nil
379
+ raised = begin
380
+ id = if ds.supports_returning?(:insert)
381
+ ds.returning(oauth_applications_id_column).insert(create_params)
382
+ else
383
+ id = db[oauth_applications_table].insert(create_params)
384
+ db[oauth_applications_table].where(oauth_applications_id_column => id).get(oauth_applications_id_column)
385
+ end
386
+ false
387
+ rescue Sequel::ConstraintViolation => e
388
+ e
389
+ end
390
+
391
+ if raised
392
+ field = raised.message[/\.(.*)$/, 1]
393
+ case raised
394
+ when Sequel::UniqueConstraintViolation
395
+ throw_error(field, unique_error_message)
396
+ when Sequel::NotNullConstraintViolation
397
+ throw_error(field, null_error_message)
398
+ end
399
+ end
400
+
401
+ !raised && id
402
+ end
403
+
404
+ # Authorize
405
+
406
+ def validate_oauth_grant_params
407
+ unless oauth_application && check_valid_redirect_uri? && check_valid_access_type?
408
+ redirect_response_error("invalid_request")
409
+ end
410
+ redirect_response_error("invalid_scope") unless check_valid_scopes?
411
+ end
412
+
413
+ def create_oauth_grant
414
+ create_params = {
415
+ oauth_grants_account_id_column => account_id,
416
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
417
+ oauth_grants_redirect_uri_column => redirect_uri,
418
+ oauth_grants_code_column => oauth_unique_id_generator,
419
+ oauth_grants_expires_in_column => Time.now + oauth_grant_expires_in,
420
+ oauth_grants_scopes_column => scopes.join(",")
421
+ }
422
+
423
+ unless (access_type = param("access_type")).empty?
424
+ create_params[oauth_grants_access_type_column] = access_type
425
+ end
426
+
427
+ ds = db[oauth_grants_table]
428
+
429
+ begin
430
+ if ds.supports_returning?(:insert)
431
+ ds.returning(authorize_code_column).insert(create_params)
432
+ else
433
+ id = ds.insert(create_params)
434
+ ds.where(oauth_grants_id_column => id).get(oauth_grants_code_column)
435
+ end
436
+ rescue Sequel::UniqueConstraintViolation
437
+ retry
438
+ end
439
+ end
440
+
441
+ # Access Tokens
442
+
443
+ def validate_oauth_token_params
444
+ redirect_response_error("invalid_request") unless param(client_id_param)
445
+
446
+ unless (grant_type = param(grant_type_param))
447
+ redirect_response_error("invalid_request")
448
+ end
449
+
450
+ case grant_type
451
+ when "authorization_code"
452
+ redirect_response_error("invalid_request") unless param(code_param)
453
+
454
+ when "refresh_token"
455
+ redirect_response_error("invalid_request") unless param(refresh_token_param)
456
+ else
457
+ redirect_response_error("invalid_request")
458
+ end
459
+ end
460
+
461
+ def generate_oauth_token(params = {})
462
+ create_params = {
463
+ oauth_grants_expires_in_column => Time.now + oauth_token_expires_in,
464
+ oauth_tokens_token_column => oauth_unique_id_generator
465
+ }.merge(params)
466
+
467
+ ds = db[oauth_tokens_table]
468
+
469
+ begin
470
+ if ds.supports_returning?(:insert)
471
+ ds.returning.insert(create_params)
472
+ else
473
+ id = ds.insert(create_params)
474
+ ds.where(oauth_tokens_id_column => id).first
475
+ end
476
+ rescue Sequel::UniqueConstraintViolation
477
+ retry
478
+ end
479
+ end
480
+
481
+ def create_oauth_token
482
+ case param(grant_type_param)
483
+ when "authorization_code"
484
+ # fetch oauth grant
485
+ oauth_grant = db[oauth_grants_table].where(
486
+ oauth_grants_code_column => param(code_param),
487
+ oauth_grants_redirect_uri_column => param(redirect_uri_param),
488
+ oauth_grants_oauth_application_id_column => db[oauth_applications_table].where(
489
+ oauth_applications_client_id_column => param(client_id_param),
490
+ oauth_applications_account_id_column => oauth_applications_account_id_column
491
+ ).select(oauth_applications_id_column)
492
+ ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
493
+ .where(oauth_grants_revoked_at_column => nil)
494
+ .first
495
+
496
+ redirect_response_error("invalid_grant") unless oauth_grant
497
+
498
+ create_params = {
499
+ oauth_tokens_account_id_column => oauth_grant[oauth_grants_account_id_column],
500
+ oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
501
+ oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
502
+ oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
503
+ }
504
+
505
+ if oauth_grant[oauth_grants_access_type_column] == "offline"
506
+ create_params[oauth_tokens_refresh_token_column] = oauth_unique_id_generator
507
+ end
508
+ # revoke oauth grant
509
+ db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
510
+ .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
511
+
512
+ generate_oauth_token(create_params)
513
+ when "refresh_token"
514
+ # fetch oauth grant
515
+ oauth_token = db[oauth_tokens_table].where(
516
+ oauth_tokens_refresh_token_column => param(refresh_token_param),
517
+ oauth_tokens_oauth_application_id_column => db[oauth_applications_table].where(
518
+ oauth_applications_client_id_column => param(client_id_param),
519
+ oauth_applications_account_id_column => account_id
520
+ ).select(oauth_applications_id_column)
521
+ ).where(oauth_grants_revoked_at_column => nil).first
522
+
523
+ redirect_response_error("invalid_grant") unless oauth_token
524
+
525
+ update_params = {
526
+ oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
527
+ oauth_tokens_expires_in_column => Time.now + oauth_token_expires_in,
528
+ oauth_tokens_token_column => oauth_unique_id_generator
529
+ }
530
+
531
+ ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
532
+ begin
533
+ if ds.supports_returning?(:update)
534
+ ds.returning.update(update_params)
535
+ else
536
+ ds.update(update_params)
537
+ ds.first
538
+ end
539
+ rescue Sequel::UniqueConstraintViolation
540
+ retry
541
+ end
542
+ else
543
+ redirect_response_error("invalid_grant")
544
+ end
545
+ end
546
+
547
+ # Token revocation
548
+
549
+ TOKEN_HINT_TYPES = %w[access_token refresh_token].freeze
550
+
551
+ def validate_oauth_revoke_params
552
+ # check if valid token hint type
553
+ redirect_response_error("unsupported_token_type") unless TOKEN_HINT_TYPES.include?(token_type_hint)
554
+
555
+ redirect_response_error("invalid_request") unless param(token_param)
556
+ end
557
+
558
+ def revoke_oauth_token
559
+ # one can only revoke tokens which haven't been revoked before, and which are
560
+ # either our tokens, or tokens from applications we own.
561
+ ds = db[oauth_tokens_table]
562
+ .where(oauth_tokens_revoked_at_column => nil)
563
+ .where(
564
+ Sequel.or(
565
+ oauth_tokens_account_id_column => account_id,
566
+ oauth_tokens_oauth_application_id_column => db[oauth_applications_table].where(
567
+ oauth_applications_client_id_column => param(client_id_param),
568
+ oauth_applications_account_id_column => account_id
569
+ ).select(oauth_applications_id_column)
570
+ )
571
+ )
572
+ ds = case token_type_hint
573
+ when "access_token"
574
+ ds.where(oauth_tokens_token_column => token)
575
+ when "refresh_token"
576
+ ds.where(oauth_tokens_refresh_token_column => token)
577
+ end
578
+
579
+ oauth_token = ds.first
580
+ redirect_response_error("invalid_request") unless oauth_token
581
+
582
+ update_params = { oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
583
+
584
+ ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
585
+
586
+ if ds.supports_returning?(:update)
587
+ ds.returning.update(update_params)
588
+ else
589
+ ds.update(update_params)
590
+ ds.first
591
+ end
592
+
593
+ # If the particular
594
+ # token is a refresh token and the authorization server supports the
595
+ # revocation of access tokens, then the authorization server SHOULD
596
+ # also invalidate all access tokens based on the same authorization
597
+ # grant
598
+ #
599
+ # we don't need to do anything here, as we revalidate existing tokens
600
+ end
601
+
602
+ # Response helpers
603
+
604
+ def redirect_response_error(error_code, redirect_url = request.referer || default_redirect)
605
+ if json_request?
606
+ throw_json_response_error(invalid_oauth_response_status, error_code)
607
+ else
608
+ redirect_url = URI.parse(redirect_url)
609
+ query_params = ["error=#{error_code}"]
610
+ if respond_to?(:"#{error_code}_message")
611
+ message = send(:"#{error_code}_message")
612
+ query_params << ["error_description=#{CGI.escape(message)}"]
613
+ end
614
+ query_params << redirect_url.query if redirect_url.query
615
+ redirect_url.query = query_params.join("&")
616
+ redirect(redirect_url.to_s)
617
+ end
618
+ end
619
+
620
+ def throw_json_response_error(status, error_code)
621
+ set_response_error_status(status)
622
+ payload = { "error" => error_code }
623
+ payload["error_description"] = send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message")
624
+ json_payload = if request.respond_to?(:convert_to_json)
625
+ request.send(:convert_to_json, payload)
626
+ else
627
+ JSON.dump(payload)
628
+ end
629
+ response["Content-Type"] ||= json_response_content_type
630
+ response["WWW-Authenticate"] = "Bearer" if status == 401
631
+ response.write(json_payload)
632
+ request.halt
633
+ end
634
+
635
+ def authorization_required
636
+ if json_request?
637
+ throw_json_response_error(authorization_required_error_status, "invalid_client")
638
+ else
639
+ set_redirect_error_flash(require_authorization_error_flash)
640
+ redirect(require_authorization_redirect)
641
+ end
642
+ end
643
+
644
+ def check_valid_scopes?
645
+ return false unless scopes
646
+
647
+ (scopes - oauth_application[oauth_applications_scopes_column].split(",")).empty?
648
+ end
649
+
650
+ def check_valid_redirect_uri?
651
+ redirect_uri == oauth_application[oauth_applications_redirect_uri_column]
652
+ end
653
+
654
+ ACCESS_TYPES = %w[offline online].freeze
655
+
656
+ def check_valid_access_type?
657
+ access_type = param("access_type")
658
+ access_type.empty? || ACCESS_TYPES.include?(access_type)
659
+ end
660
+
661
+ def check_valid_response_type?
662
+ response_type = param("response_type")
663
+
664
+ return true if response_type.empty? || response_type == "code"
665
+
666
+ return use_oauth_implicit_grant_type if response_type == "token"
667
+
668
+ false
669
+ end
670
+
671
+ # /oauth-token
672
+ route(:oauth_token) do |r|
673
+ throw_json_response_error(authorization_required_error_status, "invalid_client") unless logged_in?
674
+
675
+ # access-token
676
+ r.post do
677
+ catch_error do
678
+ validate_oauth_token_params
679
+
680
+ oauth_token = nil
681
+ transaction do
682
+ before_token
683
+ oauth_token = create_oauth_token
684
+ after_token
685
+ end
686
+
687
+ response.status = 200
688
+ response["Content-Type"] ||= json_response_content_type
689
+ json_response = {
690
+ "token" => oauth_token[:token],
691
+ "token_type" => oauth_token_type,
692
+ "expires_in" => oauth_token_expires_in
693
+ }
694
+
695
+ json_response["refresh_token"] = oauth_token[:refresh_token] if oauth_token[:refresh_token]
696
+
697
+ json_payload = if request.respond_to?(:convert_to_json)
698
+ request.send(:convert_to_json, json_response)
699
+ else
700
+ JSON.dump(json_response)
701
+ end
702
+ response.write(json_payload)
703
+ request.halt
704
+ end
705
+
706
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
707
+ end
708
+ end
709
+
710
+ # /oauth-revoke
711
+ route(:oauth_revoke) do |r|
712
+ require_account
713
+
714
+ # access-token
715
+ r.post do
716
+ catch_error do
717
+ validate_oauth_revoke_params
718
+
719
+ oauth_token = nil
720
+ transaction do
721
+ before_revoke
722
+ oauth_token = revoke_oauth_token
723
+ after_revoke
724
+ end
725
+
726
+ if json_request?
727
+ response.status = 200
728
+ response["Content-Type"] ||= json_response_content_type
729
+ json_response = {
730
+ "token" => oauth_token[:token],
731
+ "refresh_token" => oauth_token[:refresh_token],
732
+ "revoked_at" => oauth_token[:revoked_at]
733
+ }
734
+ json_payload = if request.respond_to?(:convert_to_json)
735
+ request.send(:convert_to_json, json_response)
736
+ else
737
+ JSON.dump(json_response)
738
+ end
739
+ response.write(json_payload)
740
+ request.halt
741
+ else
742
+ set_notice_flash revoke_oauth_token_notice_flash
743
+ redirect request.referer || "/"
744
+ end
745
+ end
746
+
747
+ throw_json_response_error(invalid_oauth_response_status, "invalid_request")
748
+ end
749
+ end
750
+
751
+ # /oauth-authorize
752
+ route(:oauth_authorize) do |r|
753
+ require_account
754
+
755
+ r.get do
756
+ validate_oauth_grant_params
757
+ authorize_view
758
+ end
759
+
760
+ r.post do
761
+ validate_oauth_grant_params
762
+
763
+ code = nil
764
+ query_params = []
765
+ fragment_params = []
766
+
767
+ transaction do
768
+ before_authorize
769
+ case param(response_type_param)
770
+ when "token"
771
+ redirect_response_error("invalid_request", redirect_uri) unless use_oauth_implicit_grant_type
772
+
773
+ create_params = {
774
+ oauth_tokens_account_id_column => account_id,
775
+ oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
776
+ oauth_tokens_scopes_column => scopes
777
+ }
778
+ oauth_token = generate_oauth_token(create_params)
779
+
780
+ fragment_params << ["access_token=#{oauth_token[:token]}"]
781
+ fragment_params << ["token_type=#{oauth_token_type}"]
782
+ fragment_params << ["expires_in=#{oauth_token_expires_in}"]
783
+ when "code", "", nil
784
+ code = create_oauth_grant
785
+ query_params << ["code=#{code}"]
786
+ else
787
+ redirect_response_error("invalid_request")
788
+ end
789
+ after_authorize
790
+ end
791
+
792
+ redirect_url = URI.parse(redirect_uri)
793
+ query_params << "state=#{state}" if state
794
+ query_params << redirect_url.query if redirect_url.query
795
+ redirect_url.query = query_params.join("&") unless query_params.empty?
796
+ redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
797
+
798
+ redirect(redirect_url.to_s)
799
+ end
800
+ end
801
+ end
802
+ end