No flags found
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
3b7a676
... +55 ...
20c50b0
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
29 | 29 | errSink chan AutoRefreshError |
|
30 | 30 | cache map[string]Set |
|
31 | 31 | configureCh chan struct{} |
|
32 | + | removeCh chan removeReq |
|
32 | 33 | fetching map[string]chan struct{} |
|
33 | 34 | muErrSink sync.Mutex |
|
34 | 35 | muCache sync.RWMutex |
86 | 87 | lastRefresh time.Time |
|
87 | 88 | nextRefresh time.Time |
|
88 | 89 | ||
89 | - | wl Whitelist |
|
90 | + | wl Whitelist |
|
91 | + | parseOptions []ParseOption |
|
90 | 92 | } |
|
91 | 93 | ||
92 | 94 | type resetTimerReq struct { |
114 | 116 | af := &AutoRefresh{ |
|
115 | 117 | cache: make(map[string]Set), |
|
116 | 118 | configureCh: make(chan struct{}), |
|
119 | + | removeCh: make(chan removeReq), |
|
117 | 120 | fetching: make(map[string]chan struct{}), |
|
118 | 121 | registry: make(map[string]*target), |
|
119 | 122 | resetTimerCh: make(chan *resetTimerReq), |
132 | 135 | return nil, false |
|
133 | 136 | } |
|
134 | 137 | ||
138 | + | type removeReq struct { |
|
139 | + | replyCh chan error |
|
140 | + | url string |
|
141 | + | } |
|
142 | + | ||
143 | + | // Remove removes `url` from the list of urls being watched by jwk.AutoRefresh. |
|
144 | + | // If the url is not already registered, returns an error. |
|
145 | + | func (af *AutoRefresh) Remove(url string) error { |
|
146 | + | ch := make(chan error) |
|
147 | + | af.removeCh <- removeReq{replyCh: ch, url: url} |
|
148 | + | return <-ch |
|
149 | + | } |
|
150 | + | ||
135 | 151 | // Configure registers the url to be controlled by AutoRefresh, and also |
|
136 | 152 | // sets any options associated to it. |
|
137 | 153 | // |
153 | 169 | var hasRefreshInterval bool |
|
154 | 170 | var refreshInterval time.Duration |
|
155 | 171 | var wl Whitelist |
|
172 | + | var parseOptions []ParseOption |
|
156 | 173 | minRefreshInterval := time.Hour |
|
157 | 174 | bo := backoff.Null() |
|
158 | 175 | for _, option := range options { |
|
176 | + | if v, ok := option.(ParseOption); ok { |
|
177 | + | parseOptions = append(parseOptions, v) |
|
178 | + | continue |
|
179 | + | } |
|
180 | + | ||
159 | 181 | //nolint:forcetypeassert |
|
160 | 182 | switch option.Ident() { |
|
161 | 183 | case identFetchBackoff{}: |
172 | 194 | } |
|
173 | 195 | } |
|
174 | 196 | ||
175 | - | var doReconfigure bool |
|
176 | 197 | af.muRegistry.Lock() |
|
177 | 198 | t, ok := af.registry[url] |
|
178 | 199 | if ok { |
|
179 | 200 | if t.httpcl != httpcl { |
|
180 | 201 | t.httpcl = httpcl |
|
181 | - | doReconfigure = true |
|
182 | 202 | } |
|
183 | 203 | ||
184 | 204 | if t.minRefreshInterval != minRefreshInterval { |
|
185 | 205 | t.minRefreshInterval = minRefreshInterval |
|
186 | - | doReconfigure = true |
|
187 | 206 | } |
|
188 | 207 | ||
189 | 208 | if t.refreshInterval != nil { |
|
190 | 209 | if !hasRefreshInterval { |
|
191 | 210 | t.refreshInterval = nil |
|
192 | - | doReconfigure = true |
|
193 | 211 | } else if *t.refreshInterval != refreshInterval { |
|
194 | 212 | *t.refreshInterval = refreshInterval |
|
195 | - | doReconfigure = true |
|
196 | 213 | } |
|
197 | 214 | } else { |
|
198 | 215 | if hasRefreshInterval { |
|
199 | 216 | t.refreshInterval = &refreshInterval |
|
200 | - | doReconfigure = true |
|
201 | 217 | } |
|
202 | 218 | } |
|
203 | 219 | ||
204 | 220 | if t.wl != wl { |
|
205 | 221 | t.wl = wl |
|
206 | - | doReconfigure = true |
|
207 | 222 | } |
|
223 | + | ||
224 | + | t.parseOptions = parseOptions |
|
208 | 225 | } else { |
|
209 | 226 | t = &target{ |
|
210 | 227 | backoff: bo, |
215 | 232 | // This is a placeholder timer so we can call Reset() on it later |
|
216 | 233 | // Make it sufficiently in the future so that we don't have bogus |
|
217 | 234 | // events firing |
|
218 | - | timer: time.NewTimer(24 * time.Hour), |
|
219 | - | wl: wl, |
|
235 | + | timer: time.NewTimer(24 * time.Hour), |
|
236 | + | wl: wl, |
|
237 | + | parseOptions: parseOptions, |
|
220 | 238 | } |
|
221 | 239 | if hasRefreshInterval { |
|
222 | 240 | t.refreshInterval = &refreshInterval |
|
223 | 241 | } |
|
224 | 242 | ||
225 | 243 | // Record this in the registry |
|
226 | 244 | af.registry[url] = t |
|
227 | - | doReconfigure = true |
|
228 | 245 | } |
|
229 | 246 | af.muRegistry.Unlock() |
|
230 | 247 | ||
231 | - | if doReconfigure { |
|
232 | - | // Tell the backend to reconfigure itself |
|
233 | - | af.configureCh <- struct{}{} |
|
234 | - | } |
|
248 | + | // Tell the backend to reconfigure itself |
|
249 | + | af.configureCh <- struct{}{} |
|
235 | 250 | } |
|
236 | 251 | ||
237 | 252 | func (af *AutoRefresh) releaseFetching(url string) { |
346 | 361 | // in a very fast iteration, but we assume here that refreshes happen |
|
347 | 362 | // seldom enough that being able to call one `select{}` with multiple |
|
348 | 363 | // targets / channels outweighs the speed penalty of using reflect. |
|
349 | - | baseSelcases := []reflect.SelectCase{ |
|
350 | - | { |
|
351 | - | Dir: reflect.SelectRecv, |
|
352 | - | Chan: reflect.ValueOf(ctx.Done()), |
|
353 | - | }, |
|
354 | - | { |
|
355 | - | Dir: reflect.SelectRecv, |
|
356 | - | Chan: reflect.ValueOf(af.configureCh), |
|
357 | - | }, |
|
358 | - | { |
|
359 | - | Dir: reflect.SelectRecv, |
|
360 | - | Chan: reflect.ValueOf(af.resetTimerCh), |
|
361 | - | }, |
|
364 | + | // |
|
365 | + | const ( |
|
366 | + | ctxDoneIdx = iota |
|
367 | + | configureIdx |
|
368 | + | resetTimerIdx |
|
369 | + | removeIdx |
|
370 | + | baseSelcasesLen |
|
371 | + | ) |
|
372 | + | ||
373 | + | baseSelcases := make([]reflect.SelectCase, baseSelcasesLen) |
|
374 | + | baseSelcases[ctxDoneIdx] = reflect.SelectCase{ |
|
375 | + | Dir: reflect.SelectRecv, |
|
376 | + | Chan: reflect.ValueOf(ctx.Done()), |
|
377 | + | } |
|
378 | + | baseSelcases[configureIdx] = reflect.SelectCase{ |
|
379 | + | Dir: reflect.SelectRecv, |
|
380 | + | Chan: reflect.ValueOf(af.configureCh), |
|
381 | + | } |
|
382 | + | baseSelcases[resetTimerIdx] = reflect.SelectCase{ |
|
383 | + | Dir: reflect.SelectRecv, |
|
384 | + | Chan: reflect.ValueOf(af.resetTimerCh), |
|
385 | + | } |
|
386 | + | baseSelcases[removeIdx] = reflect.SelectCase{ |
|
387 | + | Dir: reflect.SelectRecv, |
|
388 | + | Chan: reflect.ValueOf(af.removeCh), |
|
362 | 389 | } |
|
363 | - | baseidx := len(baseSelcases) |
|
364 | 390 | ||
365 | 391 | var targets []*target |
|
366 | 392 | var selcases []reflect.SelectCase |
376 | 402 | } |
|
377 | 403 | ||
378 | 404 | if cap(selcases) < len(af.registry) { |
|
379 | - | selcases = make([]reflect.SelectCase, 0, len(af.registry)+baseidx) |
|
405 | + | selcases = make([]reflect.SelectCase, 0, len(af.registry)+baseSelcasesLen) |
|
380 | 406 | } else { |
|
381 | 407 | selcases = selcases[:0] |
|
382 | 408 | } |
393 | 419 | ||
394 | 420 | chosen, recv, recvOK := reflect.Select(selcases) |
|
395 | 421 | switch chosen { |
|
396 | - | case 0: |
|
422 | + | case ctxDoneIdx: |
|
397 | 423 | // <-ctx.Done(). Just bail out of this loop |
|
398 | 424 | return |
|
399 | - | case 1: |
|
425 | + | case configureIdx: |
|
400 | 426 | // <-configureCh. rebuild the select list from the registry. |
|
401 | 427 | // since we're rebuilding everything for each iteration, |
|
402 | 428 | // we just need to start the loop all over again |
|
403 | 429 | continue |
|
404 | - | case 2: |
|
430 | + | case resetTimerIdx: |
|
405 | 431 | // <-resetTimerCh. interrupt polling, and reset the timer on |
|
406 | 432 | // a single target. this needs to be handled inside this select |
|
407 | 433 | if !recvOK { |
418 | 444 | } |
|
419 | 445 | } |
|
420 | 446 | t.timer.Reset(d) |
|
447 | + | case removeIdx: |
|
448 | + | // <-removeCh. remove the URL from future fetching |
|
449 | + | //nolint:forcetypeassert |
|
450 | + | req := recv.Interface().(removeReq) |
|
451 | + | replyCh := req.replyCh |
|
452 | + | url := req.url |
|
453 | + | af.muRegistry.Lock() |
|
454 | + | if _, ok := af.registry[url]; !ok { |
|
455 | + | replyCh <- errors.Errorf(`invalid url %q (not registered)`, url) |
|
456 | + | } else { |
|
457 | + | delete(af.registry, url) |
|
458 | + | replyCh <- nil |
|
459 | + | } |
|
460 | + | af.muRegistry.Unlock() |
|
421 | 461 | default: |
|
422 | 462 | // Do not fire a refresh in case the channel was closed. |
|
423 | 463 | if !recvOK { |
|
424 | 464 | continue |
|
425 | 465 | } |
|
426 | 466 | ||
427 | 467 | // Time to refresh a target |
|
428 | - | t := targets[chosen-baseidx] |
|
468 | + | t := targets[chosen-baseSelcasesLen] |
|
429 | 469 | ||
430 | 470 | // Check if there are other goroutines still doing the refresh asynchronously. |
|
431 | 471 | // This could happen if the refreshing goroutine is stuck on a backoff |
457 | 497 | ||
458 | 498 | // In case the refresh fails due to errors in fetching/parsing the JWKS, |
|
459 | 499 | // we want to retry. Create a backoff object, |
|
460 | - | ||
461 | - | options := []FetchOption{WithHTTPClient(t.httpcl)} |
|
500 | + | parseOptions := t.parseOptions |
|
501 | + | fetchOptions := []FetchOption{WithHTTPClient(t.httpcl)} |
|
462 | 502 | if enableBackoff { |
|
463 | - | options = append(options, WithFetchBackoff(t.backoff)) |
|
503 | + | fetchOptions = append(fetchOptions, WithFetchBackoff(t.backoff)) |
|
464 | 504 | } |
|
465 | 505 | if t.wl != nil { |
|
466 | - | options = append(options, WithFetchWhitelist(t.wl)) |
|
506 | + | fetchOptions = append(fetchOptions, WithFetchWhitelist(t.wl)) |
|
467 | 507 | } |
|
468 | 508 | ||
469 | - | res, err := fetch(ctx, url, options...) |
|
509 | + | res, err := fetch(ctx, url, fetchOptions...) |
|
470 | 510 | if err == nil { |
|
471 | 511 | if res.StatusCode != http.StatusOK { |
|
472 | 512 | // now, can there be a remote resource that responds with a status code |
|
473 | 513 | // other than 200 and still be valid...? naaaaaaahhhhhh.... |
|
474 | 514 | err = errors.Errorf(`bad response status code (%d)`, res.StatusCode) |
|
475 | 515 | } else { |
|
476 | 516 | defer res.Body.Close() |
|
477 | - | keyset, parseErr := ParseReader(res.Body) |
|
517 | + | keyset, parseErr := ParseReader(res.Body, parseOptions...) |
|
478 | 518 | if parseErr == nil { |
|
479 | 519 | // Got a new key set. replace the keyset in the target |
|
480 | 520 | af.muCache.Lock() |
63 | 63 | return &verifyOption{option.New(identMessage{}, m)} |
|
64 | 64 | } |
|
65 | 65 | ||
66 | - | // WithDetachedPayload can be used to verify a JWS message with a |
|
67 | - | // detached payload. If you have to verify using this option, you should |
|
68 | - | // know exactly how and why this works. |
|
69 | - | func WithDetachedPayload(v []byte) VerifyOption { |
|
70 | - | return &verifyOption{option.New(identDetachedPayload{}, v)} |
|
66 | + | type SignVerifyOption interface { |
|
67 | + | SignOption |
|
68 | + | VerifyOption |
|
69 | + | } |
|
70 | + | ||
71 | + | type signVerifyOption struct { |
|
72 | + | Option |
|
73 | + | } |
|
74 | + | ||
75 | + | func (*signVerifyOption) signOption() {} |
|
76 | + | func (*signVerifyOption) verifyOption() {} |
|
77 | + | ||
78 | + | // WithDetachedPayload can be used to both sign or verify a JWS message with a |
|
79 | + | // detached payload. |
|
80 | + | // |
|
81 | + | // When this option is used for `jws.Sign()`, the first parameter (normally the payload) |
|
82 | + | // must be set to `nil`. |
|
83 | + | // |
|
84 | + | // If you have to verify using this option, you should know exactly how and why this works. |
|
85 | + | func WithDetachedPayload(v []byte) SignVerifyOption { |
|
86 | + | return &signVerifyOption{option.New(identDetachedPayload{}, v)} |
|
71 | 87 | } |
|
72 | 88 | ||
73 | 89 | // WithFetchWhitelist specifies the whitelist object to be passed |
41 | 41 | x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 |
|
42 | 42 | privateParams map[string]interface{} |
|
43 | 43 | mu *sync.RWMutex |
|
44 | - | dc DecodeCtx |
|
44 | + | dc json.DecodeCtx |
|
45 | 45 | } |
|
46 | 46 | ||
47 | 47 | func NewSymmetricKey() SymmetricKey { |
340 | 340 | return cloneKey(k) |
|
341 | 341 | } |
|
342 | 342 | ||
343 | - | func (k *symmetricKey) DecodeCtx() DecodeCtx { |
|
343 | + | func (k *symmetricKey) DecodeCtx() json.DecodeCtx { |
|
344 | 344 | k.mu.RLock() |
|
345 | 345 | defer k.mu.RUnlock() |
|
346 | 346 | return k.dc |
|
347 | 347 | } |
|
348 | 348 | ||
349 | - | func (k *symmetricKey) SetDecodeCtx(dc DecodeCtx) { |
|
349 | + | func (k *symmetricKey) SetDecodeCtx(dc json.DecodeCtx) { |
|
350 | 350 | k.mu.Lock() |
|
351 | 351 | defer k.mu.Unlock() |
|
352 | 352 | k.dc = dc |
41 | 41 | // jwt.Token needs to handle private claims, and this really does not |
|
42 | 42 | // work well when it is embedded in other structure |
|
43 | 43 | type Token interface { |
|
44 | + | ||
45 | + | // Audience returns the value for "aud" field of the token |
|
44 | 46 | Audience() []string |
|
47 | + | ||
48 | + | // Expiration returns the value for "exp" field of the token |
|
45 | 49 | Expiration() time.Time |
|
50 | + | ||
51 | + | // IssuedAt returns the value for "iat" field of the token |
|
46 | 52 | IssuedAt() time.Time |
|
53 | + | ||
54 | + | // Issuer returns the value for "iss" field of the token |
|
47 | 55 | Issuer() string |
|
56 | + | ||
57 | + | // JwtID returns the value for "jti" field of the token |
|
48 | 58 | JwtID() string |
|
59 | + | ||
60 | + | // NotBefore returns the value for "nbf" field of the token |
|
49 | 61 | NotBefore() time.Time |
|
62 | + | ||
63 | + | // Subject returns the value for "sub" field of the token |
|
50 | 64 | Subject() string |
|
65 | + | ||
66 | + | // PrivateClaims return the entire set of fields (claims) in the token |
|
67 | + | // *other* than the pre-defined fields such as `iss`, `nbf`, `iat`, etc. |
|
51 | 68 | PrivateClaims() map[string]interface{} |
|
69 | + | ||
70 | + | // Get returns the value of the corresponding field in the token, such as |
|
71 | + | // `nbf`, `exp`, `iat`, and other user-defined fields. If the field does not |
|
72 | + | // exist in the token, the second return value will be `false` |
|
73 | + | // |
|
74 | + | // If you need to access fields like `alg`, `kid`, `jku`, etc, you need |
|
75 | + | // to access the corresponding fields in the JWS/JWE message. For this, |
|
76 | + | // you will need to access them by directly parsing the payload using |
|
77 | + | // `jws.Parse` and `jwe.Parse` |
|
52 | 78 | Get(string) (interface{}, bool) |
|
79 | + | ||
80 | + | // Set assigns a value to the corresponding field in the token. Some |
|
81 | + | // pre-defined fields such as `nbf`, `iat`, `iss` need their values to |
|
82 | + | // be of a specific type. See the other getter methods in this interface |
|
83 | + | // for the types of each of these fields |
|
53 | 84 | Set(string, interface{}) error |
|
54 | 85 | Remove(string) error |
|
55 | 86 | Clone() (Token, error) |
114 | 114 | } |
|
115 | 115 | ||
116 | 116 | var pubkey rsa.PublicKey |
|
117 | - | if err := keyconv.RSAPublicKey(&pubkey, key); err != nil { |
|
118 | - | return errors.Wrapf(err, `failed to retrieve rsa.PublicKey out of %T`, key) |
|
117 | + | if cs, ok := key.(crypto.Signer); ok { |
|
118 | + | cpub := cs.Public() |
|
119 | + | switch cpub := cpub.(type) { |
|
120 | + | case rsa.PublicKey: |
|
121 | + | pubkey = cpub |
|
122 | + | case *rsa.PublicKey: |
|
123 | + | pubkey = *cpub |
|
124 | + | default: |
|
125 | + | return errors.Errorf(`failed to retrieve rsa.PublicKey out of crypto.Signer %T`, key) |
|
126 | + | } |
|
127 | + | } else { |
|
128 | + | if err := keyconv.RSAPublicKey(&pubkey, key); err != nil { |
|
129 | + | return errors.Wrapf(err, `failed to retrieve rsa.PublicKey out of %T`, key) |
|
130 | + | } |
|
119 | 131 | } |
|
120 | 132 | ||
121 | 133 | h := rv.hash.New() |
Learn more Showing 6 files with coverage changes found.
jws/es256k.go
jwk/rsa_gen.go
jwt/jwt.go
jwk/jwk.go
jwk/refresh.go
jwe/encrypt.go
Files | Coverage |
---|---|
internal | 80.92% |
jwa | 100.00% |
jwe | -0.05% 64.16% |
jwk | 0.61% 73.89% |
jws | -1.21% 74.78% |
jwt | 74.56% |
cmd/jwx/jwx.go | 100.00% |
format.go | 84.62% |
formatkind_string_gen.go | 100.00% |
options.go | 66.67% |
x25519/x25519.go | 82.35% |
Project Totals (84 files) | 72.84% |
20c50b0
7b059a0
0f4c0c3
b88feb7
3fa12c9
9128896
1c0a87f
74cf7c6
6f761d9
ceca532
9586e44
8ea97be
8e20b81
ff93788
1684a12
8cb4232
dec6c0e
a01ed91
bb011b5
7e7fb5d
11e868e
10ea65a
9827619
1133b97
2ab72de
5f7b6f6
8e65abb
86fa0b5
b9239e0
653ee23
a431e5d
300b79a
cbd1104
c25f6f4
d65b4b8
0cd4202
b361f31
2d9ca15
cd4bd64
5ded25f
3774a6e
a875dcf
e557ed2
b6506af
c34daa3
e2ff675
95e922d
5e16e0d
223ae00
53ec854
96b7380
2356e0e
878d719
b1febef
224421f
15e11b0
#523
3b7a676