authlete 1.20.0 → 1.23.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 +4 -4
- data/lib/authlete/model/client-extension.rb +6 -1
- data/lib/authlete/model/client.rb +13 -1
- data/lib/authlete/model/service.rb +30 -0
- data/lib/authlete/version.rb +1 -1
- data/test/authlete/model/test_client-extension.rb +7 -2
- data/test/authlete/model/test_client.rb +23 -8
- data/test/authlete/model/test_service.rb +24 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ff212bd1b3919e176b67af639ee6f712b68edd79e84e9c53dd38a9d57e6a4b9
|
4
|
+
data.tar.gz: 3c1a772790b54d7723aaecade60563c7eef07b295eaac9d343b06ada3c9e0b98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46b424e7e7b5cf06fb3f413ec572a61e878b8de70db2cc5a6c696667e7e3f7b8203d90c0eab8aeb0d1d86c9e79d89dac9bffaa3233d304effd9c29bbb4b55c5c
|
7
|
+
data.tar.gz: 923ccd23038030e2f1cc6c77f9da2babae678fed6e291d553463c4d6f3e022cda6b7c1257d192c36bc7519a50727246b1b2d083d83ec368f2c67bd23177c6472
|
@@ -36,6 +36,9 @@ module Authlete
|
|
36
36
|
alias_method :refresh_token_duration, :refreshTokenDuration
|
37
37
|
alias_method :refresh_token_duration=, :refreshTokenDuration=
|
38
38
|
|
39
|
+
attr_accessor :tokenExchangePermitted
|
40
|
+
alias_method :token_exchange_permitted, :tokenExchangePermitted
|
41
|
+
alias_method :token_exchange_permitted=, :tokenExchangePermitted=
|
39
42
|
private
|
40
43
|
|
41
44
|
def defaults
|
@@ -43,7 +46,8 @@ module Authlete
|
|
43
46
|
requestableScopes: nil,
|
44
47
|
requestableScopesEnabled: false,
|
45
48
|
accessTokenDuration: 0,
|
46
|
-
refreshTokenDuration: 0
|
49
|
+
refreshTokenDuration: 0,
|
50
|
+
tokenExchangePermitted: false
|
47
51
|
}
|
48
52
|
end
|
49
53
|
|
@@ -52,6 +56,7 @@ module Authlete
|
|
52
56
|
@requestableScopesEnabled = hash[:requestableScopesEnabled]
|
53
57
|
@accessTokenDuration = hash[:accessTokenDuration]
|
54
58
|
@refreshTokenDuration = hash[:refreshTokenDuration]
|
59
|
+
@tokenExchangePermitted = hash[:tokenExchangePermitted]
|
55
60
|
end
|
56
61
|
end
|
57
62
|
end
|
@@ -312,6 +312,14 @@ module Authlete
|
|
312
312
|
attr_accessor :singleAccessTokenPerSubject
|
313
313
|
alias_method :single_access_token_per_subject, :singleAccessTokenPerSubject
|
314
314
|
alias_method :single_access_token_per_subject=, :singleAccessTokenPerSubject=
|
315
|
+
|
316
|
+
attr_accessor :pkceRequired
|
317
|
+
alias_method :pkce_required, :pkceRequired
|
318
|
+
alias_method :pkce_required=, :pkceRequired=
|
319
|
+
|
320
|
+
attr_accessor :pkceS256Required
|
321
|
+
alias_method :pkce_s256_required, :pkceS256Required
|
322
|
+
alias_method :pkce_s256_required=, :pkceS256Required=
|
315
323
|
private
|
316
324
|
|
317
325
|
def defaults
|
@@ -392,7 +400,9 @@ module Authlete
|
|
392
400
|
requestObjectEncryptionAlgMatchRequired: false,
|
393
401
|
requestObjectEncryptionEncMatchRequired: false,
|
394
402
|
digestAlgorithm: nil,
|
395
|
-
singleAccessTokenPerSubject: false
|
403
|
+
singleAccessTokenPerSubject: false,
|
404
|
+
pkceRequired: false,
|
405
|
+
pkceS256Required: false,
|
396
406
|
}
|
397
407
|
end
|
398
408
|
|
@@ -474,6 +484,8 @@ module Authlete
|
|
474
484
|
@requestObjectEncryptionEncMatchRequired = hash[:requestObjectEncryptionEncMatchRequired]
|
475
485
|
@digestAlgorithm = hash[:digestAlgorithm]
|
476
486
|
@singleAccessTokenPerSubject = hash[:singleAccessTokenPerSubject]
|
487
|
+
@pkceRequired = hash[:pkceRequired]
|
488
|
+
@pkceS256Required = hash[:pkceS256Required]
|
477
489
|
end
|
478
490
|
|
479
491
|
def to_hash_value(key, var)
|
@@ -563,6 +563,26 @@ module Authlete
|
|
563
563
|
alias_method :trust_anchors, :trustAnchors
|
564
564
|
alias_method :trust_anchors=, :trustAnchors=
|
565
565
|
|
566
|
+
attr_accessor :tokenExchangeByIdentifiableClientsOnly
|
567
|
+
alias_method :token_exchange_by_identifiable_clients_only, :tokenExchangeByIdentifiableClientsOnly
|
568
|
+
alias_method :token_exchange_by_identifiable_clients_only=, :tokenExchangeByIdentifiableClientsOnly=
|
569
|
+
|
570
|
+
attr_accessor :tokenExchangeByConfidentialClientsOnly
|
571
|
+
alias_method :token_exchange_by_confidential_clients_only, :tokenExchangeByConfidentialClientsOnly
|
572
|
+
alias_method :token_exchange_by_confidential_clients_only=, :tokenExchangeByConfidentialClientsOnly=
|
573
|
+
|
574
|
+
attr_accessor :tokenExchangeByPermittedClientsOnly
|
575
|
+
alias_method :token_exchange_by_permitted_clients_only, :tokenExchangeByPermittedClientsOnly
|
576
|
+
alias_method :token_exchange_by_permitted_clients_only=, :tokenExchangeByPermittedClientsOnly=
|
577
|
+
|
578
|
+
attr_accessor :tokenExchangeEncryptedJwtRejected
|
579
|
+
alias_method :token_exchange_encrypted_jwt_rejected, :tokenExchangeEncryptedJwtRejected
|
580
|
+
alias_method :token_exchange_encrypted_jwt_rejected=, :tokenExchangeEncryptedJwtRejected=
|
581
|
+
|
582
|
+
attr_accessor :tokenExchangeUnsignedJwtRejected
|
583
|
+
alias_method :token_exchange_unsigned_jwt_rejected, :tokenExchangeUnsignedJwtRejected
|
584
|
+
alias_method :token_exchange_unsigned_jwt_rejected=, :tokenExchangeUnsignedJwtRejected=
|
585
|
+
|
566
586
|
private
|
567
587
|
|
568
588
|
def defaults
|
@@ -706,6 +726,11 @@ module Authlete
|
|
706
726
|
federationRegistrationEndpoint: nil,
|
707
727
|
supportedClientRegistrationTypes: nil,
|
708
728
|
trustAnchors: nil,
|
729
|
+
tokenExchangeByIdentifiableClientsOnly: false,
|
730
|
+
tokenExchangeByConfidentialClientsOnly: false,
|
731
|
+
tokenExchangeByPermittedClientsOnly: false,
|
732
|
+
tokenExchangeEncryptedJwtRejected: false,
|
733
|
+
tokenExchangeUnsignedJwtRejected: false,
|
709
734
|
}
|
710
735
|
end
|
711
736
|
|
@@ -849,6 +874,11 @@ module Authlete
|
|
849
874
|
@federationRegistrationEndpoint = hash[:federationRegistrationEndpoint]
|
850
875
|
@supportedClientRegistrationTypes = hash[:supportedClientRegistrationTypes]
|
851
876
|
@trustAnchors = get_parsed_array(hash[:trustAnchors]) { |e| Authlete::Model::TrustAnchor.parse(e) }
|
877
|
+
@tokenExchangeByIdentifiableClientsOnly = hash[:tokenExchangeByIdentifiableClientsOnly]
|
878
|
+
@tokenExchangeByConfidentialClientsOnly = hash[:tokenExchangeByConfidentialClientsOnly]
|
879
|
+
@tokenExchangeByPermittedClientsOnly = hash[:tokenExchangeByPermittedClientsOnly]
|
880
|
+
@tokenExchangeEncryptedJwtRejected = hash[:tokenExchangeEncryptedJwtRejected]
|
881
|
+
@tokenExchangeUnsignedJwtRejected = hash[:tokenExchangeUnsignedJwtRejected]
|
852
882
|
|
853
883
|
end
|
854
884
|
|
data/lib/authlete/version.rb
CHANGED
@@ -25,6 +25,7 @@ class ClientExtensionTest < Minitest::Test
|
|
25
25
|
REQUESTABLE_SCOPES_ENABLED = true
|
26
26
|
ACCESS_TOKEN_DURATION = 10000
|
27
27
|
REFRESH_TOKEN_DURATION = 10000
|
28
|
+
TOKEN_EXCHANGE_PERMITTED = false
|
28
29
|
|
29
30
|
|
30
31
|
def generate_json
|
@@ -33,7 +34,8 @@ class ClientExtensionTest < Minitest::Test
|
|
33
34
|
"requestableScopes": [ "<requestable-scope0>", "<requestable-scope1>" ],
|
34
35
|
"requestableScopesEnabled": true,
|
35
36
|
"accessTokenDuration": 10000,
|
36
|
-
"refreshTokenDuration": 10000
|
37
|
+
"refreshTokenDuration": 10000,
|
38
|
+
"tokenExchangePermitted": false
|
37
39
|
}
|
38
40
|
JSON
|
39
41
|
end
|
@@ -44,7 +46,8 @@ class ClientExtensionTest < Minitest::Test
|
|
44
46
|
requestableScopes: %w(<requestable-scope0> <requestable-scope1>),
|
45
47
|
requestableScopesEnabled: true,
|
46
48
|
accessTokenDuration: 10000,
|
47
|
-
refreshTokenDuration: 10000
|
49
|
+
refreshTokenDuration: 10000,
|
50
|
+
tokenExchangePermitted: false
|
48
51
|
}
|
49
52
|
end
|
50
53
|
|
@@ -54,6 +57,7 @@ class ClientExtensionTest < Minitest::Test
|
|
54
57
|
obj.requestable_scopes_enabled = REQUESTABLE_SCOPES_ENABLED
|
55
58
|
obj.access_token_duration = ACCESS_TOKEN_DURATION
|
56
59
|
obj.refresh_token_duration = REFRESH_TOKEN_DURATION
|
60
|
+
obj.token_exchange_permitted = TOKEN_EXCHANGE_PERMITTED
|
57
61
|
end
|
58
62
|
|
59
63
|
|
@@ -62,6 +66,7 @@ class ClientExtensionTest < Minitest::Test
|
|
62
66
|
assert_equal REQUESTABLE_SCOPES_ENABLED, obj.requestableScopesEnabled
|
63
67
|
assert_equal ACCESS_TOKEN_DURATION, obj.accessTokenDuration
|
64
68
|
assert_equal REFRESH_TOKEN_DURATION, obj.refreshTokenDuration
|
69
|
+
assert_equal TOKEN_EXCHANGE_PERMITTED, obj.tokenExchangePermitted
|
65
70
|
end
|
66
71
|
|
67
72
|
|
@@ -85,11 +85,13 @@ class ClientTest < Minitest::Test
|
|
85
85
|
REQUESTABLE_SCOPES_ENABLED = true
|
86
86
|
ACCESS_TOKEN_DURATION = 10000
|
87
87
|
REFRESH_TOKEN_DURATION = 10000
|
88
|
+
TOKEN_EXCHANGE_PERMITTED = false
|
88
89
|
EXTENSION = Authlete::Model::ClientExtension.new(
|
89
90
|
requestableScopes: REQUESTABLE_SCOPES,
|
90
91
|
requestableScopesEnabled: REQUESTABLE_SCOPES_ENABLED,
|
91
92
|
accessTokenDuration: ACCESS_TOKEN_DURATION,
|
92
|
-
refreshTokenDuration: REFRESH_TOKEN_DURATION
|
93
|
+
refreshTokenDuration: REFRESH_TOKEN_DURATION,
|
94
|
+
tokenExchangePermitted: TOKEN_EXCHANGE_PERMITTED
|
93
95
|
)
|
94
96
|
TLS_CLIENT_AUTH_SUBJECT_DN = '<tls-client-auth-subject-dn>'
|
95
97
|
TLS_CLIENT_AUTH_SAN_DNS = '<tls-client-auth-san-dns>'
|
@@ -121,6 +123,8 @@ class ClientTest < Minitest::Test
|
|
121
123
|
REQUEST_OBJECT_ENCRYPTION_ENC_MATCH_REQUIRED = false
|
122
124
|
DIGEST_ALGORITHM = '<digest-algorithm>'
|
123
125
|
SINGLE_ACCESS_TOKEN_PER_SUBJECT = false
|
126
|
+
PKCE_REQUIRED = false
|
127
|
+
PKCE_S256_REQUIRED = false
|
124
128
|
|
125
129
|
def generate_json
|
126
130
|
return <<~JSON
|
@@ -177,7 +181,8 @@ class ClientTest < Minitest::Test
|
|
177
181
|
"requestableScopes": [ "<requestable-scope0>", "<requestable-scope1>" ],
|
178
182
|
"requestableScopesEnabled": true,
|
179
183
|
"accessTokenDuration": 10000,
|
180
|
-
"refreshTokenDuration": 10000
|
184
|
+
"refreshTokenDuration": 10000,
|
185
|
+
"tokenExchangePermitted": false
|
181
186
|
},
|
182
187
|
"tlsClientAuthSubjectDn": "<tls-client-auth-subject-dn>",
|
183
188
|
"tlsClientAuthSanDns": "<tls-client-auth-san-dns>",
|
@@ -206,11 +211,13 @@ class ClientTest < Minitest::Test
|
|
206
211
|
"requestObjectEncryptionAlgMatchRequired": false,
|
207
212
|
"requestObjectEncryptionEncMatchRequired": false,
|
208
213
|
"digestAlgorithm": "<digest-algorithm>",
|
209
|
-
"singleAccessTokenPerSubject": false
|
214
|
+
"singleAccessTokenPerSubject": false,
|
215
|
+
"pkceRequired": false,
|
216
|
+
"pkceS256Required": false
|
210
217
|
}
|
211
|
-
|
212
|
-
|
213
|
-
|
218
|
+
JSON
|
219
|
+
end
|
220
|
+
|
214
221
|
|
215
222
|
def generate_hash
|
216
223
|
{
|
@@ -266,7 +273,8 @@ class ClientTest < Minitest::Test
|
|
266
273
|
requestableScopes: [ '<requestable-scope0>', '<requestable-scope1>' ],
|
267
274
|
requestableScopesEnabled: true,
|
268
275
|
accessTokenDuration: 10000,
|
269
|
-
refreshTokenDuration: 10000
|
276
|
+
refreshTokenDuration: 10000,
|
277
|
+
tokenExchangePermitted: false
|
270
278
|
},
|
271
279
|
tlsClientAuthSubjectDn: '<tls-client-auth-subject-dn>',
|
272
280
|
tlsClientAuthSanDns: '<tls-client-auth-san-dns>',
|
@@ -295,7 +303,9 @@ class ClientTest < Minitest::Test
|
|
295
303
|
requestObjectEncryptionAlgMatchRequired: false,
|
296
304
|
requestObjectEncryptionEncMatchRequired: false,
|
297
305
|
digestAlgorithm: '<digest-algorithm>',
|
298
|
-
singleAccessTokenPerSubject: false
|
306
|
+
singleAccessTokenPerSubject: false,
|
307
|
+
pkceRequired: false,
|
308
|
+
pkceS256Required: false,
|
299
309
|
}
|
300
310
|
end
|
301
311
|
|
@@ -378,6 +388,8 @@ class ClientTest < Minitest::Test
|
|
378
388
|
obj.requestObjectEncryptionEncMatchRequired = REQUEST_OBJECT_ENCRYPTION_ENC_MATCH_REQUIRED
|
379
389
|
obj.digestAlgorithm = DIGEST_ALGORITHM
|
380
390
|
obj.singleAccessTokenPerSubject = SINGLE_ACCESS_TOKEN_PER_SUBJECT
|
391
|
+
obj.pkceRequired = PKCE_REQUIRED
|
392
|
+
obj.pkceS256Required = PKCE_S256_REQUIRED
|
381
393
|
end
|
382
394
|
|
383
395
|
|
@@ -440,6 +452,7 @@ class ClientTest < Minitest::Test
|
|
440
452
|
assert_equal REQUESTABLE_SCOPES_ENABLED, obj.extension.requestableScopesEnabled
|
441
453
|
assert_equal ACCESS_TOKEN_DURATION, obj.extension.accessTokenDuration
|
442
454
|
assert_equal REFRESH_TOKEN_DURATION, obj.extension.refreshTokenDuration
|
455
|
+
assert_equal TOKEN_EXCHANGE_PERMITTED, obj.extension.tokenExchangePermitted
|
443
456
|
assert_equal TLS_CLIENT_AUTH_SUBJECT_DN, obj.tlsClientAuthSubjectDn
|
444
457
|
assert_equal TLS_CLIENT_AUTH_SAN_DNS, obj.tlsClientAuthSanDns
|
445
458
|
assert_equal TLS_CLIENT_AUTH_SAN_URI, obj.tlsClientAuthSanUri
|
@@ -467,6 +480,8 @@ class ClientTest < Minitest::Test
|
|
467
480
|
assert_equal REQUEST_OBJECT_ENCRYPTION_ENC_MATCH_REQUIRED, obj.requestObjectEncryptionEncMatchRequired
|
468
481
|
assert_equal DIGEST_ALGORITHM, obj.digestAlgorithm
|
469
482
|
assert_equal SINGLE_ACCESS_TOKEN_PER_SUBJECT, obj.singleAccessTokenPerSubject
|
483
|
+
assert_equal PKCE_REQUIRED, obj.pkceRequired
|
484
|
+
assert_equal PKCE_S256_REQUIRED, obj.pkceS256Required
|
470
485
|
end
|
471
486
|
|
472
487
|
|
@@ -183,7 +183,11 @@ class ServiceTest < Minitest::Test
|
|
183
183
|
TRUST_ANCHOR_ENTITY_ID = '<entity-id>'
|
184
184
|
TRUST_ANCHOR_JWKS = '<jwks>'
|
185
185
|
TRUST_ANCHORS = [ Authlete::Model::TrustAnchor.new(entityId: TRUST_ANCHOR_ENTITY_ID, jwks: TRUST_ANCHOR_JWKS) ]
|
186
|
-
|
186
|
+
TOKEN_EXCHANGE_BY_IDENTIFIABLE_CLIENTS_ONLY = false
|
187
|
+
TOKEN_EXCHANGE_BY_CONFIDENTIAL_CLIENTS_ONLY = false
|
188
|
+
TOKEN_EXCHANGE_BY_PERMITTED_CLIENTS_ONLY = false
|
189
|
+
TOKEN_EXCHANGE_ENCRYPTED_JWT_REJECTED = false
|
190
|
+
TOKEN_EXCHANGE_UNSIGNED_JWT_REJECTED = false
|
187
191
|
|
188
192
|
def generate_json
|
189
193
|
return <<~JSON
|
@@ -326,7 +330,12 @@ class ServiceTest < Minitest::Test
|
|
326
330
|
"signedJwksUri": "<signed-jwks-uri>",
|
327
331
|
"federationRegistrationEndpoint": "<federation-registration-endpoint>",
|
328
332
|
"supportedClientRegistrationTypes": [ "AUTOMATIC", "EXPLICIT"],
|
329
|
-
"trustAnchors": [{ "entityId": "<entity-id>", "jwks": "<jwks>" }]
|
333
|
+
"trustAnchors": [{ "entityId": "<entity-id>", "jwks": "<jwks>" }],
|
334
|
+
"tokenExchangeByIdentifiableClientsOnly": false,
|
335
|
+
"tokenExchangeByConfidentialClientsOnly": false,
|
336
|
+
"tokenExchangeByPermittedClientsOnly": false,
|
337
|
+
"tokenExchangeEncryptedJwtRejected": false,
|
338
|
+
"tokenExchangeUnsignedJwtRejected": false
|
330
339
|
}
|
331
340
|
JSON
|
332
341
|
|
@@ -474,6 +483,11 @@ class ServiceTest < Minitest::Test
|
|
474
483
|
federationRegistrationEndpoint: '<federation-registration-endpoint>',
|
475
484
|
supportedClientRegistrationTypes: [ 'AUTOMATIC', 'EXPLICIT'],
|
476
485
|
trustAnchors: [{ entityId: "<entity-id>", jwks: "<jwks>" }],
|
486
|
+
tokenExchangeByIdentifiableClientsOnly: false,
|
487
|
+
tokenExchangeByConfidentialClientsOnly: false,
|
488
|
+
tokenExchangeByPermittedClientsOnly: false,
|
489
|
+
tokenExchangeEncryptedJwtRejected: false,
|
490
|
+
tokenExchangeUnsignedJwtRejected: false,
|
477
491
|
}
|
478
492
|
end
|
479
493
|
|
@@ -618,6 +632,9 @@ class ServiceTest < Minitest::Test
|
|
618
632
|
obj.federation_registration_endpoint = FEDERATION_REGISTRATION_ENDPOINT
|
619
633
|
obj.supported_client_registration_types = SUPPORTED_CLIENT_REGISTRATION_TYPES
|
620
634
|
obj.trust_anchors = TRUST_ANCHORS
|
635
|
+
obj.token_exchange_by_identifiable_clients_only = TOKEN_EXCHANGE_BY_IDENTIFIABLE_CLIENTS_ONLY
|
636
|
+
obj.token_exchange_by_confidential_clients_only = TOKEN_EXCHANGE_BY_CONFIDENTIAL_CLIENTS_ONLY
|
637
|
+
obj.token_exchange_by_permitted_clients_only = TOKEN_EXCHANGE_BY_PERMITTED_CLIENTS_ONLY
|
621
638
|
end
|
622
639
|
|
623
640
|
|
@@ -776,6 +793,11 @@ class ServiceTest < Minitest::Test
|
|
776
793
|
assert_equal SUPPORTED_CLIENT_REGISTRATION_TYPES, obj.supported_client_registration_types
|
777
794
|
assert_equal TRUST_ANCHOR_ENTITY_ID, obj.trustAnchors[0].entityId
|
778
795
|
assert_equal TRUST_ANCHOR_JWKS, obj.trustAnchors[0].jwks
|
796
|
+
assert_equal TOKEN_EXCHANGE_BY_IDENTIFIABLE_CLIENTS_ONLY, obj.token_exchange_by_identifiable_clients_only
|
797
|
+
assert_equal TOKEN_EXCHANGE_BY_CONFIDENTIAL_CLIENTS_ONLY, obj.token_exchange_by_confidential_clients_only
|
798
|
+
assert_equal TOKEN_EXCHANGE_BY_PERMITTED_CLIENTS_ONLY, obj.token_exchange_by_permitted_clients_only
|
799
|
+
assert_equal TOKEN_EXCHANGE_ENCRYPTED_JWT_REJECTED, obj.token_exchange_encrypted_jwt_rejected
|
800
|
+
assert_equal TOKEN_EXCHANGE_UNSIGNED_JWT_REJECTED, obj.token_exchange_unsigned_jwt_rejected
|
779
801
|
end
|
780
802
|
|
781
803
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: authlete
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.23.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Takahiko Kawasaki
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-08-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|