omniauth_openid_connect 0.3.5 → 0.5.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: 9c030571ea9bcbd861ebe4d8455282c0b1b34c17af77294660b1c0123ed976ab
4
- data.tar.gz: 2f4b2a8cd026797260f36e358ac7678a6827486889f23aab325d08e0d390b649
3
+ metadata.gz: 34218d7218e2aca766623a1252b641baa20d81330cfd38c67a733f9221d76aad
4
+ data.tar.gz: 54f874832122f3cc1444280d8820d222a4dff7ef4c3580eb899d4da1efb38051
5
5
  SHA512:
6
- metadata.gz: afdf6363a18b019939a88abc612be51b20780fdeae2f1f300ef5e9df5a1002a97812b17360939e1ba9bb0b3bf4e54471e3da0ec6de858ac2afa40df15fdfb23d
7
- data.tar.gz: e449bd59e5e75cfcce12ae27e5072fb8e3fe821e969a510856bbad0ed2553252345826ed16e97ecdbe1405a8c4be275195f759e71aed99fd699bf9509bffa0f3
6
+ metadata.gz: e4465213a06e82fd61d9997d0ec1b9f993620dead092add6ba2f07b34aa08dca53bcb63d92a256839c7310478c89ecff9359778e5721cf1cc6dcc300e5d780f8
7
+ data.tar.gz: 8fdbda1f579f271f6273a6380a7a9cf581b4b1239b589a1e2cb98799b8731056c3ec222c519e445d95387749ab62b58839e4005906938e0e1f76b9d396e76565
@@ -0,0 +1,63 @@
1
+ name: Main
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ - master
7
+
8
+ pull_request:
9
+ types: [opened, synchronize, reopened]
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ ruby: ["2.5", "2.6", "2.7", "3.0", "3.1"]
18
+ name: Ruby ${{ matrix.ruby }}
19
+
20
+ steps:
21
+ - name: Checkout code
22
+ uses: actions/checkout@v2
23
+
24
+ - name: Setup Ruby
25
+ uses: ruby/setup-ruby@v1
26
+ with:
27
+ ruby-version: ${{ matrix.ruby }}
28
+ bundler-cache: true
29
+
30
+ - name: Run tests
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
@@ -1,3 +1,6 @@
1
+ Gemspec/RequiredRubyVersion:
2
+ Enabled: false
3
+
1
4
  LineLength:
2
5
  Description: 'Limit lines to 130 characters.'
3
6
  Max: 130
@@ -36,7 +39,10 @@ Documentation:
36
39
  Enabled: false
37
40
 
38
41
  Metrics/AbcSize:
39
- Max: 50
42
+ Max: 60
43
+
44
+ Metrics/ClassLength:
45
+ Max: 300
40
46
 
41
47
  Metrics/CyclomaticComplexity:
42
48
  Max: 50
@@ -52,7 +58,4 @@ Metrics/MethodLength:
52
58
 
53
59
  AllCops:
54
60
  Exclude:
