omniauth_openid_connect 0.4.0 → 0.6.0

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: 7b5060fd9a0536e2740b340c56459f76b628912dea1ad54da415c2453921e6e3
4
- data.tar.gz: ada9bd68c69837538cd485ce0595525b2109feb0f79611a3ceb06fa1bf7eed96
3
+ metadata.gz: d5ccd20bf84d71220597692301b64b1c3b0f8bf19faf1d788a6f1185bf42bfcb
4
+ data.tar.gz: 9336782a2449bfdefc07521f79bcd8030bf4cc2a6f094a01d9613dbcc21727f9
5
5
  SHA512:
6
- metadata.gz: 24c424fb608e45beba966b6914accdfad6e5af277c9428c94d3aec4ab3f4c010bfa66117a42d6ad1f57de9ea6d6d3ff99eaa5a33bdcbc753d1b7db568f3e870b
7
- data.tar.gz: f2ebfcd68b35425cc656ecaa58151b962d0bc96a4023f1bfcf312e363e2e0878c298bc90d7fc70104220b71cdbc77346b3f64c4653222b8df1110a12bb249dd0
6
+ metadata.gz: 07b23acf7a852a2b5a1d7a4b6852108562fc7ce2c7f00ead2fe91989f00d736643c8e9f510ff8029956a0e55006d70b4671006ed4d2ca8a5c9a74f641a0c0e60
7
+ data.tar.gz: e22f6c174179f08acf046f05a50da341a01d576353a929fca9bd4c49f6d5ceaa5a761ce7ddaa4cfa6dae7470923bd0a646d634f6b7f563dca93df436c9b60a05
@@ -9,12 +9,12 @@ on:
9
9
  types: [opened, synchronize, reopened]
10
10
 
11
11
  jobs:
12
- base:
12
+ test:
13
13
  runs-on: ubuntu-latest
14
14
  strategy:
15
15
  fail-fast: false
16
16
  matrix:
17
- ruby: ["2.5", "2.6", "2.7", "3.0"]
17
+ ruby: ["2.5", "2.6", "2.7", "3.0", "3.1"]
18
18
  name: Ruby ${{ matrix.ruby }}
19
19
 
20
20
  steps:
@@ -29,3 +29,35 @@ jobs:
29
29
 
30
30
  - name: Run tests
31
31
  run: bundle exec rake
32
+
33
+ - name: Coveralls Parallel
34
+ uses: coverallsapp/github-action@master
35
+ with:
36
+ github-token: ${{ secrets.github_token }}
37
+ flag-name: ruby-${{ matrix.ruby }}
38
+ parallel: true
39
+
40
+ finish:
41
+ needs: test
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - name: Coveralls Finished
45
+ uses: coverallsapp/github-action@master
46
+ with:
47
+ github-token: ${{ secrets.github_token }}
48
+ parallel-finished: true
49
+
50
+ rubocop:
51
+ runs-on: ubuntu-latest
52
+ steps:
53
+ - name: Checkout code
54
+ uses: actions/checkout@v2
55
+
56
+ - name: Setup Ruby
57
+ uses: ruby/setup-ruby@v1
58
+ with:
59
+ bundler-cache: true
60
+ ruby-version: "2.7"
61
+
62
+ - name: rubocop
63
+ run: bundle exec rubocop --parallel
data/.rubocop.yml CHANGED
@@ -58,7 +58,4 @@ Metrics/MethodLength:
58
58
 
59
59
  AllCops:
60
60
  Exclude:
