doorkeeper-openid_connect 1.6.3 → 1.7.4

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -1
  3. data/README.md +11 -0
  4. data/app/controllers/doorkeeper/authorizations_controller.rb +17 -0
  5. data/app/controllers/doorkeeper/openid_connect/discovery_controller.rb +25 -19
  6. data/app/controllers/doorkeeper/openid_connect/userinfo_controller.rb +5 -1
  7. data/config/locales/en.yml +1 -0
  8. data/lib/doorkeeper/oauth/id_token_request.rb +8 -12
  9. data/lib/doorkeeper/oauth/id_token_response.rb +2 -0
  10. data/lib/doorkeeper/oauth/id_token_token_request.rb +2 -0
  11. data/lib/doorkeeper/oauth/id_token_token_response.rb +2 -0
  12. data/lib/doorkeeper/openid_connect.rb +26 -1
  13. data/lib/doorkeeper/openid_connect/claims/aggregated_claim.rb +2 -0
  14. data/lib/doorkeeper/openid_connect/claims/claim.rb +6 -4
  15. data/lib/doorkeeper/openid_connect/claims/distributed_claim.rb +2 -0
  16. data/lib/doorkeeper/openid_connect/claims/normal_claim.rb +2 -0
  17. data/lib/doorkeeper/openid_connect/claims_builder.rb +3 -1
  18. data/lib/doorkeeper/openid_connect/config.rb +20 -10
  19. data/lib/doorkeeper/openid_connect/engine.rb +2 -0
  20. data/lib/doorkeeper/openid_connect/errors.rb +4 -3
  21. data/lib/doorkeeper/openid_connect/helpers/controller.rb +58 -31
  22. data/lib/doorkeeper/openid_connect/id_token.rb +4 -2
  23. data/lib/doorkeeper/openid_connect/id_token_token.rb +2 -0
  24. data/lib/doorkeeper/openid_connect/oauth/authorization/code.rb +25 -8
  25. data/lib/doorkeeper/openid_connect/oauth/authorization_code_request.rb +4 -2
  26. data/lib/doorkeeper/openid_connect/oauth/password_access_token_request.rb +3 -1
  27. data/lib/doorkeeper/openid_connect/oauth/pre_authorization.rb +24 -3
  28. data/lib/doorkeeper/openid_connect/oauth/token_response.rb +3 -1
  29. data/lib/doorkeeper/openid_connect/orm/active_record.rb +2 -0
  30. data/lib/doorkeeper/openid_connect/orm/active_record/access_grant.rb +3 -1
  31. data/lib/doorkeeper/openid_connect/orm/active_record/request.rb +5 -3
  32. data/lib/doorkeeper/openid_connect/rails/routes.rb +3 -1
  33. data/lib/doorkeeper/openid_connect/rails/routes/mapper.rb +2 -0
  34. data/lib/doorkeeper/openid_connect/rails/routes/mapping.rb +2 -0
  35. data/lib/doorkeeper/openid_connect/response_mode.rb +30 -0
  36. data/lib/doorkeeper/openid_connect/response_types_config.rb +2 -2
  37. data/lib/doorkeeper/openid_connect/user_info.rb +2 -0
  38. data/lib/doorkeeper/openid_connect/version.rb +3 -1
  39. data/lib/doorkeeper/request/id_token.rb +2 -0
  40. data/lib/doorkeeper/request/id_token_token.rb +2 -0
  41. data/lib/generators/doorkeeper/openid_connect/install_generator.rb +4 -2
  42. data/lib/generators/doorkeeper/openid_connect/migration_generator.rb +3 -1
  43. data/lib/generators/doorkeeper/openid_connect/templates/initializer.rb +19 -5
  44. data/lib/generators/doorkeeper/openid_connect/templates/migration.rb.erb +3 -2
  45. metadata +29 -36
  46. data/.gitignore +0 -8
  47. data/.ruby-version +0 -1
  48. data/.travis.yml +0 -34
  49. data/CONTRIBUTING.md +0 -45
  50. data/Gemfile +0 -11
  51. data/Rakefile +0 -24
  52. data/bin/console +0 -9
  53. data/bin/setup +0 -8
  54. data/doorkeeper-openid_connect.gemspec +0 -30
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  class Engine < ::Rails::Engine
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module Errors
4
6
  class OpenidConnectError < StandardError
5
- def error_name
6
- self.class.name.demodulize.underscore
7
+ def type
8
+ self.class.name.demodulize.underscore.to_sym
7
9
  end
8
10
  end
9
11
 
@@ -24,7 +26,6 @@ module Doorkeeper
24
26
  class LoginRequired < OpenidConnectError; end
25
27
  class ConsentRequired < OpenidConnectError; end
