omniauth_openid_connect 0.3.0 → 0.3.5

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.
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