61
- - bin/**/*
62
- - Rakefile
63
- - config/**/*
64
- - test/**/*
61
+ - vendor/bundle/**/*
data/CHANGELOG.md CHANGED
@@ -1,4 +1,16 @@
1
- # v0.4.0 (02.05.2021)
1
+ # v0.6.0 (21.01.2023)
2
+
3
+ - Support verification of HS256-signed JWTs (https://github.com/omniauth/omniauth_openid_connect/pull/134)
4
+
5
+ # v0.5.0 (26.12.2022)
6
+
7
+ - Support the "nonce" parameter forwarding without a session [#130](https://github.com/omniauth/omniauth_openid_connect/pull/130)
8
+ - Fetch key from JWKS URI if available [#133](https://github.com/omniauth/omniauth_openid_connect/pull/133)
9
+ - Make the state parameter verification optional [#122](https://github.com/omniauth/omniauth_openid_connect/pull/122)
10
+ - Add email_verified claim in user info [#131](https://github.com/omniauth/omniauth_openid_connect/pull/131)
11
+ - Add PKCE verification support [#128](https://github.com/omniauth/omniauth_openid_connect/pull/128)
12
+
13
+ # v0.4.0 (06.02.2022)
2
14
 
3
15
  - Support dynamic parameters to the authorize URI [#90](https://github.com/omniauth/omniauth_openid_connect/pull/90)
4
16
  - Upgrade Faker and replace Travis with Github Actions [#102](https://github.com/omniauth/omniauth_openid_connect/pull/102)
data/Gemfile CHANGED
@@ -2,3 +2,9 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
  gemspec
5
+
6
+ if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('3.1')
7
+ gem 'net-imap', require: false
8
+ gem 'net-pop', require: false
9
+ gem 'net-smtp', require: false
10
+ end
data/README.md CHANGED
@@ -1,12 +1,3 @@
1
- # Maintainers Wanted
2
-
3
- This project is looking for maintainers.
4
-
5
- Due to lack of using this gem in my projects I have no time to keep it alive.
6
- Feel free to open an issue if you interested.
7
-
8
- _This project is built and maintained 100% voluntarily._
9
-
10
1
  # OmniAuth::OpenIDConnect
11
2
 
12
3
  Originally was [omniauth-openid-connect](https://github.com/jjbohn/omniauth-openid-connect)
@@ -14,6 +5,7 @@ Originally was [omniauth-openid-connect](https://github.com/jjbohn/omniauth-open
14
5
  I've forked this repository and launch as separate gem because maintaining of original was dropped.
15
6
 
16
7
  [![Build Status](https://github.com/omniauth/omniauth_openid_connect/actions/workflows/main.yml/badge.svg)](https://github.com/omniauth/omniauth_openid_connect/actions/workflows/main.yml)
8
+ [![Coverage Status](https://coveralls.io/repos/github/omniauth/omniauth_openid_connect/badge.svg)](https://coveralls.io/github/omniauth/omniauth_openid_connect)
17
9
 
18
10
  ## Installation
19
11
 
@@ -31,7 +23,7 @@ Or install it yourself as:
31
23
 
32
24
  ## Supported Ruby Versions
33
25
 
34
- OmniAuth::OpenIDConnect is tested under 2.4, 2.5, 2.6, 2.7
26
+ OmniAuth::OpenIDConnect is tested under 2.5, 2.6, 2.7, 3.0, 3.1
35
27
 
36
28
  ## Usage
37
29
 
@@ -55,24 +47,29 @@ config.omniauth :openid_connect, {
55
47
 
56
48
  ### Options Overview
57
49
 
58
- | Field | Description | Required | Default | Example/Options |
59
- |------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------------------------|-----------------------------------------------------|
60
- | name | Arbitrary string to identify connection and identify it from other openid_connect providers | no | String: openid_connect | :my_idp |
61
- | issuer | Root url for the authorization server | yes | | https://myprovider.com |
62
- | 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 |
63
- | client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic | "basic", "jwks" |
64
- | scope | Which OpenID scopes to include (:openid is always required) | no | Array<sym> [:openid] | [:openid, :profile, :email] |
65
- | response_type | Which OAuth2 response type to use with the authorization request | no | String: code | one of: 'code', 'id_token' |
66
- | 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) } |
67
- | 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 |
68
- | display | An optional parameter to the authorization request to determine how the authorization and consent page | no | nil | one of: :page, :popup, :touch, :wap |
69
- | 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 |
70
- | send_scope_to_token_endpoint | Should the scope parameter be sent to the authorization token endpoint? | no | true | one of: true, false |
71
- | 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 |
72
- | uid_field | The field of the user info response to be used as a unique id | no | 'sub' | "sub", "preferred_username" |
73
- | extra_authorize_params | A hash of extra fixed parameters that will be merged to the authorization request | no | Hash | {"tenant" => "common"} |
74
- | allow_authorize_params | A list of allowed dynamic parameters that will be merged to the authorization request | no | Array | [:screen_name] |
75
- | client_options | A hash of client options detailed in its own section | yes | | |
50
+ | Field | Description | Required | Default | Example/Options |
51
+ |------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------------------|-----------------------------------------------------|
52
+ | name | Arbitrary string to identify connection and identify it from other openid_connect providers | no | String: openid_connect | :my_idp |
53
+ | issuer | Root url for the authorization server | yes | | https://myprovider.com |
54
+ | 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 |
55
+ | client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic | "basic", "jwks" |
56
+ | scope | Which OpenID scopes to include (:openid is always required) | no | Array<sym> [:openid] | [:openid, :profile, :email] |
57
+ | response_type | Which OAuth2 response type to use with the authorization request | no | String: code | one of: 'code', 'id_token' |
58
+ | 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) } |
59
+ | require_state | Should state param be verified - this is recommended, not required by the OIDC specification | no | true | false |
60
+ | 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 |
61
+ | display | An optional parameter to the authorization request to determine how the authorization and consent page | no | nil | one of: :page, :popup, :touch, :wap |
62
+ | 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 |
63
+ | send_scope_to_token_endpoint | Should the scope parameter be sent to the authorization token endpoint? | no | true | one of: true, false |
64
+ | 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 |
65
+ | uid_field | The field of the user info response to be used as a unique id | no | 'sub' | "sub", "preferred_username" |
66
+ | extra_authorize_params | A hash of extra fixed parameters that will be merged to the authorization request | no | Hash | {"tenant" => "common"} |
67
+ | allow_authorize_params | A list of allowed dynamic parameters that will be merged to the authorization request | no | Array | [:screen_name] |
68
+ | pkce | Enable [PKCE flow](https://oauth.net/2/pkce/) | no | false | one of: true, false |
69
+ | pkce_verifier | Specify a custom PKCE verifier code. | no | A random 128-char string | Proc.new { SecureRandom.hex(64) } |
70
+ | pkce_options | Specify a custom implementation of the PKCE code challenge/method. | no | SHA256(code_challenge) in hex | Proc to customise the code challenge generation |
71
+ | client_options | A hash of client options detailed in its own section | yes | | |
72
+ | jwt_secret_base64 | For HMAC with SHA2 (e.g. HS256) signing algorithms, specify the base64-encoded secret used to sign the JWT token. Defaults to the OAuth2 client secret if not specified. | no | client_options.secret | "bXlzZWNyZXQ=\n"
76
73
 
77
74
  ### Client Config Options
78
75
 
@@ -102,7 +99,7 @@ These are the configuration options for the client_options hash of the configura
102
99
 
103
100
  * `response_type` tells the authorization server which grant type the application wants to use,
104
101
  currently, only `:code` (Authorization Code grant) and `:id_token` (Implicit grant) are valid.
105
- * If you want to pass `state` paramete by yourself. You can set Proc Object.
102
+ * If you want to pass `state` parameter by yourself. You can set Proc Object.
106
103
  e.g. `state: Proc.new { SecureRandom.hex(32) }`
107
104
  * `nonce` is optional. If don't want to pass "nonce" parameter to provider, You should specify
108
105
  `false` to `send_nonce` option. (default true)
@@ -124,6 +121,11 @@ These are the configuration options for the client_options hash of the configura
124
121
  property can be used to add the attribute to the token request. Initial value is `true`, which means that the
125
122
  scope attribute is included by default.
126
123
 
124
+ ## Additional notes
125
+ * In some cases, you may want to go straight to the callback phase - e.g. when requested by a stateless client, like a mobile app.
126
+ In such example, the session is empty, so you have to forward certain parameters received from the client.
127
+ Currently supported ones are `code_verifier` and `nonce` - simply provide them as the `/callback` request parameters.
128
+
127
129
  For the full low down on OpenID Connect, please check out
128
130
  [the spec](http://openid.net/specs/openid-connect-core-1_0.html).
129
131
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAuth
4
4
  module OpenIDConnect
5
- VERSION = '0.4.0'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'addressable/uri'
3
+ require 'base64'
4
4
  require 'timeout'
5
5
  require 'net/http'
6
6
  require 'open-uri'
@@ -10,7 +10,7 @@ require 'forwardable'
10
10
 
11
11
  module OmniAuth
12
12
  module Strategies
13
- class OpenIDConnect
13
+ class OpenIDConnect # rubocop:disable Metrics/ClassLength
14
14
  include OmniAuth::Strategy
15
15
  extend Forwardable
16
16
 
@@ -37,10 +37,12 @@ module OmniAuth
37
37
  option :issuer
38
38
  option :discovery, false
39
39
  option :client_signing_alg
40
+ option :jwt_secret_base64
40
41
  option :client_jwk_signing_key
41
42
  option :client_x509_signing_key
42
43
  option :scope, [:openid]
43
44
  option :response_type, 'code' # ['code', 'id_token']
45
+ option :require_state, true
44
46
  option :state
45
47
  option :response_mode # [:query, :fragment, :form_post, :web_message]
46
48
  option :display, nil # [:page, :popup, :touch, :wap]
@@ -57,6 +59,14 @@ module OmniAuth
57
59
  option :extra_authorize_params, {}
58
60
  option :allow_authorize_params, []
59
61
  option :uid_field, 'sub'
62
+ option :pkce, false
63
+ option :pkce_verifier, nil
64
+ option :pkce_options, {
65
+ code_challenge: proc { |verifier|
66
+ Base64.urlsafe_encode64(Digest::SHA2.digest(verifier), padding: false)
67
+ },
68
+ code_challenge_method: 'S256',
69
+ }
60
70
 
61
71
  def uid
62
72
  user_info.raw_attributes[options.uid_field.to_sym] || user_info.sub
@@ -66,6 +76,7 @@ module OmniAuth
66
76
  {
67
77
  name: user_info.name,
68
78
  email: user_info.email,
79
+ email_verified: user_info.email_verified,
69
80
  nickname: user_info.preferred_username,
70
81
  first_name: user_info.given_name,
71
82
  last_name: user_info.family_name,
@@ -107,7 +118,7 @@ module OmniAuth
107
118
  def callback_phase
108
119
  error = params['error_reason'] || params['error']
109
120
  error_description = params['error_description'] || params['error_reason']
110
- invalid_state = params['state'].to_s.empty? || params['state'] != stored_state
121
+ invalid_state = (options.require_state && params['state'].to_s.empty?) || params['state'] != stored_state
111
122
 
112
123
  raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error
113
124
  raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state
@@ -178,17 +189,52 @@ module OmniAuth
178
189
  opts[key] = request.params[key.to_s] unless opts.key?(key)
179
190
  end
180
191
 
192
+ if options.pkce
193
+ verifier = options.pkce_verifier ? options.pkce_verifier.call : SecureRandom.hex(64)
194
+
195
+ opts.merge!(pkce_authorize_params(verifier))
196
+ session['omniauth.pkce.verifier'] = verifier
197
+ end
198
+
181
199
  client.authorization_uri(opts.reject { |_k, v| v.nil? })
182
200
  end
183
201
 
184
202
  def public_key
185
- return config.jwks if options.discovery
203
+ @public_key ||= if options.discovery
204
+ config.jwks
205
+ elsif configured_public_key
206
+ configured_public_key
207
+ elsif client_options.jwks_uri
208
+ fetch_key
209
+ end
210
+ end
211
+
212
+ # Some OpenID providers use the OAuth2 client secret as the shared secret, but
213
+ # Keycloak uses a separate key that's stored inside the database.
214
+ def secret
215
+ base64_decoded_jwt_secret || client_options.secret
216
+ end
186
217
 
187
- key_or_secret || config.jwks
218
+ def pkce_authorize_params(verifier)
219
+ # NOTE: see https://tools.ietf.org/html/rfc7636#appendix-A
220
+ {
221
+ code_challenge: options.pkce_options[:code_challenge].call(verifier),
222
+ code_challenge_method: options.pkce_options[:code_challenge_method],
223
+ }
188
224
  end
189
225
 
190
226
  private
191
227
 
228
+ def fetch_key
229
+ @fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get_content(client_options.jwks_uri))
230
+ end
231
+
232
+ def base64_decoded_jwt_secret
233
+ return unless options.jwt_secret_base64
234
+
235
+ Base64.decode64(options.jwt_secret_base64)
236
+ end
237
+
192
238
  def issuer
193
239
  resource = "#{ client_options.scheme }://#{ client_options.host }"
194
240
  resource = "#{ resource }:#{ client_options.port }" if client_options.port
@@ -220,18 +266,88 @@ module OmniAuth
220
266
  def access_token
221
267
  return @access_token if @access_token
222
268
 
223
- @access_token = client.access_token!(
269
+ token_request_params = {
224
270
  scope: (options.scope if options.send_scope_to_token_endpoint),
225
- client_auth_method: options.client_auth_method
226
- )
271
+ client_auth_method: options.client_auth_method,
272
+ }
227
273
 
274
+ token_request_params[:code_verifier] = params['code_verifier'] || session.delete('omniauth.pkce.verifier') if options.pkce
275
+
276
+ @access_token = client.access_token!(token_request_params)
228
277
  verify_id_token!(@access_token.id_token) if configured_response_type == 'code'
229
278
 
230
279
  @access_token
231
280
  end
232
281
 
282
+ # Unlike ::OpenIDConnect::ResponseObject::IdToken.decode, this
283
+ # method splits the decoding and verification of JWT into two
284
+ # steps. First, we decode the JWT without verifying it to
285
+ # determine the algorithm used to sign. Then, we verify it using
286
+ # the appropriate public key (e.g. if algorithm is RS256) or
287
+ # shared secret (e.g. if algorithm is HS256). This works around a
288
+ # limitation in the openid_connect gem:
289
+ # https://github.com/nov/openid_connect/issues/61
233
290
  def decode_id_token(id_token)
234
- ::OpenIDConnect::ResponseObject::IdToken.decode(id_token, public_key)
291
+ decoded = JSON::JWT.decode(id_token, :skip_verification)
292
+ algorithm = decoded.algorithm.to_sym
293
+
294
+ validate_client_algorithm!(algorithm)
295
+
296
+ keyset =
297
+ case algorithm
298
+ when :HS256, :HS384, :HS512
299
+ secret
300
+ else
301
+ public_key
302
+ end
303
+
304
+ decoded.verify!(keyset)
305
+ ::OpenIDConnect::ResponseObject::IdToken.new(decoded)
306
+ rescue JSON::JWK::Set::KidNotFound
307
+ # If the JWT has a key ID (kid), then we know that the set of
308
+ # keys supplied doesn't contain the one we want, and we're
309
+ # done. However, if there is no kid, then we try each key
310
+ # individually to see if one works:
311
+ # https://github.com/nov/json-jwt/pull/92#issuecomment-824654949
312
+ raise if decoded&.header&.key?('kid')
313
+
314
+ decoded = decode_with_each_key!(id_token, keyset)
315
+
316
+ raise unless decoded
317
+
318
+ decoded
319
+ end
320
+
321
+ # If client_signing_alg is specified, we check that the returned JWT
322
+ # matches the expected algorithm. If not, we reject it.
323
+ def validate_client_algorithm!(algorithm)
324
+ client_signing_alg = options.client_signing_alg&.to_sym
325
+
326
+ return unless client_signing_alg
327
+ return if algorithm == client_signing_alg
328
+
329
+ reason = "Received JWT is signed with #{algorithm}, but client_singing_alg is configured for #{client_signing_alg}"
330
+ raise CallbackError, error: :invalid_jwt_algorithm, reason: reason, uri: params['error_uri']
331
+ end
332
+
333
+ def decode!(id_token, key)
334
+ ::OpenIDConnect::ResponseObject::IdToken.decode(id_token, key)
335
+ end
336
+
337
+ def decode_with_each_key!(id_token, keyset)
338
+ return unless keyset.is_a?(JSON::JWK::Set)
339
+
340
+ keyset.each do |key|
341
+ begin
342
+ decoded = decode!(id_token, key)
343
+ rescue JSON::JWS::VerificationFailed, JSON::JWS::UnexpectedAlgorithm, JSON::JWS::UnknownAlgorithm
344
+ next
345
+ end
346
+
347
+ return decoded if decoded
348
+ end
349
+
350
+ nil
235
351
  end
236
352
 
237
353
  def client_options
@@ -273,17 +389,12 @@ module OmniAuth
273
389
  super
274
390
  end
275
391
 
276
- def key_or_secret
277
- case options.client_signing_alg
278
- when :HS256, :HS384, :HS512
279
- client_options.secret
280
- when :RS256, :RS384, :RS512
281
- if options.client_jwk_signing_key
282
- parse_jwk_key(options.client_jwk_signing_key)
283
- elsif options.client_x509_signing_key
284
- parse_x509_key(options.client_x509_signing_key)
285
- end
286
- end
392
+ def configured_public_key
393
+ @configured_public_key ||= if options.client_jwk_signing_key
394
+ parse_jwk_key(options.client_jwk_signing_key)
395
+ elsif options.client_x509_signing_key
396
+ parse_x509_key(options.client_x509_signing_key)
397
+ end
287
398
  end
288
399
 
289
400
  def parse_x509_key(key)
@@ -353,7 +464,7 @@ module OmniAuth
353
464
 
354
465
  decode_id_token(id_token).verify!(issuer: options.issuer,
355
466
  client_id: client_options.identifier,
356
- nonce: stored_nonce)
467
+ nonce: params['nonce'].presence || stored_nonce)
357
468
  end
358
469
 
359
470
  class CallbackError < StandardError
@@ -27,10 +27,8 @@ Gem::Specification.new do |spec|
27
27
  'rubygems_mfa_required' => 'true',
28
28
  }
29
29
 
30
- spec.add_dependency 'addressable', '~> 2.5'
31
30
  spec.add_dependency 'omniauth', '>= 1.9', '< 3'
32
31
  spec.add_dependency 'openid_connect', '~> 1.1'
33
- spec.add_development_dependency 'coveralls', '~> 0.8'
34
32
  spec.add_development_dependency 'faker', '~> 2.0'
35
33
  spec.add_development_dependency 'guard', '~> 2.14'
36
34
  spec.add_development_dependency 'guard-bundler', '~> 2.2'
@@ -39,5 +37,6 @@ Gem::Specification.new do |spec|
39
37
  spec.add_development_dependency 'mocha', '~> 1.7'
40
38
  spec.add_development_dependency 'rake', '~> 12.0'
41
39
  spec.add_development_dependency 'rubocop', '~> 1.12'
42
- spec.add_development_dependency 'simplecov', '~> 0.12'
40
+ spec.add_development_dependency 'simplecov', '~> 0.21'
41
+ spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
43
42
  end