55
- - bin/**/*
56
- - Rakefile
57
- - config/**/*
58
- - test/**/*
61
+ - vendor/bundle/**/*
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # v0.5.0 (26.12.2022)
2
+
3
+ - Support the "nonce" parameter forwarding without a session [#130](https://github.com/omniauth/omniauth_openid_connect/pull/130)
4
+ - Fetch key from JWKS URI if available [#133](https://github.com/omniauth/omniauth_openid_connect/pull/133)
5
+ - Make the state parameter verification optional [#122](https://github.com/omniauth/omniauth_openid_connect/pull/122)
6
+ - Add email_verified claim in user info [#131](https://github.com/omniauth/omniauth_openid_connect/pull/131)
7
+ - Add PKCE verification support [#128](https://github.com/omniauth/omniauth_openid_connect/pull/128)
8
+
9
+ # v0.4.0 (06.02.2022)
10
+
11
+ - Support dynamic parameters to the authorize URI [#90](https://github.com/omniauth/omniauth_openid_connect/pull/90)
12
+ - Upgrade Faker and replace Travis with Github Actions [#102](https://github.com/omniauth/omniauth_openid_connect/pull/102)
13
+ - Make `omniauth_openid_connect` gem compatible with `omniauth v2.0` [#95](https://github.com/omniauth/omniauth_openid_connect/pull/95)
14
+ - Fall back to the discovered jwks when no key specified [#97](https://github.com/omniauth/omniauth_openid_connect/pull/97)
15
+ - Allow updating to omniauth v2 [#88](https://github.com/omniauth/omniauth_openid_connect/pull/88)
16
+
1
17
  # v0.3.5 (07.06.2020)
2
18
 
3
19
  - bugfix: Info from decoded id_token is not exposed into `request.env['omniauth.auth']` [#61](https://github.com/m0n9oose/omniauth_openid_connect/pull/61)
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/Guardfile CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  guard 'minitest' do
7
7
  # with Minitest::Unit
8
- watch(%r{^test/(.*)\/(.*)_test\.rb})
8
+ watch(%r{^test/(.*)/(.*)_test\.rb})
9
9
  watch(%r{^lib/(.*)\.rb}) { |m| "test/lib/#{m[1]}_test.rb" }
10
10
  watch(%r{^test/test_helper\.rb}) { 'test' }
11
11
  end
data/README.md CHANGED
@@ -4,7 +4,8 @@ Originally was [omniauth-openid-connect](https://github.com/jjbohn/omniauth-open
4
4
 
5
5
  I've forked this repository and launch as separate gem because maintaining of original was dropped.
6
6
 
7
- [![Build Status](https://travis-ci.org/m0n9oose/omniauth_openid_connect.png?branch=master)](https://travis-ci.org/m0n9oose/omniauth_openid_connect)
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)
8
9
 
9
10
  ## Installation
10
11
 
@@ -19,10 +20,10 @@ And then execute:
19
20
  Or install it yourself as:
20
21
 
21
22
  $ gem install omniauth_openid_connect
22
-
23
+
23
24
  ## Supported Ruby Versions
24
25
 
25
- 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
26
27
 
27
28
  ## Usage
28
29
 
@@ -46,22 +47,28 @@ config.omniauth :openid_connect, {
46
47
 
47
48
  ### Options Overview
48
49
 
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 | | |
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 | | |
65
72
 
66
73
  ### Client Config Options
67
74
 
@@ -91,7 +98,7 @@ These are the configuration options for the client_options hash of the configura
91
98
 
92
99
  * `response_type` tells the authorization server which grant type the application wants to use,
93
100
  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.
101
+ * If you want to pass `state` parameter by yourself. You can set Proc Object.
95
102
  e.g. `state: Proc.new { SecureRandom.hex(32) }`
96
103
  * `nonce` is optional. If don't want to pass "nonce" parameter to provider, You should specify
97
104
  `false` to `send_nonce` option. (default true)
@@ -113,6 +120,11 @@ These are the configuration options for the client_options hash of the configura
113
120
  property can be used to add the attribute to the token request. Initial value is `true`, which means that the
114
121
  scope attribute is included by default.
115
122
 
123
+ ## Additional notes
124
+ * 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.
125
+ In such example, the session is empty, so you have to forward certain parameters received from the client.
126
+ Currently supported ones are `code_verifier` and `nonce` - simply provide them as the `/callback` request parameters.
127
+
116
128
  For the full low down on OpenID Connect, please check out
117
129
  [the spec](http://openid.net/specs/openid-connect-core-1_0.html).
118
130
 
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
 
@@ -3,7 +3,9 @@
3
3
  module OmniAuth
4
4
  module OpenIDConnect
5
5
  class Error < RuntimeError; end
6
+
6
7
  class MissingCodeError < Error; end
8
+
7
9
  class MissingIdTokenError < Error; end
8
10
  end
9
11
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAuth
4
4
  module OpenIDConnect
5
- VERSION = '0.3.5'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'addressable/uri'
4
3
  require 'timeout'
5
4
  require 'net/http'
6
5
  require 'open-uri'
@@ -10,7 +9,7 @@ require 'forwardable'
10
9
 
11
10
  module OmniAuth
12
11
  module Strategies
13
- class OpenIDConnect
12
+ class OpenIDConnect # rubocop:disable Metrics/ClassLength
14
13
  include OmniAuth::Strategy
15
14
  extend Forwardable
16
15
 
@@ -41,6 +40,7 @@ module OmniAuth
41
40
  option :client_x509_signing_key
42
41
  option :scope, [:openid]
43
42
  option :response_type, 'code' # ['code', 'id_token']
43
+ option :require_state, true
44
44
  option :state
45
45
  option :response_mode # [:query, :fragment, :form_post, :web_message]
46
46
  option :display, nil # [:page, :popup, :touch, :wap]
@@ -55,7 +55,16 @@ module OmniAuth
55
55
  option :client_auth_method
56
56
  option :post_logout_redirect_uri
57
57
  option :extra_authorize_params, {}
58
+ option :allow_authorize_params, []
58
59
  option :uid_field, 'sub'
60
+ option :pkce, false
61
+ option :pkce_verifier, nil
62
+ option :pkce_options, {
63
+ code_challenge: proc { |verifier|
64
+ Base64.urlsafe_encode64(Digest::SHA2.digest(verifier), padding: false)
65
+ },
66
+ code_challenge_method: 'S256',
67
+ }
59
68
 
60
69
  def uid
61
70
  user_info.raw_attributes[options.uid_field.to_sym] || user_info.sub
@@ -65,6 +74,7 @@ module OmniAuth
65
74
  {
66
75
  name: user_info.name,
67
76
  email: user_info.email,
77
+ email_verified: user_info.email_verified,
68
78
  nickname: user_info.preferred_username,
69
79
  first_name: user_info.given_name,
70
80
  last_name: user_info.family_name,
@@ -106,7 +116,7 @@ module OmniAuth
106
116
  def callback_phase
107
117
  error = params['error_reason'] || params['error']
108
118
  error_description = params['error_description'] || params['error_reason']
109
- invalid_state = params['state'].to_s.empty? || params['state'] != stored_state
119
+ invalid_state = (options.require_state && params['state'].to_s.empty?) || params['state'] != stored_state
110
120
 
111
121
  raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error
112
122
  raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state
@@ -173,17 +183,44 @@ module OmniAuth
173
183
 
174
184
  opts.merge!(options.extra_authorize_params) unless options.extra_authorize_params.empty?
175
185
 
186
+ options.allow_authorize_params.each do |key|
187
+ opts[key] = request.params[key.to_s] unless opts.key?(key)
188
+ end
189
+
190
+ if options.pkce
191
+ verifier = options.pkce_verifier ? options.pkce_verifier.call : SecureRandom.hex(64)
192
+
193
+ opts.merge!(pkce_authorize_params(verifier))
194
+ session['omniauth.pkce.verifier'] = verifier
195
+ end
196
+
176
197
  client.authorization_uri(opts.reject { |_k, v| v.nil? })
177
198
  end
178
199
 
179
200
  def public_key
180
- return config.jwks if options.discovery
201
+ @public_key ||= if options.discovery
202
+ config.jwks
203
+ elsif key_or_secret
204
+ key_or_secret
205
+ elsif client_options.jwks_uri
206
+ fetch_key
207
+ end
208
+ end
181
209
 
182
- key_or_secret
210
+ def pkce_authorize_params(verifier)
211
+ # NOTE: see https://tools.ietf.org/html/rfc7636#appendix-A
212
+ {
213
+ code_challenge: options.pkce_options[:code_challenge].call(verifier),
214
+ code_challenge_method: options.pkce_options[:code_challenge_method],
215
+ }
183
216
  end
184
217
 
185
218
  private
186
219
 
220
+ def fetch_key
221
+ @fetch_key ||= parse_jwk_key(::OpenIDConnect.http_client.get_content(client_options.jwks_uri))
222
+ end
223
+
187
224
  def issuer
188
225
  resource = "#{ client_options.scheme }://#{ client_options.host }"
189
226
  resource = "#{ resource }:#{ client_options.port }" if client_options.port
@@ -215,11 +252,14 @@ module OmniAuth
215
252
  def access_token
216
253
  return @access_token if @access_token
217
254
 
218
- @access_token = client.access_token!(
255
+ token_request_params = {
219
256
  scope: (options.scope if options.send_scope_to_token_endpoint),
220
- client_auth_method: options.client_auth_method
221
- )
257
+ client_auth_method: options.client_auth_method,
258
+ }
222
259
 
260
+ token_request_params[:code_verifier] = params['code_verifier'] || session.delete('omniauth.pkce.verifier') if options.pkce
261
+
262
+ @access_token = client.access_token!(token_request_params)
223
263
  verify_id_token!(@access_token.id_token) if configured_response_type == 'code'
224
264
 
225
265
  @access_token
@@ -256,6 +296,12 @@ module OmniAuth
256
296
  session.delete('omniauth.nonce')
257
297
  end
258
298
 
299
+ def script_name
300
+ return '' if @env.nil?
301
+
302
+ super
303
+ end
304
+
259
305
  def session
260
306
  return {} if @env.nil?
261
307
 
@@ -263,16 +309,17 @@ module OmniAuth
263
309
  end
264
310
 
265
311
  def key_or_secret
266
- case options.client_signing_alg
267
- when :HS256, :HS384, :HS512
268
- client_options.secret
269
- when :RS256, :RS384, :RS512
270
- if options.client_jwk_signing_key
271
- parse_jwk_key(options.client_jwk_signing_key)
272
- elsif options.client_x509_signing_key
273
- parse_x509_key(options.client_x509_signing_key)
312
+ @key_or_secret ||=
313
+ case options.client_signing_alg&.to_sym
314
+ when :HS256, :HS384, :HS512
315
+ client_options.secret
316
+ when :RS256, :RS384, :RS512
317
+ if options.client_jwk_signing_key
318
+ parse_jwk_key(options.client_jwk_signing_key)
319
+ elsif options.client_x509_signing_key
320
+ parse_x509_key(options.client_x509_signing_key)
321
+ end
274
322
  end
275
- end
276
323
  end
277
324
 
278
325
  def parse_x509_key(key)
@@ -342,13 +389,14 @@ module OmniAuth
342
389
 
343
390
  decode_id_token(id_token).verify!(issuer: options.issuer,
344
391
  client_id: client_options.identifier,
345
- nonce: stored_nonce)
392
+ nonce: params['nonce'].presence || stored_nonce)
346
393
  end
347
394
 
348
395
  class CallbackError < StandardError
349
396
  attr_accessor :error, :error_reason, :error_uri
350
397
 
351
398
  def initialize(data)
399
+ super
352
400
  self.error = data[:error]
353
401
  self.error_reason = data[:reason]
354
402
  self.error_uri = data[:uri]
@@ -19,17 +19,24 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.add_dependency 'addressable', '~> 2.5'
23
- spec.add_dependency 'omniauth', '~> 1.9'
22
+ spec.metadata = {
23
+ 'bug_tracker_uri' => 'https://github.com/m0n9oose/omniauth_openid_connect/issues',
24
+ 'changelog_uri' => 'https://github.com/m0n9oose/omniauth_openid_connect/releases',
25
+ 'documentation_uri' => "https://github.com/m0n9oose/omniauth_openid_connect/tree/v#{spec.version}#readme",
26
+ 'source_code_uri' => "https://github.com/m0n9oose/omniauth_openid_connect/tree/v#{spec.version}",
27
+ 'rubygems_mfa_required' => 'true',
28
+ }
29
+
30
+ spec.add_dependency 'omniauth', '>= 1.9', '< 3'
24
31
  spec.add_dependency 'openid_connect', '~> 1.1'
25
- spec.add_development_dependency 'coveralls', '~> 0.8'
26
- spec.add_development_dependency 'faker', '~> 1.6'
32
+ spec.add_development_dependency 'faker', '~> 2.0'
27
33
  spec.add_development_dependency 'guard', '~> 2.14'
28
34
  spec.add_development_dependency 'guard-bundler', '~> 2.2'
29
35
  spec.add_development_dependency 'guard-minitest', '~> 2.4'
30
36
  spec.add_development_dependency 'minitest', '~> 5.1'
31
37
  spec.add_development_dependency 'mocha', '~> 1.7'
32
38
  spec.add_development_dependency 'rake', '~> 12.0'
33
- spec.add_development_dependency 'rubocop', '~> 0.63'
34
- spec.add_development_dependency 'simplecov', '~> 0.12'
39
+ spec.add_development_dependency 'rubocop', '~> 1.12'
40
+ spec.add_development_dependency 'simplecov', '~> 0.21'
41
+ spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
35
42
  end