26
28
  class InteractionRequired < OpenidConnectError; end
27
- class AccountSelectionRequired < OpenidConnectError; end
28
29
  end
29
30
  end
30
31
  end
@@ -1,9 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module Helpers
4
6
  module Controller
5
7
  private
6
8
 
9
+ # FIXME: remove after Doorkeeper will merge it
10
+ def current_resource_owner
11
+ return @current_resource_owner if defined?(@current_resource_owner)
12
+
13
+ super
14
+ end
15
+
7
16
  def authenticate_resource_owner!
8
17
  super.tap do |owner|
9
18
  next unless oidc_authorization_request?
@@ -11,14 +20,14 @@ module Doorkeeper
11
20
  handle_oidc_prompt_param!(owner)
12
21
  handle_oidc_max_age_param!(owner)
13
22
  end
14
- rescue Errors::OpenidConnectError => exception
15
- handle_oidc_error!(exception)
23
+ rescue Errors::OpenidConnectError => e
24
+ handle_oidc_error!(e)
16
25
  end
17
26
 
18
27
  def oidc_authorization_request?
19
28
  controller_path == Doorkeeper::Rails::Routes.mapping[:authorizations][:controllers] &&
20
29
  action_name == 'new' &&
21
- pre_auth.client &&
30
+ pre_auth.valid? &&
22
31
  pre_auth.scopes.include?('openid')
23
32
  end
24
33
 
@@ -29,14 +38,20 @@ module Doorkeeper
29
38
  # FIXME: workaround for Rails 5, see https://github.com/rails/rails/issues/25106
30
39
  @_response_body = nil
31
40
 
32
- error_response = if pre_auth.valid?
33
- ::Doorkeeper::OAuth::ErrorResponse.new(
34
- name: exception.error_name,
35
- state: params[:state],
36
- redirect_uri: params[:redirect_uri]
37
- )
38
- else
39
- pre_auth.error_response
41
+ error_response = if exception.type == :invalid_request
42
+ ::Doorkeeper::OAuth::InvalidRequestResponse.new(
43
+ name: exception.type,
44
+ state: params[:state],
45
+ redirect_uri: params[:redirect_uri],
46
+ response_on_fragment: pre_auth.response_on_fragment?,
47
+ )
48
+ else
49
+ ::Doorkeeper::OAuth::ErrorResponse.new(
50
+ name: exception.type,
51
+ state: params[:state],
52
+ redirect_uri: params[:redirect_uri],
53
+ response_on_fragment: pre_auth.response_on_fragment?,
54
+ )
40
55
  end
41
56
 
42
57
  response.headers.merge!(error_response.headers)
@@ -53,17 +68,16 @@ module Doorkeeper
53
68
 
54
69
  prompt_values.each do |prompt|
55
70
  case prompt
56
- when 'none' then
57
- raise Errors::InvalidRequest if (prompt_values - [ 'none' ]).any?
71
+ when 'none'
72
+ raise Errors::InvalidRequest if (prompt_values - ['none']).any?
58
73
  raise Errors::LoginRequired unless owner
59
- raise Errors::ConsentRequired if oidc_consent_required?(owner)
60
- when 'login' then
74
+ raise Errors::ConsentRequired if oidc_consent_required?
75
+ when 'login'
61
76
  reauthenticate_oidc_resource_owner(owner) if owner
62
- when 'consent' then
77
+ when 'consent'
63
78
  render :new
64
- when 'select_account' then
65
- # TODO: let the user implement this
66
- raise Errors::AccountSelectionRequired
79
+ when 'select_account'
80
+ select_account_for_oidc_resource_owner(owner)
67
81
  else
68
82
  raise Errors::InvalidRequest
69
83
  end
@@ -74,40 +88,53 @@ module Doorkeeper
74
88
  max_age = params[:max_age].to_i
75
89
  return unless max_age > 0 && owner
76
90
 
77
- auth_time = instance_exec owner,
91
+ auth_time = instance_exec(
92
+ owner,
78
93
  &Doorkeeper::OpenidConnect.configuration.auth_time_from_resource_owner
94
+ )
79
95
 
80
96
  if !auth_time || (Time.zone.now - auth_time) > max_age
81
97
  reauthenticate_oidc_resource_owner(owner)
82
98
  end
83
99
  end
84
100
 
85
- def reauthenticate_oidc_resource_owner(owner)
101
+ def return_without_oidc_prompt_param(prompt_value)
86
102
  return_to = URI.parse(request.path)
87
103
  return_to.query = request.query_parameters.tap do |params|
