omniauth_openid_connect 0.3.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +63 -0
- data/.rubocop.yml +8 -5
- data/CHANGELOG.md +16 -0
- data/Gemfile +6 -0
- data/Guardfile +1 -1
- data/README.md +32 -20
- data/Rakefile +2 -0
- data/lib/omniauth/openid_connect/errors.rb +2 -0
- data/lib/omniauth/openid_connect/version.rb +1 -1
- data/lib/omniauth/strategies/openid_connect.rb +66 -18
- data/omniauth_openid_connect.gemspec +13 -6
- data/test/lib/omniauth/strategies/openid_connect_test.rb +226 -79
- data/test/strategy_test_case.rb +37 -3
- data/test/test_helper.rb +17 -7
- metadata +41 -50
- data/.github/config/rubocop_linter_action.yml +0 -59
- data/.github/workflows/rubocop.yml +0 -22
- data/.travis.yml +0 -8
- data/test/fixtures/id_token.txt +0 -1
- data/test/fixtures/jwks.json +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34218d7218e2aca766623a1252b641baa20d81330cfd38c67a733f9221d76aad
|
4
|
+
data.tar.gz: 54f874832122f3cc1444280d8820d222a4dff7ef4c3580eb899d4da1efb38051
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
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
|
-
-
|
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
data/Guardfile
CHANGED
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://
|
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.
|
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
|
50
|
-
|
51
|
-
| name | Arbitrary string to identify connection and identify it from other openid_connect providers
|
52
|
-
| issuer | Root url for the authorization server | yes |
|
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
|
54
|
-
| client_auth_method | Which authentication method to use to authenticate your app with the authorization server | no | Sym: basic
|
55
|
-
| scope | Which OpenID scopes to include (:openid is always required) | no | Array<sym> [:openid]
|
56
|
-
| response_type | Which OAuth2 response type to use with the authorization request | no | String: code
|
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
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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`
|
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,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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
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.
|
23
|
-
|
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 '
|
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', '~>
|
34
|
-
spec.add_development_dependency 'simplecov', '~> 0.
|
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
|