doorkeeper-openid_connect 1.6.3 → 1.7.4

Sign up to get free protection for your applications and to get access to all the features.
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