88
- params['prompt'] = params['prompt'].to_s.sub(/\blogin\s*\b/, '').strip
104
+ params['prompt'] = params['prompt'].to_s.sub(/\b#{prompt_value}\s*\b/, '').strip
89
105
  params.delete('prompt') if params['prompt'].blank?
90
106
  end.to_query
107
+ return_to.to_s
108
+ end
109
+
110
+ def reauthenticate_oidc_resource_owner(owner)
111
+ return_to = return_without_oidc_prompt_param('login')
91
112
 
92
- instance_exec owner, return_to.to_s,
113
+ instance_exec(
114
+ owner,
115
+ return_to,
93
116
  &Doorkeeper::OpenidConnect.configuration.reauthenticate_resource_owner
117
+ )
94
118
 
95
119
  raise Errors::LoginRequired unless performed?
96
120
  end
97
121
 
98
- def matching_tokens_for_oidc_resource_owner(owner)
99
- Doorkeeper::AccessToken.authorized_tokens_for(pre_auth.client.id, owner.id).select do |token|
100
- Doorkeeper::AccessToken.scopes_match?(token.scopes, pre_auth.scopes, pre_auth.client.scopes)
101
- end
122
+ def oidc_consent_required?
123
+ !skip_authorization? && !matching_token?
102
124
  end
103
125
 
104
- def oidc_consent_required?(owner)
105
- return false if skip_authorization?
106
- matching_tokens_for_oidc_resource_owner(owner).blank?
126
+ def select_account_for_oidc_resource_owner(owner)
127
+ return_to = return_without_oidc_prompt_param('select_account')
128
+
129
+ instance_exec(
130
+ owner,
131
+ return_to,
132
+ &Doorkeeper::OpenidConnect.configuration.select_account_for_resource_owner
133
+ )
107
134
  end
108
135
  end
109
136
  end
110
137
  end
111
138
 
112
- Helpers::Controller.send :prepend, OpenidConnect::Helpers::Controller
139
+ Helpers::Controller.prepend OpenidConnect::Helpers::Controller
113
140
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  class IdToken
@@ -9,7 +11,7 @@ module Doorkeeper
9
11
  @access_token = access_token
10
12
  @nonce = nonce
11
13
  @resource_owner = Doorkeeper::OpenidConnect.configuration.resource_owner_from_access_token.call(access_token)
12
- @issued_at = Time.now
14
+ @issued_at = Time.zone.now
13
15
  end
14
16
 
15
17
  def claims
@@ -46,7 +48,7 @@ module Doorkeeper
46
48
  end
47
49
 
48
50
  def audience
49
- @access_token.application.uid
51
+ @access_token.application.try(:uid)
50
52
  end
51
53
 
52
54
  def expiration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  class IdTokenToken < IdToken
@@ -1,22 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module OAuth
4
6
  module Authorization
5
7
  module Code
6
- def issue_token
7
- super.tap do |access_grant|
8
- if pre_auth.nonce.present?
9
- ::Doorkeeper::OpenidConnect::Request.create!(
10
- access_grant: access_grant,
11
- nonce: pre_auth.nonce
12
- )
8
+ if Doorkeeper::OAuth::Authorization::Code.method_defined?(:issue_token!)
9
+ def issue_token!
10
+ super.tap do |access_grant|
11
+ create_openid_request(access_grant) if pre_auth.nonce.present?
12
+ end
13
+ end
14
+
15
+ alias issue_token issue_token!
16
+ else
17
+ # FIXME: drop this after dropping support of Doorkeeper < 5.4
18
+ def issue_token
19
+ super.tap do |access_grant|
20
+ create_openid_request(access_grant) if pre_auth.nonce.present?
13
21
  end
14
22
  end
15
23
  end
24
+
25
+ private
26
+
27
+ def create_openid_request(access_grant)
28
+ ::Doorkeeper::OpenidConnect::Request.create!(
29
+ access_grant: access_grant,
30
+ nonce: pre_auth.nonce
31
+ )
32
+ end
16
33
  end
17
34
  end
18
35
  end
19
36
  end
20
37
 
21
- OAuth::Authorization::Code.send :prepend, OpenidConnect::OAuth::Authorization::Code
38
+ OAuth::Authorization::Code.prepend OpenidConnect::OAuth::Authorization::Code
22
39
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module OAuth
@@ -8,7 +10,7 @@ module Doorkeeper
8
10
  super
9
11
 
10
12
  nonce =
11
- if openid_request = grant.openid_request
13
+ if (openid_request = grant.openid_request)
12
14
  openid_request.destroy!
13
15
  openid_request.nonce
14
16
  end
@@ -20,5 +22,5 @@ module Doorkeeper
20
22
  end
21
23
  end
22
24
 
23
- OAuth::AuthorizationCodeRequest.send :prepend, OpenidConnect::OAuth::AuthorizationCodeRequest
25
+ OAuth::AuthorizationCodeRequest.prepend OpenidConnect::OAuth::AuthorizationCodeRequest
24
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module OAuth
@@ -20,5 +22,5 @@ module Doorkeeper
20
22
  end
21
23
  end
22
24
 
23
- OAuth::PasswordAccessTokenRequest.send :prepend, OpenidConnect::OAuth::PasswordAccessTokenRequest
25
+ OAuth::PasswordAccessTokenRequest.prepend OpenidConnect::OAuth::PasswordAccessTokenRequest
24
26
  end
@@ -1,16 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module OAuth
4
6
  module PreAuthorization
5
7
  attr_reader :nonce
6
8
 
7
- def initialize(server, client, attrs = {})
8
- super
9
+ def initialize(server, attrs = {}, resource_owner = nil)
10
+ if (Doorkeeper::VERSION::MAJOR >= 5 && Doorkeeper::VERSION::MINOR >= 4) ||
11
+ Doorkeeper::VERSION::MAJOR >= 6
12
+ super
13
+ else
14
+ super(server, attrs)
15
+ end
9
16
  @nonce = attrs[:nonce]
10
17
  end
18
+
19
+ # This method will be updated when doorkeeper move to version > 5.2.2
20
+ # TODO: delete this method and refactor response_on_fragment? method (below) when doorkeeper gem version constrains is > 5.2.2
21
+ def error_response
22
+ if error == :invalid_request
23
+ Doorkeeper::OAuth::InvalidRequestResponse.from_request(self, response_on_fragment: response_on_fragment?)
24
+ else
25
+ Doorkeeper::OAuth::ErrorResponse.from_request(self, response_on_fragment: response_on_fragment?)
26
+ end
27
+ end
28
+
29
+ def response_on_fragment?
30
+ Doorkeeper::OpenidConnect::ResponseMode.new(response_type).fragment?
31
+ end
11
32
  end
12
33
  end
13
34
  end
14
35
 
15
- OAuth::PreAuthorization.send :prepend, OpenidConnect::OAuth::PreAuthorization
36
+ OAuth::PreAuthorization.prepend OpenidConnect::OAuth::PreAuthorization
16
37
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module OAuth
@@ -19,5 +21,5 @@ module Doorkeeper
19
21
  end
20
22
  end
21
23
 
22
- OAuth::TokenResponse.send :prepend, OpenidConnect::OAuth::TokenResponse
24
+ OAuth::TokenResponse.prepend OpenidConnect::OAuth::TokenResponse
23
25
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/lazy_load_hooks'
2
4
 
3
5
  module Doorkeeper
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module AccessGrant
@@ -12,5 +14,5 @@ module Doorkeeper
12
14
  end
13
15
  end
14
16
 
15
- AccessGrant.send :prepend, OpenidConnect::AccessGrant
17
+ AccessGrant.prepend OpenidConnect::AccessGrant
16
18
  end
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
- class Request < ActiveRecord::Base
5
+ class Request < ApplicationRecord
4
6
  self.table_name = "#{table_name_prefix}oauth_openid_requests#{table_name_suffix}".to_sym
5
7
 
6
8
  validates :access_grant_id, :nonce, presence: true
7
9
  belongs_to :access_grant,
8
- class_name: 'Doorkeeper::AccessGrant',
9
- inverse_of: :openid_request
10
+ class_name: 'Doorkeeper::AccessGrant',
11
+ inverse_of: :openid_request
10
12
  end
11
13
  end
12
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'doorkeeper/openid_connect/rails/routes/mapping'
2
4
  require 'doorkeeper/openid_connect/rails/routes/mapper'
3
5
 
@@ -12,7 +14,7 @@ module Doorkeeper
12
14
  end
13
15
 
14
16
  def self.install!
15
- ActionDispatch::Routing::Mapper.send :include, Doorkeeper::OpenidConnect::Rails::Routes::Helper
17
+ ActionDispatch::Routing::Mapper.include Doorkeeper::OpenidConnect::Rails::Routes::Helper
16
18
  end
17
19
 
18
20
  attr_accessor :routes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module Rails
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OpenidConnect
3
5
  module Rails
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module OpenidConnect
5
+ class ResponseMode
6
+ attr_reader :type
7
+
8
+ def initialize(response_type)
9
+ @type = response_type
10
+ end
11
+
12
+ def fragment?
13
+ mode == 'fragment'
14
+ end
15
+
16
+ def query?
17
+ mode == 'query'
18
+ end
19
+
20
+ def mode
21
+ case type
22
+ when 'token', 'id_token', 'id_token token'
23
+ 'fragment'
24
+ else
25
+ 'query'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end