omniauth_openid_connect 0.3.0 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8916e5d71adfaa8dd6c64c168746671ba026fec61798ee85a9393c03b36c5bbd
4
- data.tar.gz: adfa760da9122452dc6cc486fb721ef1ce32a79910b8c16a32b6b153430b3e28
3
+ metadata.gz: 9c030571ea9bcbd861ebe4d8455282c0b1b34c17af77294660b1c0123ed976ab
4
+ data.tar.gz: 2f4b2a8cd026797260f36e358ac7678a6827486889f23aab325d08e0d390b649
5
5
  SHA512:
6
- metadata.gz: 877b098cd0f6a167cd6486a91de34668a6a8c7329ab0fb0eddcbb5914e548895db64a5e9e2ecf20308a90685f3baf29327b0a1e163d007b76e992908b398ea96
7
- data.tar.gz: 0ae557ebfc1319225171bba2fdae139a7c6a98338812bec2d3a5b50c58946f825511565c0a0d4c8d07a658a892ba2b652f78c12fd79a7fd1466c16925b85a3e5
6
+ metadata.gz: afdf6363a18b019939a88abc612be51b20780fdeae2f1f300ef5e9df5a1002a97812b17360939e1ba9bb0b3bf4e54471e3da0ec6de858ac2afa40df15fdfb23d
7
+ data.tar.gz: e449bd59e5e75cfcce12ae27e5072fb8e3fe821e969a510856bbad0ed2553252345826ed16e97ecdbe1405a8c4be275195f759e71aed99fd699bf9509bffa0f3
@@ -0,0 +1,59 @@
1
+ # Description: The name of the check that will be created.
2
+ # Valid Options: A reasonably sized string.
3
+ # Default: 'Rubocop Action'
4
+ check_name: 'Rubocop Results'
5
+
6
+ # Description: Versions required to run your RuboCop checks.
7
+ # Valid options: RuboCop and any RuboCop extension, by default the latest gem version will be used. You can explicitly state that
8
+ # (not required) or use a version number, like '1.5.1'.
9
+ # Default:
10
+ # versions:
11
+ # - rubocop: 'latest'
12
+ versions:
13
+ - rubocop
14
+ - rubocop-minitest
15
+ - rubocop-performance: '1.5.1'
16
+
17
+ # Description: Rubocop configuration file path relative to the workspace.
18
+ # Valid options: A valid file path inside of the workspace.
19
+ # Default: nil
20
+ # Note: This does not need to be filled out for Rubocop to still find your config.
21
+ # Resource: https://rubocop.readthedocs.io/en/stable/configuration/
22
+ rubocop_config_path: '.rubocop.yml'
23
+
24
+ # Run all cops enabled by configuration except this list.
25
+ # Valid options: list of valid cop(s) and/or departments.
26
+ # Default: nil
27
+ # Resource: https://rubocop.readthedocs.io/en/stable/cops/
28
+ # rubocop_excluded_cops:
29
+ # - 'Style/FrozenStringLiteralComment'
30
+
31
+ # Minimum severity for exit with error code
32
+ # Valid options: 'refactor', 'convention', 'warning', 'error', or 'fatal'.
33
+ # Default: 'warning'
34
+ # Resource: https://rubocop.readthedocs.io/en/stable/configuration/#severity
35
+ # rubocop_fail_level: 'warning'
36
+
37
+ # Whether or not to use --force-exclusion when building the rubocop command. Use this if you are only linting modified
38
+ # files and typically excluded files have been changed. For example, if you exclude db/schema.rb in your rubocop.yml
39
+ # but a change gets made, then with the check_scope config set to 'modified' rubocop will lint db/schema.rb. If you set
40
+ # this to true, rubocop will ignore it.
41
+ # Valid options: true || false
42
+ # Default: false
43
+
44
+ # Instead of installing gems from rubygems, we can run `bundle install` on your project,
45
+ # you would need to do this if you are using something like 'rubocop-github' or if you don't
46
+ # want to list out dependencies with the `versions` key.
47
+ # Valid options: true || false
48
+ # Default: false
49
+ # bundle: false
50
+
51
+ # The scope of code that Rubocop should lint. Use this if you only want to lint changed files. If this is not set
52
+ # or not equal to 'modified', Rubocop is run against the entire codebase. Note that this will not work on the master branch.
53
+ # Valid options: 'modified'
54
+ # Default: nil
55
+
56
+ # The base branch against which changes will be compared, if check_scope config is set to 'modified'.
57
+ # This setting is not used if check_scope != 'modified'.
58
+ # Valid options: 'origin/another_branch'
59
+ # Default: 'origin/master'
@@ -0,0 +1,17 @@
1
+ # Number of days of inactivity before an issue becomes stale
2
+ daysUntilStale: 60
3
+ # Number of days of inactivity before a stale issue is closed
4
+ daysUntilClose: 7
5
+ # Issues with these labels will never be considered stale
6
+ exemptLabels:
7
+ - pinned
8
+ - security
9
+ # Label to use when marking an issue as stale
10
+ staleLabel: wontfix
11
+ # Comment to post when marking an issue as stale. Set to `false` to disable
12
+ markComment: >
13
+ This issue has been automatically marked as stale because it has not had
14
+ recent activity. It will be closed if no further activity occurs. Thank you
15
+ for your contributions.
16
+ # Comment to post when closing a stale issue. Set to `false` to disable
17
+ closeComment: false
@@ -0,0 +1,22 @@
1
+ name: Rubocop check
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - "*"
7
+ push:
8
+ branches:
9
+ - master
10
+ jobs:
11
+ build:
12
+ name: RuboCop Action
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout Action
16
+ uses: actions/checkout@v1
17
+ - name: Rubocop Linter Action
18
+ uses: andrewmcodes/rubocop-linter-action@v3.2.0
19
+ with:
20
+ action_config_path: '.github/config/rubocop_linter_action.yml'
21
+ env:
22
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,58 @@
1
+ LineLength:
2
+ Description: 'Limit lines to 130 characters.'
3
+ Max: 130
4
+
5
+ Layout/SpaceInsideStringInterpolation:
6
+ Enabled: false
7
+
8
+ Layout/MultilineOperationIndentation:
9
+ EnforcedStyle: indented
10
+
11
+ StringLiterals:
12
+ EnforcedStyle: single_quotes
13
+
14
+ Style/TrailingCommaInArrayLiteral:
15
+ EnforcedStyleForMultiline: comma
16
+ Style/TrailingCommaInHashLiteral:
17
+ EnforcedStyleForMultiline: comma
18
+
19
+ Style/SafeNavigation:
20
+ Enabled: false
21
+
22
+ Style/EmptyMethod:
23
+ Description: 'Checks the formatting of empty method definitions.'
24
+ StyleGuide: '#no-single-line-methods'
25
+ Enabled: false
26
+
27
+ HashSyntax:
28
+ Description: "Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax\n{ :a => 1, :b => 2 }"
29
+ EnforcedStyle: ruby19
30
+ Enabled: true
31
+
32
+ RedundantBegin:
33
+ Enabled: true
34
+
35
+ Documentation:
36
+ Enabled: false
37
+
38
+ Metrics/AbcSize:
39
+ Max: 50
40
+
41
+ Metrics/CyclomaticComplexity:
42
+ Max: 50
43
+
44
+ Metrics/PerceivedComplexity:
45
+ Max: 15
46
+
47
+ Metrics/BlockLength:
48
+ Max: 40
49
+
50
+ Metrics/MethodLength:
51
+ Max: 45
52
+
53
+ AllCops:
54
+ Exclude:
55
+ - bin/**/*
56
+ - Rakefile
57
+ - config/**/*
58
+ - test/**/*
@@ -2,3 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 2.4
4
4
  - 2.5
5
+ - 2.6
6
+ - 2.7
7
+ - jruby-head
8
+ - ruby-head
@@ -1,3 +1,37 @@
1
+ # v0.3.5 (07.06.2020)
2
+
3
+ - bugfix: Info from decoded id_token is not exposed into `request.env['omniauth.auth']` [#61](https://github.com/m0n9oose/omniauth_openid_connect/pull/61)
4
+ - bugfix: NoMethodError (`undefined method 'count' for #<OpenIDConnect::ResponseObject::IdToken>`) [#60](https://github.com/m0n9oose/omniauth_openid_connect/pull/60)
5
+
6
+ # v0.3.4 (21.05.2020)
7
+
8
+ - Try to verify id_token when response_type is code [#44](https://github.com/m0n9oose/omniauth_openid_connect/pull/44)
9
+ - Provide more information on error [#49](https://github.com/m0n9oose/omniauth_openid_connect/pull/49)
10
+ - Update configuration documentation [#53](https://github.com/m0n9oose/omniauth_openid_connect/pull/53)
11
+ - Add documentation about the send_scope_to_token_endpoint config property [#52](https://github.com/m0n9oose/omniauth_openid_connect/pull/52)
12
+ - refactor: take uid_field from raw_attributes [#54](https://github.com/m0n9oose/omniauth_openid_connect/pull/54)
13
+ - chore(ci): add 2.7, ruby-head and jruby-head [#55](https://github.com/m0n9oose/omniauth_openid_connect/pull/55)
14
+
15
+ # v0.3.3 (09.11.2019)
16
+
17
+ - Pass `acr_values` to authorize url [#43](https://github.com/m0n9oose/omniauth_openid_connect/pull/43)
18
+ - Add raw info for id token [#42](https://github.com/m0n9oose/omniauth_openid_connect/pull/42)
19
+ - Fixed `id_token` verification when `id_token` is not used [#41](https://github.com/m0n9oose/omniauth_openid_connect/pull/41)
20
+ - Cast `response_type` to string when checking if it is set in params [#36](https://github.com/m0n9oose/omniauth_openid_connect/pull/36)
21
+ - Support both symbol and string version of `response_type` option [#35](https://github.com/m0n9oose/omniauth_openid_connect/pull/35)
22
+ - Fix gemspec homepage [#33](https://github.com/m0n9oose/omniauth_openid_connect/pull/33)
23
+ - Add support for `response_type` `id_token` [#32](https://github.com/m0n9oose/omniauth_openid_connect/pull/32)
24
+
25
+ # v0.3.2 (03.08.2019)
26
+
27
+ - Use response_mode in `authorize_uri` if the option is defined [#30](https://github.com/m0n9oose/omniauth_openid_connect/pull/30)
28
+ - Move verification of `id_token` to before accessing tokens [#28](https://github.com/m0n9oose/omniauth_openid_connect/pull/28)
29
+ - Update omniauth dependency [#26](https://github.com/m0n9oose/omniauth_openid_connect/pull/26)
30
+
31
+ # v0.3.1 (08.06.2019)
32
+
33
+ - Set default OmniAuth name to openid_connect [#23](https://github.com/m0n9oose/omniauth_openid_connect/pull/23)
34
+
1
35
  # v0.3.0 (27.04.2019)
2
36
 
3
37
  - RP-Initiated Logout phase [#5](https://github.com/m0n9oose/omniauth_openid_connect/pull/5)
data/Gemfile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
  gemspec
data/Guardfile CHANGED
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A sample Guardfile
2
4
  # More info at https://github.com/guard/guard#readme
3
5
 
4
6
  guard 'minitest' do
5
7
  # with Minitest::Unit
6
- watch(%r|^test/(.*)\/(.*)_test\.rb|)
7
- watch(%r|^lib/(.*)\.rb|) { |m| "test/lib/#{m[1]}_test.rb" }
8
- watch(%r|^test/test_helper\.rb|) { "test" }
8
+ watch(%r{^test/(.*)\/(.*)_test\.rb})
9
+ watch(%r{^lib/(.*)\.rb}) { |m| "test/lib/#{m[1]}_test.rb" }
10
+ watch(%r{^test/test_helper\.rb}) { 'test' }
9
11
  end
10
12
 
11
13
  guard :bundler do
data/README.md CHANGED
@@ -19,6 +19,10 @@ And then execute:
19
19
  Or install it yourself as:
20
20
 
21
21
  $ gem install omniauth_openid_connect
22
+
23
+ ## Supported Ruby Versions
24
+
25
+ OmniAuth::OpenIDConnect is tested under 2.4, 2.5, 2.6, 2.7
22
26
 
23
27
  ## Usage
24
28
 
@@ -40,7 +44,44 @@ config.omniauth :openid_connect, {
40
44
  }
41
45
  ```
42
46
 
43
- Configuration details:
47
+ ### Options Overview
48
+
49
+ | Field | Description | Required | Default | Example/Options |
50
+ |------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------------------------|-----------------------------------------------------|
51
+ | name | Arbitrary string to identify connection and identify it from other openid_connect providers | no | String: openid_connect | :my_idp |
52
+ | issuer | Root url for the authorization server | yes | | https://myprovider.com |
53
+ | discovery | Should OpenID discovery be used. This is recommended if the IDP provides a discovery endpoint. See client config for how to manually enter discovered values. | no | false | one of: true, false |
54
+ | client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic | "basic", "jwks" |
55
+ | scope | Which OpenID scopes to include (:openid is always required) | no | Array<sym> [:openid] | [:openid, :profile, :email] |
56
+ | response_type | Which OAuth2 response type to use with the authorization request | no | String: code | one of: 'code', 'id_token' |
57
+ | state | A value to be used for the OAuth2 state parameter on the authorization request. Can be a proc that generates a string. | no | Random 16 character string | Proc.new { SecureRandom.hex(32) } |
58
+ | response_mode | The response mode per [spec](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) | no | nil | one of: :query, :fragment, :form_post, :web_message |
59
+ | display | An optional parameter to the authorization request to determine how the authorization and consent page | no | nil | one of: :page, :popup, :touch, :wap |
60
+ | prompt | An optional parameter to the authrization request to determine what pages the user will be shown | no | nil | one of: :none, :login, :consent, :select_account |
61
+ | send_scope_to_token_endpoint | Should the scope parameter be sent to the authorization token endpoint? | no | true | one of: true, false |
62
+ | post_logout_redirect_uri | The logout redirect uri to use per the [session management draft](https://openid.net/specs/openid-connect-session-1_0.html) | no | empty | https://myapp.com/logout/callback |
63
+ | uid_field | The field of the user info response to be used as a unique id | no | 'sub' | "sub", "preferred_username" |
64
+ | client_options | A hash of client options detailed in its own section | yes | | |
65
+
66
+ ### Client Config Options
67
+
68
+ These are the configuration options for the client_options hash of the configuration.
69
+
70
+ | Field | Description | Default | Replaced by discovery? |
71
+ |------------------------|-----------------------------------------------------------------|------------|------------------------|
72
+ | identifier | The OAuth2 client_id | | |
73
+ | secret | The OAuth2 client secret | | |
74
+ | redirect_uri | The OAuth2 authorization callback url in your app | | |
75
+ | scheme | The http scheme to use | https | |
76
+ | host | The host of the authorization server | nil | |
77
+ | port | The port for the authorization server | 443 | |
78
+ | authorization_endpoint | The authorize endpoint on the authorization server | /authorize | yes |
79
+ | token_endpoint | The token endpoint on the authorization server | /token | yes |
80
+ | userinfo_endpoint | The user info endpoint on the authorization server | /userinfo | yes |
81
+ | jwks_uri | The jwks_uri on the authorization server | /jwk | yes |
82
+ | end_session_endpoint | The url to call to log the user out at the authorization server | nil | yes |
83
+
84
+ ### Additional Configuration Notes
44
85
  * `name` is arbitrary, I recommend using the name of your provider. The name
45
86
  configuration exists because you could be using multiple OpenID Connect
46
87
  providers in a single app.
@@ -48,11 +89,9 @@ Configuration details:
48
89
  **NOTE**: if you use this gem with Devise you should use `:openid_connect` name,
49
90
  or Devise would route to 'users/auth/:provider' rather than 'users/auth/openid_connect'
50
91
 
51
- * Although `response_type` is an available option, currently, only `:code`
52
- is valid. There are plans to bring in implicit flow and hybrid flow at some
53
- point, but it hasn't come up yet for me. Those flows aren't best practive for
54
- server side web apps anyway and are designed more for native/mobile apps.
55
- * If you want to pass `state` paramete by yourself. You can set Proc Object.
92
+ * `response_type` tells the authorization server which grant type the application wants to use,
93
+ currently, only `:code` (Authorization Code grant) and `:id_token` (Implicit grant) are valid.
94
+ * If you want to pass `state` paramete by yourself. You can set Proc Object.
56
95
  e.g. `state: Proc.new { SecureRandom.hex(32) }`
57
96
  * `nonce` is optional. If don't want to pass "nonce" parameter to provider, You should specify
58
97
  `false` to `send_nonce` option. (default true)
@@ -60,14 +99,19 @@ Configuration details:
60
99
  `:client_auth_method` option, automatically set `:basic`.
61
100
  * Use "OpenID Connect Discovery", You should specify `true` to `discovery` option. (default false)
62
101
  * In "OpenID Connect Discovery", generally provider should have Webfinger endpoint.
63
- If provider does not have Webfinger endpoint, You can specify "Issuer" to option.
64
- e.g. `issuer: "https://myprovider.com"`
102
+ If provider does not have Webfinger endpoint, You can specify "Issuer" to option.
103
+ e.g. `issuer: "https://myprovider.com"`
65
104
  It means to get configuration from "https://myprovider.com/.well-known/openid-configuration".
66
105
  * The uid is by default using the `sub` value from the `user_info` response,
67
106
  which in some applications is not the expected value. To avoid such limitations, the uid label can be
68
107
  configured by providing the omniauth `uid_field` option to a different label (i.e. `preferred_username`)
69
108
  that appears in the `user_info` details.
70
109
  * The `issuer` property should exactly match the provider's issuer link.
110
+ * The `response_mode` option is optional and specifies how the result of the authorization request is formatted.
111
+ * Some OpenID Connect providers require the `scope` attribute in requests to the token endpoint, even if
112
+ this is not in the protocol specifications. In those cases, the `send_scope_to_token_endpoint`
113
+ property can be used to add the attribute to the token request. Initial value is `true`, which means that the
114
+ scope attribute is included by default.
71
115
 
72
116
  For the full low down on OpenID Connect, please check out
73
117
  [the spec](http://openid.net/specs/openid-connect-core-1_0.html).
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'omniauth/openid_connect/errors'
2
4
  require 'omniauth/openid_connect/version'
3
5
  require 'omniauth/strategies/openid_connect'
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OmniAuth
2
4
  module OpenIDConnect
3
5
  class Error < RuntimeError; end
4
6
  class MissingCodeError < Error; end
7
+ class MissingIdTokenError < Error; end
5
8
  end
6
9
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OmniAuth
2
4
  module OpenIDConnect
3
- VERSION = '0.3.0'
5
+ VERSION = '0.3.5'
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'addressable/uri'
2
4
  require 'timeout'
3
5
  require 'net/http'
@@ -12,32 +14,37 @@ module OmniAuth
12
14
  include OmniAuth::Strategy
13
15
  extend Forwardable
14
16
 
17
+ RESPONSE_TYPE_EXCEPTIONS = {
18
+ 'id_token' => { exception_class: OmniAuth::OpenIDConnect::MissingIdTokenError, key: :missing_id_token }.freeze,
19
+ 'code' => { exception_class: OmniAuth::OpenIDConnect::MissingCodeError, key: :missing_code }.freeze,
20
+ }.freeze
21
+
15
22
  def_delegator :request, :params
16
23
 
17
- option :client_options, {
18
- identifier: nil,
19
- secret: nil,
20
- redirect_uri: nil,
21
- scheme: 'https',
22
- host: nil,
23
- port: 443,
24
- authorization_endpoint: '/authorize',
25
- token_endpoint: '/token',
26
- userinfo_endpoint: '/userinfo',
27
- jwks_uri: '/jwk',
28
- end_session_endpoint: nil
29
- }
24
+ option :name, 'openid_connect'
25
+ option(:client_options, identifier: nil,
26
+ secret: nil,
27
+ redirect_uri: nil,
28
+ scheme: 'https',
29
+ host: nil,
30
+ port: 443,
31
+ authorization_endpoint: '/authorize',
32
+ token_endpoint: '/token',
33
+ userinfo_endpoint: '/userinfo',
34
+ jwks_uri: '/jwk',
35
+ end_session_endpoint: nil)
36
+
30
37
  option :issuer
31
38
  option :discovery, false
32
39
  option :client_signing_alg
33
40
  option :client_jwk_signing_key
34
41
  option :client_x509_signing_key
35
42
  option :scope, [:openid]
36
- option :response_type, "code"
43
+ option :response_type, 'code' # ['code', 'id_token']
37
44
  option :state
38
- option :response_mode
39
- option :display, nil #, [:page, :popup, :touch, :wap]
40
- option :prompt, nil #, [:none, :login, :consent, :select_account]
45
+ option :response_mode # [:query, :fragment, :form_post, :web_message]
46
+ option :display, nil # [:page, :popup, :touch, :wap]
47
+ option :prompt, nil # [:none, :login, :consent, :select_account]
41
48
  option :hd, nil
42
49
  option :max_age
43
50
  option :ui_locales
@@ -47,13 +54,11 @@ module OmniAuth
47
54
  option :send_scope_to_token_endpoint, true
48
55
  option :client_auth_method
49
56
  option :post_logout_redirect_uri
57
+ option :extra_authorize_params, {}
50
58
  option :uid_field, 'sub'
51
59
 
52
60
  def uid
53
- user_info.public_send(options.uid_field.to_s)
54
- rescue NoMethodError
55
- log :warn, "User sub:#{user_info.sub} missing info field: #{options.uid_field}"
56
- user_info.sub
61
+ user_info.raw_attributes[options.uid_field.to_sym] || user_info.sub
57
62
  end
58
63
 
59
64
  info do
@@ -66,7 +71,7 @@ module OmniAuth
66
71
  gender: user_info.gender,
67
72
  image: user_info.picture,
68
73
  phone: user_info.phone_number,
69
- urls: { website: user_info.website }
74
+ urls: { website: user_info.website },
70
75
  }
71
76
  end
72
77
 
@@ -80,7 +85,7 @@ module OmniAuth
80
85
  token: access_token.access_token,
81
86
  refresh_token: access_token.refresh_token,
82
87
  expires_in: access_token.expires_in,
83
- scope: access_token.scope
88
+ scope: access_token.scope,
84
89
  }
85
90
  end
86
91
 
@@ -93,29 +98,36 @@ module OmniAuth
93
98
  end
94
99
 
95
100
  def request_phase
96
- options.issuer = issuer if options.issuer.nil? || options.issuer.empty?
101
+ options.issuer = issuer if options.issuer.to_s.empty?
97
102
  discover!
98
103
  redirect authorize_uri
99
104
  end
100
105
 
101
106
  def callback_phase
102
107
  error = params['error_reason'] || params['error']
103
- if error
104
- raise CallbackError.new(params['error'], params['error_description'] || params['error_reason'], params['error_uri'])
105
- elsif params['state'].to_s.empty? || params['state'] != stored_state
106
- raise CallbackError, 'Invalid state parameter'
107
- elsif !params['code']
108
- return fail!(:missing_code, OmniAuth::OpenIDConnect::MissingCodeError.new(params['error']))
109
- else
110
- options.issuer = issuer if options.issuer.nil? || options.issuer.empty?
111
- discover!
112
- client.redirect_uri = redirect_uri
113
- client.authorization_code = authorization_code
114
- access_token
115
- super
116
- end
117
- rescue CallbackError, ::Rack::OAuth2::Client::Error => e
118
- fail!(:invalid_credentials, e)
108
+ error_description = params['error_description'] || params['error_reason']
109
+ invalid_state = params['state'].to_s.empty? || params['state'] != stored_state
110
+
111
+ raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error
112
+ raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state
113
+
114
+ return unless valid_response_type?
115
+
116
+ options.issuer = issuer if options.issuer.nil? || options.issuer.empty?
117
+
118
+ verify_id_token!(params['id_token']) if configured_response_type == 'id_token'
119
+ discover!
120
+ client.redirect_uri = redirect_uri
121
+
122
+ return id_token_callback_phase if configured_response_type == 'id_token'
123
+
124
+ client.authorization_code = authorization_code
125
+ access_token
126
+ super
127
+ rescue CallbackError => e
128
+ fail!(e.error, e)
129
+ rescue ::Rack::OAuth2::Client::Error => e
130
+ fail!(e.response[:error], e)
119
131
  rescue ::Timeout::Error, ::Errno::ETIMEDOUT => e
120
132
  fail!(:timeout, e)
121
133
  rescue ::SocketError => e
@@ -124,7 +136,7 @@ module OmniAuth
124
136
 
125
137
  def other_phase
126
138
  if logout_path_pattern.match?(current_path)
127
- options.issuer = issuer if options.issuer.nil? || options.issuer.empty?
139
+ options.issuer = issuer if options.issuer.to_s.empty?
128
140
  discover!
129
141
  return redirect(end_session_uri) if end_session_uri
130
142
  end
@@ -137,6 +149,7 @@ module OmniAuth
137
149
 
138
150
  def end_session_uri
139
151
  return unless end_session_endpoint_is_valid?
152
+
140
153
  end_session_uri = URI(client_options.end_session_endpoint)
141
154
  end_session_uri.query = encoded_post_logout_redirect_uri
142
155
  end_session_uri.to_s
@@ -146,6 +159,7 @@ module OmniAuth
146
159
  client.redirect_uri = redirect_uri
147
160
  opts = {
148
161
  response_type: options.response_type,
162
+ response_mode: options.response_mode,
149
163
  scope: options.scope,
150
164
  state: new_state,
151
165
  login_hint: params['login_hint'],
@@ -154,12 +168,17 @@ module OmniAuth
154
168
  prompt: options.prompt,
155
169
  nonce: (new_nonce if options.send_nonce),
156
170
  hd: options.hd,
171
+ acr_values: options.acr_values,
157
172
  }
158
- client.authorization_uri(opts.reject { |k, v| v.nil? })
173
+
174
+ opts.merge!(options.extra_authorize_params) unless options.extra_authorize_params.empty?
175
+
176
+ client.authorization_uri(opts.reject { |_k, v| v.nil? })
159
177
  end
160
178
 
161
179
  def public_key
162
180
  return config.jwks if options.discovery
181
+
163
182
  key_or_secret
164
183
  end
165
184
 
@@ -173,6 +192,7 @@ module OmniAuth
173
192
 
174
193
  def discover!
175
194
  return unless options.discovery
195
+
176
196
  client_options.authorization_endpoint = config.authorization_endpoint
177
197
  client_options.token_endpoint = config.token_endpoint
178
198
  client_options.userinfo_endpoint = config.userinfo_endpoint
@@ -181,23 +201,28 @@ module OmniAuth
181
201
  end
182
202
 
183
203
  def user_info
184
- @user_info ||= access_token.userinfo!
204
+ return @user_info if @user_info
205
+
206
+ if access_token.id_token
207
+ decoded = decode_id_token(access_token.id_token).raw_attributes
208
+
209
+ @user_info = ::OpenIDConnect::ResponseObject::UserInfo.new access_token.userinfo!.raw_attributes.merge(decoded)
210
+ else
211
+ @user_info = access_token.userinfo!
212
+ end
185
213
  end
186
214
 
187
215
  def access_token
188
- @access_token ||= begin
189
- _access_token = client.access_token!(
190
- scope: (options.scope if options.send_scope_to_token_endpoint),
191
- client_auth_method: options.client_auth_method
192
- )
193
- _id_token = decode_id_token _access_token.id_token
194
- _id_token.verify!(
195
- issuer: options.issuer,
196
- client_id: client_options.identifier,
197
- nonce: stored_nonce
198
- )
199
- _access_token
200
- end
216
+ return @access_token if @access_token
217
+
218
+ @access_token = client.access_token!(
219
+ scope: (options.scope if options.send_scope_to_token_endpoint),
220
+ client_auth_method: options.client_auth_method
221
+ )
222
+
223
+ verify_id_token!(@access_token.id_token) if configured_response_type == 'code'
224
+
225
+ @access_token
201
226
  end
202
227
 
203
228
  def decode_id_token(id_token)
@@ -233,20 +258,20 @@ module OmniAuth
233
258
 
234
259
  def session
235
260
  return {} if @env.nil?
261
+
236
262
  super
237
263
  end
238
264
 
239
265
  def key_or_secret
240
266
  case options.client_signing_alg
241
267
  when :HS256, :HS384, :HS512
242
- return client_options.secret
268
+ client_options.secret
243
269
  when :RS256, :RS384, :RS512
244
270
  if options.client_jwk_signing_key
245
- return parse_jwk_key(options.client_jwk_signing_key)
271
+ parse_jwk_key(options.client_jwk_signing_key)
246
272
  elsif options.client_x509_signing_key
247
- return parse_x509_key(options.client_x509_signing_key)
273
+ parse_x509_key(options.client_x509_signing_key)
248
274
  end
249
- else
250
275
  end
251
276
  end
252
277
 
@@ -256,24 +281,24 @@ module OmniAuth
256
281
 
257
282
  def parse_jwk_key(key)
258
283
  json = JSON.parse(key)
259
- if json.has_key?('keys')
260
- JSON::JWK::Set.new json['keys']
261
- else
262
- JSON::JWK.new json
263
- end
284
+ return JSON::JWK::Set.new(json['keys']) if json.key?('keys')
285
+
286
+ JSON::JWK.new(json)
264
287
  end
265
288
 
266
289
  def decode(str)
267
- UrlSafeBase64.decode64(str).unpack('B*').first.to_i(2).to_s
290
+ UrlSafeBase64.decode64(str).unpack1('B*').to_i(2).to_s
268
291
  end
269
292
 
270
293
  def redirect_uri
271
294
  return client_options.redirect_uri unless params['redirect_uri']
295
+
272
296
  "#{ client_options.redirect_uri }?redirect_uri=#{ CGI.escape(params['redirect_uri']) }"
273
297
  end
274
298
 
275
299
  def encoded_post_logout_redirect_uri
276
300
  return unless options.post_logout_redirect_uri
301
+
277
302
  URI.encode_www_form(
278
303
  post_logout_redirect_uri: options.post_logout_redirect_uri
279
304
  )
@@ -288,13 +313,45 @@ module OmniAuth
288
313
  @logout_path_pattern ||= %r{\A#{Regexp.quote(request_path)}(/logout)}
289
314
  end
290
315
 
316
+ def id_token_callback_phase
317
+ user_data = decode_id_token(params['id_token']).raw_attributes
318
+ env['omniauth.auth'] = AuthHash.new(
319
+ provider: name,
320
+ uid: user_data['sub'],
321
+ info: { name: user_data['name'], email: user_data['email'] },
322
+ extra: { raw_info: user_data }
323
+ )
324
+ call_app!
325
+ end
326
+
327
+ def valid_response_type?
328
+ return true if params.key?(configured_response_type)
329
+
330
+ error_attrs = RESPONSE_TYPE_EXCEPTIONS[configured_response_type]
331
+ fail!(error_attrs[:key], error_attrs[:exception_class].new(params['error']))
332
+
333
+ false
334
+ end
335
+
336
+ def configured_response_type
337
+ @configured_response_type ||= options.response_type.to_s
338
+ end
339
+
340
+ def verify_id_token!(id_token)
341
+ return unless id_token
342
+
343
+ decode_id_token(id_token).verify!(issuer: options.issuer,
344
+ client_id: client_options.identifier,
345
+ nonce: stored_nonce)
346
+ end
347
+
291
348
  class CallbackError < StandardError
292
349
  attr_accessor :error, :error_reason, :error_uri
293
350
 
294
- def initialize(error, error_reason=nil, error_uri=nil)
295
- self.error = error
296
- self.error_reason = error_reason
297
- self.error_uri = error_uri
351
+ def initialize(data)
352
+ self.error = data[:error]
353
+ self.error_reason = data[:reason]
354
+ self.error_uri = data[:uri]
298
355
  end
299
356
 
300
357
  def message