forked from mystiq/dex
vendor: revendor
This commit is contained in:
parent
522749b5d8
commit
a876ab37af
192 changed files with 12003 additions and 18629 deletions
16
glide.lock
generated
16
glide.lock
generated
|
@ -1,12 +1,12 @@
|
||||||
hash: bc7fa6bbfddcb39710064a9d6dab090d49bd88486571cae12c68c4b70093e716
|
hash: c3530f2a60a64c2efc4c3ac499fcd15f79de2a532715ba2b9841c1d404942b2e
|
||||||
updated: 2016-11-03T14:31:37.323302694-07:00
|
updated: 2016-11-17T15:18:56.701287533-08:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/cockroachdb/cockroach-go
|
- name: github.com/cockroachdb/cockroach-go
|
||||||
version: 31611c0501c812f437d4861d87d117053967c955
|
version: 31611c0501c812f437d4861d87d117053967c955
|
||||||
subpackages:
|
subpackages:
|
||||||
- crdb
|
- crdb
|
||||||
- name: github.com/ericchiang/oidc
|
- name: github.com/coreos/go-oidc
|
||||||
version: 1907f0e61549f9081f26bdf269f11603496c9dee
|
version: 5a7f09ab5787e846efa7f56f4a08b6d6926d08c4
|
||||||
- name: github.com/ghodss/yaml
|
- name: github.com/ghodss/yaml
|
||||||
version: bea76d6a4713e18b7f5321a2b020738552def3ea
|
version: bea76d6a4713e18b7f5321a2b020738552def3ea
|
||||||
- name: github.com/go-sql-driver/mysql
|
- name: github.com/go-sql-driver/mysql
|
||||||
|
@ -52,6 +52,7 @@ imports:
|
||||||
version: 6a513affb38dc9788b449d59ffed099b8de18fa0
|
version: 6a513affb38dc9788b449d59ffed099b8de18fa0
|
||||||
subpackages:
|
subpackages:
|
||||||
- context
|
- context
|
||||||
|
- context/ctxhttp
|
||||||
- http2
|
- http2
|
||||||
- http2/hpack
|
- http2/hpack
|
||||||
- internal/timeseries
|
- internal/timeseries
|
||||||
|
@ -87,13 +88,8 @@ imports:
|
||||||
version: 4e86f4367175e39f69d9358a5f17b4dda270378d
|
version: 4e86f4367175e39f69d9358a5f17b4dda270378d
|
||||||
- name: gopkg.in/ldap.v2
|
- name: gopkg.in/ldap.v2
|
||||||
version: 0e7db8eb77695b5a952f0e5d78df9ab160050c73
|
version: 0e7db8eb77695b5a952f0e5d78df9ab160050c73
|
||||||
- name: gopkg.in/square/go-jose.v1
|
|
||||||
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
|
|
||||||
subpackages:
|
|
||||||
- cipher
|
|
||||||
- json
|
|
||||||
- name: gopkg.in/square/go-jose.v2
|
- name: gopkg.in/square/go-jose.v2
|
||||||
version: f209f41628247c56938cb20ef51d589ddad6c30b
|
version: 8c5257b2f658f86d174ae68c6a592eaf6a9608d9
|
||||||
subpackages:
|
subpackages:
|
||||||
- cipher
|
- cipher
|
||||||
- json
|
- json
|
||||||
|
|
2
vendor/github.com/coreos/go-oidc/.gitignore
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/bin
|
||||||
|
/gopath
|
16
vendor/github.com/coreos/go-oidc/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/coreos/go-oidc/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7.3
|
||||||
|
- 1.6.3
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -v -t github.com/coreos/go-oidc
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/golang/lint/golint
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./test
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
71
vendor/github.com/coreos/go-oidc/CONTRIBUTING.md
generated
vendored
Normal file
71
vendor/github.com/coreos/go-oidc/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# How to Contribute
|
||||||
|
|
||||||
|
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
|
||||||
|
GitHub pull requests. This document outlines some of the conventions on
|
||||||
|
development workflow, commit message formatting, contact points and other
|
||||||
|
resources to make it easier to get your contribution accepted.
|
||||||
|
|
||||||
|
# Certificate of Origin
|
||||||
|
|
||||||
|
By contributing to this project you agree to the Developer Certificate of
|
||||||
|
Origin (DCO). This document was created by the Linux Kernel community and is a
|
||||||
|
simple statement that you, as a contributor, have the legal right to make the
|
||||||
|
contribution. See the [DCO](DCO) file for details.
|
||||||
|
|
||||||
|
# Email and Chat
|
||||||
|
|
||||||
|
The project currently uses the general CoreOS email list and IRC channel:
|
||||||
|
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
|
||||||
|
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
|
||||||
|
|
||||||
|
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
|
||||||
|
are very busy and read the mailing lists.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
- Fork the repository on GitHub
|
||||||
|
- Read the [README](README.md) for build and test instructions
|
||||||
|
- Play with the project, submit bugs, submit patches!
|
||||||
|
|
||||||
|
## Contribution Flow
|
||||||
|
|
||||||
|
This is a rough outline of what a contributor's workflow looks like:
|
||||||
|
|
||||||
|
- Create a topic branch from where you want to base your work (usually master).
|
||||||
|
- Make commits of logical units.
|
||||||
|
- Make sure your commit messages are in the proper format (see below).
|
||||||
|
- Push your changes to a topic branch in your fork of the repository.
|
||||||
|
- Make sure the tests pass, and add any new tests as appropriate.
|
||||||
|
- Submit a pull request to the original repository.
|
||||||
|
|
||||||
|
Thanks for your contributions!
|
||||||
|
|
||||||
|
### Format of the Commit Message
|
||||||
|
|
||||||
|
We follow a rough convention for commit messages that is designed to answer two
|
||||||
|
questions: what changed and why. The subject line should feature the what and
|
||||||
|
the body of the commit should describe the why.
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts: add the test-cluster command
|
||||||
|
|
||||||
|
this uses tmux to setup a test cluster that you can easily kill and
|
||||||
|
start for debugging.
|
||||||
|
|
||||||
|
Fixes #38
|
||||||
|
```
|
||||||
|
|
||||||
|
The format can be described more formally as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<subsystem>: <what changed>
|
||||||
|
<BLANK LINE>
|
||||||
|
<why this change was made>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The first line is the subject and should be no longer than 70 characters, the
|
||||||
|
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||||
|
This allows the message to be easier to read on GitHub as well as in various
|
||||||
|
git tools.
|
36
vendor/github.com/coreos/go-oidc/DCO
generated
vendored
Normal file
36
vendor/github.com/coreos/go-oidc/DCO
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
660 York Street, Suite 102,
|
||||||
|
San Francisco, CA 94110 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
0
vendor/github.com/ericchiang/oidc/LICENSE → vendor/github.com/coreos/go-oidc/LICENSE
generated
vendored
0
vendor/github.com/ericchiang/oidc/LICENSE → vendor/github.com/coreos/go-oidc/LICENSE
generated
vendored
3
vendor/github.com/coreos/go-oidc/MAINTAINERS
generated
vendored
Normal file
3
vendor/github.com/coreos/go-oidc/MAINTAINERS
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Bobby Rullo <bobby.rullo@coreos.com> (@bobbyrullo)
|
||||||
|
Ed Rooth <ed.rooth@coreos.com> (@sym3tri)
|
||||||
|
Eric Chiang <eric.chiang@coreos.com> (@ericchiang)
|
5
vendor/github.com/coreos/go-oidc/NOTICE
generated
vendored
Normal file
5
vendor/github.com/coreos/go-oidc/NOTICE
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CoreOS Project
|
||||||
|
Copyright 2014 CoreOS, Inc
|
||||||
|
|
||||||
|
This product includes software developed at CoreOS, Inc.
|
||||||
|
(http://www.coreos.com/).
|
72
vendor/github.com/coreos/go-oidc/README.md
generated
vendored
Normal file
72
vendor/github.com/coreos/go-oidc/README.md
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
# go-oidc
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/coreos/go-oidc?status.svg)](https://godoc.org/github.com/coreos/go-oidc)
|
||||||
|
[![Build Status](https://travis-ci.org/coreos/go-oidc.png?branch=master)](https://travis-ci.org/coreos/go-oidc)
|
||||||
|
|
||||||
|
## OpenID Connect support for Go
|
||||||
|
|
||||||
|
This package enables OpenID Connect support for the [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) package.
|
||||||
|
|
||||||
|
```go
|
||||||
|
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure an OpenID Connect aware OAuth2 client.
|
||||||
|
oauth2Config := oauth2.Config{
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
|
||||||
|
// Discovery returns the OAuth2 endpoints.
|
||||||
|
Endpoint: provider.Endpoint(),
|
||||||
|
|
||||||
|
// "openid" is a required scope for OpenID Connect flows.
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
OAuth2 redirects are unchanged.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The on responses, the provider can be used to verify ID Tokens.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var verifier = provider.Verifier()
|
||||||
|
|
||||||
|
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Verify state and errors.
|
||||||
|
|
||||||
|
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the ID Token from OAuth2 token.
|
||||||
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
// handle missing token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and verify ID Token payload.
|
||||||
|
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract custom claims
|
||||||
|
var claims struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Verified bool `json:"email_verified"`
|
||||||
|
}
|
||||||
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
21
vendor/github.com/coreos/go-oidc/example/README.md
generated
vendored
Normal file
21
vendor/github.com/coreos/go-oidc/example/README.md
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
These are example uses of the oidc package. Each requires a Google account and the client ID and secret of a registered OAuth2 application. To create one:
|
||||||
|
|
||||||
|
1. Visit your [Google Developer Console][google-developer-console].
|
||||||
|
2. Click "Credentials" on the left column.
|
||||||
|
3. Click the "Create credentials" button followed by "OAuth client ID".
|
||||||
|
4. Select "Web application" and add "http://127.0.0.1:5556/auth/google/callback" as an authorized redirect URI.
|
||||||
|
5. Click create and add the printed client ID and secret to your environment using the following variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
GOOGLE_OAUTH2_CLIENT_ID
|
||||||
|
GOOGLE_OAUTH2_CLIENT_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally run the examples using the Go tool and navigate to http://127.0.0.1:5556.
|
||||||
|
|
||||||
|
```
|
||||||
|
go run ./examples/idtoken/app.go
|
||||||
|
```
|
||||||
|
[google-developer-console]: https://console.developers.google.com/apis/dashboard
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ericchiang/oidc"
|
oidc "github.com/coreos/go-oidc"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
@ -27,7 +27,7 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
verifier := provider.NewVerifier(ctx)
|
verifier := provider.Verifier()
|
||||||
|
|
||||||
config := oauth2.Config{
|
config := oauth2.Config{
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
|
@ -59,7 +59,7 @@ func main() {
|
||||||
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
idToken, err := verifier.Verify(rawIDToken)
|
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ericchiang/oidc"
|
oidc "github.com/coreos/go-oidc"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
@ -42,7 +42,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the nonce source to create a custom ID Token verifier.
|
// Use the nonce source to create a custom ID Token verifier.
|
||||||
nonceEnabledVerifier := provider.NewVerifier(ctx, oidc.VerifyNonce(nonceSource{}))
|
nonceEnabledVerifier := provider.Verifier(oidc.VerifyNonce(nonceSource{}))
|
||||||
|
|
||||||
config := oauth2.Config{
|
config := oauth2.Config{
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
|
@ -76,7 +76,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Verify the ID Token signature and nonce.
|
// Verify the ID Token signature and nonce.
|
||||||
idToken, err := nonceEnabledVerifier.Verify(rawIDToken)
|
idToken, err := nonceEnabledVerifier.Verify(ctx, rawIDToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ericchiang/oidc"
|
oidc "github.com/coreos/go-oidc"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
150
vendor/github.com/coreos/go-oidc/gen.go
generated
vendored
Normal file
150
vendor/github.com/coreos/go-oidc/gen.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// This file is used to generate keys for tests.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type key struct {
|
||||||
|
name string
|
||||||
|
new func() (crypto.Signer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = []key{
|
||||||
|
{
|
||||||
|
"ECDSA_256", func() (crypto.Signer, error) {
|
||||||
|
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ECDSA_384", func() (crypto.Signer, error) {
|
||||||
|
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ECDSA_521", func() (crypto.Signer, error) {
|
||||||
|
return ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RSA_1024", func() (crypto.Signer, error) {
|
||||||
|
return rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RSA_2048", func() (crypto.Signer, error) {
|
||||||
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RSA_4096", func() (crypto.Signer, error) {
|
||||||
|
return rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJWK(k key, prefix, ident string) (privBytes, pubBytes []byte, err error) {
|
||||||
|
priv, err := k.new()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("generate %s: %v", k.name, err)
|
||||||
|
}
|
||||||
|
pub := priv.Public()
|
||||||
|
|
||||||
|
privKey := &jose.JSONWebKey{Key: priv}
|
||||||
|
thumbprint, err := privKey.Thumbprint(crypto.SHA256)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("computing thumbprint: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyID := hex.EncodeToString(thumbprint)
|
||||||
|
privKey.KeyID = keyID
|
||||||
|
pubKey := &jose.JSONWebKey{Key: pub, KeyID: keyID}
|
||||||
|
|
||||||
|
privBytes, err = json.MarshalIndent(privKey, prefix, ident)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pubBytes, err = json.MarshalIndent(pubKey, prefix, ident)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyData struct {
|
||||||
|
Name string
|
||||||
|
Priv string
|
||||||
|
Pub string
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmpl = template.Must(template.New("").Parse(`// +build !golint
|
||||||
|
|
||||||
|
// This file contains statically created JWKs for tests created by gen.go
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustLoadJWK(s string) jose.JSONWebKey {
|
||||||
|
var jwk jose.JSONWebKey
|
||||||
|
if err := json.Unmarshal([]byte(s), &jwk); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return jwk
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
{{- range $i, $key := .Keys }}
|
||||||
|
testKey{{ $key.Name }} = mustLoadJWK(` + "`" + `{{ $key.Pub }}` + "`" + `)
|
||||||
|
testKey{{ $key.Name }}_Priv = mustLoadJWK(` + "`" + `{{ $key.Priv }}` + "`" + `)
|
||||||
|
{{ end -}}
|
||||||
|
)
|
||||||
|
`))
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var tmplData struct {
|
||||||
|
Keys []keyData
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
log.Printf("generating %s", k.name)
|
||||||
|
priv, pub, err := newJWK(k, "\t", "\t")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
name := fmt.Sprintf("%s_%d", k.name, i)
|
||||||
|
|
||||||
|
tmplData.Keys = append(tmplData.Keys, keyData{
|
||||||
|
Name: name,
|
||||||
|
Priv: string(priv),
|
||||||
|
Pub: string(pub),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
if err := tmpl.Execute(buff, tmplData); err != nil {
|
||||||
|
log.Fatalf("excuting template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile("jose_test.go", buff.Bytes(), 0644); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
7
vendor/github.com/coreos/go-oidc/http/client.go
generated
vendored
Normal file
7
vendor/github.com/coreos/go-oidc/http/client.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
Do(*http.Request) (*http.Response, error)
|
||||||
|
}
|
2
vendor/github.com/coreos/go-oidc/http/doc.go
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/http/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package http is DEPRECATED. Use net/http instead.
|
||||||
|
package http
|
156
vendor/github.com/coreos/go-oidc/http/http.go
generated
vendored
Normal file
156
vendor/github.com/coreos/go-oidc/http/http.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteError(w http.ResponseWriter, code int, msg string) {
|
||||||
|
e := struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}{
|
||||||
|
Error: msg,
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(e)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("go-oidc: failed to marshal %#v: %v", e, err)
|
||||||
|
code = http.StatusInternalServerError
|
||||||
|
b = []byte(`{"error":"server_error"}`)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(code)
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicAuth parses a username and password from the request's
|
||||||
|
// Authorization header. This was pulled from golang master:
|
||||||
|
// https://codereview.appspot.com/76540043
|
||||||
|
func BasicAuth(r *http.Request) (username, password string, ok bool) {
|
||||||
|
auth := r.Header.Get("Authorization")
|
||||||
|
if auth == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(auth, "Basic ") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic "))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cs := string(c)
|
||||||
|
s := strings.IndexByte(cs, ':')
|
||||||
|
if s < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return cs[:s], cs[s+1:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheControlMaxAge(hdr string) (time.Duration, bool, error) {
|
||||||
|
for _, field := range strings.Split(hdr, ",") {
|
||||||
|
parts := strings.SplitN(strings.TrimSpace(field), "=", 2)
|
||||||
|
k := strings.ToLower(strings.TrimSpace(parts[0]))
|
||||||
|
if k != "max-age" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 1 {
|
||||||
|
return 0, false, errors.New("max-age has no value")
|
||||||
|
}
|
||||||
|
|
||||||
|
v := strings.TrimSpace(parts[1])
|
||||||
|
if v == "" {
|
||||||
|
return 0, false, errors.New("max-age has empty value")
|
||||||
|
}
|
||||||
|
|
||||||
|
age, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if age <= 0 {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Duration(age) * time.Second, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expires(date, expires string) (time.Duration, bool, error) {
|
||||||
|
if date == "" || expires == "" {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
te, err := time.Parse(time.RFC1123, expires)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
td, err := time.Parse(time.RFC1123, date)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl := te.Sub(td)
|
||||||
|
|
||||||
|
// headers indicate data already expired, caller should not
|
||||||
|
// have to care about this case
|
||||||
|
if ttl <= 0 {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ttl, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cacheable(hdr http.Header) (time.Duration, bool, error) {
|
||||||
|
ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control"))
|
||||||
|
if err != nil || ok {
|
||||||
|
return ttl, ok, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return expires(hdr.Get("Date"), hdr.Get("Expires"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeQuery appends additional query values to an existing URL.
|
||||||
|
func MergeQuery(u url.URL, q url.Values) url.URL {
|
||||||
|
uv := u.Query()
|
||||||
|
for k, vs := range q {
|
||||||
|
for _, v := range vs {
|
||||||
|
uv.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.RawQuery = uv.Encode()
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResourceLocation appends a resource id to the end of the requested URL path.
|
||||||
|
func NewResourceLocation(reqURL *url.URL, id string) string {
|
||||||
|
var u url.URL
|
||||||
|
u = *reqURL
|
||||||
|
u.Path = path.Join(u.Path, id)
|
||||||
|
u.RawQuery = ""
|
||||||
|
u.Fragment = ""
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyRequest returns a clone of the provided *http.Request.
|
||||||
|
// The returned object is a shallow copy of the struct and a
|
||||||
|
// deep copy of its Header field.
|
||||||
|
func CopyRequest(r *http.Request) *http.Request {
|
||||||
|
r2 := *r
|
||||||
|
r2.Header = make(http.Header)
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = s
|
||||||
|
}
|
||||||
|
return &r2
|
||||||
|
}
|
380
vendor/github.com/coreos/go-oidc/http/http_test.go
generated
vendored
Normal file
380
vendor/github.com/coreos/go-oidc/http/http_test.go
generated
vendored
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCacheControlMaxAgeSuccess(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
hdr string
|
||||||
|
wantAge time.Duration
|
||||||
|
wantOK bool
|
||||||
|
}{
|
||||||
|
{"max-age=12", 12 * time.Second, true},
|
||||||
|
{"max-age=-12", 0, false},
|
||||||
|
{"max-age=0", 0, false},
|
||||||
|
{"public, max-age=12", 12 * time.Second, true},
|
||||||
|
{"public, max-age=40192, must-revalidate", 40192 * time.Second, true},
|
||||||
|
{"public, not-max-age=12, must-revalidate", time.Duration(0), false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
maxAge, ok, err := cacheControlMaxAge(tt.hdr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: err=%v", i, err)
|
||||||
|
}
|
||||||
|
if tt.wantAge != maxAge {
|
||||||
|
t.Errorf("case %d: want=%d got=%d", i, tt.wantAge, maxAge)
|
||||||
|
}
|
||||||
|
if tt.wantOK != ok {
|
||||||
|
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheControlMaxAgeFail(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"max-age=aasdf",
|
||||||
|
"max-age=",
|
||||||
|
"max-age",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
_, ok, err := cacheControlMaxAge(tt)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("case %d: want ok=false, got true", i)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: want non-nil err", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeQuery(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
u string
|
||||||
|
q url.Values
|
||||||
|
w string
|
||||||
|
}{
|
||||||
|
// No values
|
||||||
|
{
|
||||||
|
u: "http://example.com",
|
||||||
|
q: nil,
|
||||||
|
w: "http://example.com",
|
||||||
|
},
|
||||||
|
// No additional values
|
||||||
|
{
|
||||||
|
u: "http://example.com?foo=bar",
|
||||||
|
q: nil,
|
||||||
|
w: "http://example.com?foo=bar",
|
||||||
|
},
|
||||||
|
// Simple addition
|
||||||
|
{
|
||||||
|
u: "http://example.com",
|
||||||
|
q: url.Values{
|
||||||
|
"foo": []string{"bar"},
|
||||||
|
},
|
||||||
|
w: "http://example.com?foo=bar",
|
||||||
|
},
|
||||||
|
// Addition with existing values
|
||||||
|
{
|
||||||
|
u: "http://example.com?dog=boo",
|
||||||
|
q: url.Values{
|
||||||
|
"foo": []string{"bar"},
|
||||||
|
},
|
||||||
|
w: "http://example.com?dog=boo&foo=bar",
|
||||||
|
},
|
||||||
|
// Merge
|
||||||
|
{
|
||||||
|
u: "http://example.com?dog=boo",
|
||||||
|
q: url.Values{
|
||||||
|
"dog": []string{"elroy"},
|
||||||
|
},
|
||||||
|
w: "http://example.com?dog=boo&dog=elroy",
|
||||||
|
},
|
||||||
|
// Add and merge
|
||||||
|
{
|
||||||
|
u: "http://example.com?dog=boo",
|
||||||
|
q: url.Values{
|
||||||
|
"dog": []string{"elroy"},
|
||||||
|
"foo": []string{"bar"},
|
||||||
|
},
|
||||||
|
w: "http://example.com?dog=boo&dog=elroy&foo=bar",
|
||||||
|
},
|
||||||
|
// Multivalue merge
|
||||||
|
{
|
||||||
|
u: "http://example.com?dog=boo",
|
||||||
|
q: url.Values{
|
||||||
|
"dog": []string{"elroy", "penny"},
|
||||||
|
},
|
||||||
|
w: "http://example.com?dog=boo&dog=elroy&dog=penny",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
ur, err := url.Parse(tt.u)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: failed parsing test url: %v, error: %v", i, tt.u, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := MergeQuery(*ur, tt.q)
|
||||||
|
want, err := url.Parse(tt.w)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: failed parsing want url: %v, error: %v", i, tt.w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(*want, got) {
|
||||||
|
t.Errorf("case %d: want: %v, got: %v", i, *want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpiresPass(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
date string
|
||||||
|
exp string
|
||||||
|
wantTTL time.Duration
|
||||||
|
wantOK bool
|
||||||
|
}{
|
||||||
|
// Expires and Date properly set
|
||||||
|
{
|
||||||
|
date: "Thu, 01 Dec 1983 22:00:00 GMT",
|
||||||
|
exp: "Fri, 02 Dec 1983 01:00:00 GMT",
|
||||||
|
wantTTL: 10800 * time.Second,
|
||||||
|
wantOK: true,
|
||||||
|
},
|
||||||
|
// empty headers
|
||||||
|
{
|
||||||
|
date: "",
|
||||||
|
exp: "",
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
// lack of Expirs short-ciruits Date parsing
|
||||||
|
{
|
||||||
|
date: "foo",
|
||||||
|
exp: "",
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
// lack of Date short-ciruits Expires parsing
|
||||||
|
{
|
||||||
|
date: "",
|
||||||
|
exp: "foo",
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
// no Date
|
||||||
|
{
|
||||||
|
exp: "Thu, 01 Dec 1983 22:00:00 GMT",
|
||||||
|
wantTTL: 0,
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
// no Expires
|
||||||
|
{
|
||||||
|
date: "Thu, 01 Dec 1983 22:00:00 GMT",
|
||||||
|
wantTTL: 0,
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
// Expires < Date
|
||||||
|
{
|
||||||
|
date: "Fri, 02 Dec 1983 01:00:00 GMT",
|
||||||
|
exp: "Thu, 01 Dec 1983 22:00:00 GMT",
|
||||||
|
wantTTL: 0,
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
ttl, ok, err := expires(tt.date, tt.exp)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: err=%v", i, err)
|
||||||
|
}
|
||||||
|
if tt.wantTTL != ttl {
|
||||||
|
t.Errorf("case %d: want=%d got=%d", i, tt.wantTTL, ttl)
|
||||||
|
}
|
||||||
|
if tt.wantOK != ok {
|
||||||
|
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpiresFail(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
date string
|
||||||
|
exp string
|
||||||
|
}{
|
||||||
|
// malformed Date header
|
||||||
|
{
|
||||||
|
date: "foo",
|
||||||
|
exp: "Fri, 02 Dec 1983 01:00:00 GMT",
|
||||||
|
},
|
||||||
|
// malformed exp header
|
||||||
|
{
|
||||||
|
date: "Fri, 02 Dec 1983 01:00:00 GMT",
|
||||||
|
exp: "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
_, _, err := expires(tt.date, tt.exp)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: expected non-nil error", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheablePass(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
headers http.Header
|
||||||
|
wantTTL time.Duration
|
||||||
|
wantOK bool
|
||||||
|
}{
|
||||||
|
// valid Cache-Control
|
||||||
|
{
|
||||||
|
headers: http.Header{
|
||||||
|
"Cache-Control": []string{"max-age=100"},
|
||||||
|
},
|
||||||
|
wantTTL: 100 * time.Second,
|
||||||
|
wantOK: true,
|
||||||
|
},
|
||||||
|
// valid Date/Expires
|
||||||
|
{
|
||||||
|
headers: http.Header{
|
||||||
|
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
|
||||||
|
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
|
||||||
|
},
|
||||||
|
wantTTL: 10800 * time.Second,
|
||||||
|
wantOK: true,
|
||||||
|
},
|
||||||
|
// Cache-Control supersedes Date/Expires
|
||||||
|
{
|
||||||
|
headers: http.Header{
|
||||||
|
"Cache-Control": []string{"max-age=100"},
|
||||||
|
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
|
||||||
|
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
|
||||||
|
},
|
||||||
|
wantTTL: 100 * time.Second,
|
||||||
|
wantOK: true,
|
||||||
|
},
|
||||||
|
// no caching headers
|
||||||
|
{
|
||||||
|
headers: http.Header{},
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
ttl, ok, err := Cacheable(tt.headers)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: err=%v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.wantTTL != ttl {
|
||||||
|
t.Errorf("case %d: want=%d got=%d", i, tt.wantTTL, ttl)
|
||||||
|
}
|
||||||
|
if tt.wantOK != ok {
|
||||||
|
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheableFail(t *testing.T) {
|
||||||
|
tests := []http.Header{
|
||||||
|
// invalid Cache-Control short-circuits
|
||||||
|
http.Header{
|
||||||
|
"Cache-Control": []string{"max-age"},
|
||||||
|
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
|
||||||
|
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
|
||||||
|
},
|
||||||
|
// no Cache-Control, invalid Expires
|
||||||
|
http.Header{
|
||||||
|
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
|
||||||
|
"Expires": []string{"boo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
_, _, err := Cacheable(tt)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: want non-nil err", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewResourceLocation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
ru *url.URL
|
||||||
|
id string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ru: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "example.com",
|
||||||
|
},
|
||||||
|
id: "foo",
|
||||||
|
want: "http://example.com/foo",
|
||||||
|
},
|
||||||
|
// https
|
||||||
|
{
|
||||||
|
ru: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "example.com",
|
||||||
|
},
|
||||||
|
id: "foo",
|
||||||
|
want: "https://example.com/foo",
|
||||||
|
},
|
||||||
|
// with path
|
||||||
|
{
|
||||||
|
ru: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "one/two/three",
|
||||||
|
},
|
||||||
|
id: "foo",
|
||||||
|
want: "http://example.com/one/two/three/foo",
|
||||||
|
},
|
||||||
|
// with fragment
|
||||||
|
{
|
||||||
|
ru: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "example.com",
|
||||||
|
Fragment: "frag",
|
||||||
|
},
|
||||||
|
id: "foo",
|
||||||
|
want: "http://example.com/foo",
|
||||||
|
},
|
||||||
|
// with query
|
||||||
|
{
|
||||||
|
ru: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "example.com",
|
||||||
|
RawQuery: "dog=elroy",
|
||||||
|
},
|
||||||
|
id: "foo",
|
||||||
|
want: "http://example.com/foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := NewResourceLocation(tt.ru, tt.id)
|
||||||
|
if tt.want != got {
|
||||||
|
t.Errorf("case %d: want=%s, got=%s", i, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyRequest(t *testing.T) {
|
||||||
|
r1, err := http.NewRequest("GET", "http://example.com", strings.NewReader("foo"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r2 := CopyRequest(r1)
|
||||||
|
if !reflect.DeepEqual(r1, r2) {
|
||||||
|
t.Fatalf("Result of CopyRequest incorrect: %#v != %#v", r1, r2)
|
||||||
|
}
|
||||||
|
}
|
29
vendor/github.com/coreos/go-oidc/http/url.go
generated
vendored
Normal file
29
vendor/github.com/coreos/go-oidc/http/url.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseNonEmptyURL checks that a string is a parsable URL which is also not empty
|
||||||
|
// since `url.Parse("")` does not return an error. Must contian a scheme and a host.
|
||||||
|
func ParseNonEmptyURL(u string) (*url.URL, error) {
|
||||||
|
if u == "" {
|
||||||
|
return nil, errors.New("url is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
ur, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ur.Scheme == "" {
|
||||||
|
return nil, errors.New("url scheme is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ur.Host == "" {
|
||||||
|
return nil, errors.New("url host is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ur, nil
|
||||||
|
}
|
49
vendor/github.com/coreos/go-oidc/http/url_test.go
generated
vendored
Normal file
49
vendor/github.com/coreos/go-oidc/http/url_test.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseNonEmptyURL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
u string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"", false},
|
||||||
|
{"http://", false},
|
||||||
|
{"example.com", false},
|
||||||
|
{"example", false},
|
||||||
|
{"http://example", true},
|
||||||
|
{"http://example:1234", true},
|
||||||
|
{"http://example.com", true},
|
||||||
|
{"http://example.com:1234", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
u, err := ParseNonEmptyURL(tt.u)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("err: %v", err)
|
||||||
|
if tt.ok {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.ok {
|
||||||
|
t.Errorf("case %d: expected error but got none", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
uu, err := url.Parse(tt.u)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if uu.String() != u.String() {
|
||||||
|
t.Errorf("case %d: incorrect url value, want: %q, got: %q", i, uu.String(), u.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
vendor/github.com/coreos/go-oidc/jose.go
generated
vendored
Normal file
20
vendor/github.com/coreos/go-oidc/jose.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// +build !golint
|
||||||
|
|
||||||
|
// Don't lint this file. We don't want to have to add a comment to each constant.
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
const (
|
||||||
|
// JOSE asymmetric signing algorithm values as defined by RFC 7518
|
||||||
|
//
|
||||||
|
// see: https://tools.ietf.org/html/rfc7518#section-3.1
|
||||||
|
RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
|
||||||
|
RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
|
||||||
|
RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
|
||||||
|
ES256 = "ES256" // ECDSA using P-256 and SHA-256
|
||||||
|
ES384 = "ES384" // ECDSA using P-384 and SHA-384
|
||||||
|
ES512 = "ES512" // ECDSA using P-521 and SHA-512
|
||||||
|
PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||||
|
PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||||
|
PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||||
|
)
|
126
vendor/github.com/coreos/go-oidc/jose/claims.go
generated
vendored
Normal file
126
vendor/github.com/coreos/go-oidc/jose/claims.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Claims map[string]interface{}
|
||||||
|
|
||||||
|
func (c Claims) Add(name string, value interface{}) {
|
||||||
|
c[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Claims) StringClaim(name string) (string, bool, error) {
|
||||||
|
cl, ok := c[name]
|
||||||
|
if !ok {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := cl.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", false, fmt.Errorf("unable to parse claim as string: %v", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Claims) StringsClaim(name string) ([]string, bool, error) {
|
||||||
|
cl, ok := c[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := cl.([]string); ok {
|
||||||
|
return v, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// When unmarshaled, []string will become []interface{}.
|
||||||
|
if v, ok := cl.([]interface{}); ok {
|
||||||
|
var ret []string
|
||||||
|
for _, vv := range v {
|
||||||
|
str, ok := vv.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
|
||||||
|
}
|
||||||
|
ret = append(ret, str)
|
||||||
|
}
|
||||||
|
return ret, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Claims) Int64Claim(name string) (int64, bool, error) {
|
||||||
|
cl, ok := c[name]
|
||||||
|
if !ok {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := cl.(int64)
|
||||||
|
if !ok {
|
||||||
|
vf, ok := cl.(float64)
|
||||||
|
if !ok {
|
||||||
|
return 0, false, fmt.Errorf("unable to parse claim as int64: %v", name)
|
||||||
|
}
|
||||||
|
v = int64(vf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Claims) Float64Claim(name string) (float64, bool, error) {
|
||||||
|
cl, ok := c[name]
|
||||||
|
if !ok {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := cl.(float64)
|
||||||
|
if !ok {
|
||||||
|
vi, ok := cl.(int64)
|
||||||
|
if !ok {
|
||||||
|
return 0, false, fmt.Errorf("unable to parse claim as float64: %v", name)
|
||||||
|
}
|
||||||
|
v = float64(vi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
|
||||||
|
v, ok, err := c.Float64Claim(name)
|
||||||
|
if !ok || err != nil {
|
||||||
|
return time.Time{}, ok, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := math.Trunc(v)
|
||||||
|
ns := (v - s) * math.Pow(10, 9)
|
||||||
|
return time.Unix(int64(s), int64(ns)).UTC(), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeClaims(payload []byte) (Claims, error) {
|
||||||
|
var c Claims
|
||||||
|
if err := json.Unmarshal(payload, &c); err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed JWT claims, unable to decode: %v", err)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalClaims(c Claims) ([]byte, error) {
|
||||||
|
b, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeClaims(c Claims) (string, error) {
|
||||||
|
b, err := marshalClaims(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodeSegment(b), nil
|
||||||
|
}
|
328
vendor/github.com/coreos/go-oidc/jose/claims_test.go
generated
vendored
Normal file
328
vendor/github.com/coreos/go-oidc/jose/claims_test.go
generated
vendored
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
cl Claims
|
||||||
|
key string
|
||||||
|
ok bool
|
||||||
|
err bool
|
||||||
|
val string
|
||||||
|
}{
|
||||||
|
// ok, no err, claim exists
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: "bar",
|
||||||
|
ok: true,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// no claims
|
||||||
|
{
|
||||||
|
cl: Claims{},
|
||||||
|
key: "foo",
|
||||||
|
val: "",
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// missing claim
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "xxx",
|
||||||
|
val: "",
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// unparsable: type
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": struct{}{},
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: "",
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
// unparsable: nil value
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": nil,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: "",
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
val, ok, err := tt.cl.StringClaim(tt.key)
|
||||||
|
|
||||||
|
if tt.err && err == nil {
|
||||||
|
t.Errorf("case %d: want err=non-nil, got err=nil", i)
|
||||||
|
} else if !tt.err && err != nil {
|
||||||
|
t.Errorf("case %d: want err=nil, got err=%v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.ok != ok {
|
||||||
|
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.val != val {
|
||||||
|
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
cl Claims
|
||||||
|
key string
|
||||||
|
ok bool
|
||||||
|
err bool
|
||||||
|
val int64
|
||||||
|
}{
|
||||||
|
// ok, no err, claim exists
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": int64(100),
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: int64(100),
|
||||||
|
ok: true,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// no claims
|
||||||
|
{
|
||||||
|
cl: Claims{},
|
||||||
|
key: "foo",
|
||||||
|
val: 0,
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// missing claim
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "xxx",
|
||||||
|
val: 0,
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// unparsable: type
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": struct{}{},
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: 0,
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
// unparsable: nil value
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": nil,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: 0,
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
val, ok, err := tt.cl.Int64Claim(tt.key)
|
||||||
|
|
||||||
|
if tt.err && err == nil {
|
||||||
|
t.Errorf("case %d: want err=non-nil, got err=nil", i)
|
||||||
|
} else if !tt.err && err != nil {
|
||||||
|
t.Errorf("case %d: want err=nil, got err=%v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.ok != ok {
|
||||||
|
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.val != val {
|
||||||
|
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTime(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
unixNow := now.Unix()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
cl Claims
|
||||||
|
key string
|
||||||
|
ok bool
|
||||||
|
err bool
|
||||||
|
val time.Time
|
||||||
|
}{
|
||||||
|
// ok, no err, claim exists
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": unixNow,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: time.Unix(now.Unix(), 0).UTC(),
|
||||||
|
ok: true,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// no claims
|
||||||
|
{
|
||||||
|
cl: Claims{},
|
||||||
|
key: "foo",
|
||||||
|
val: time.Time{},
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// missing claim
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "xxx",
|
||||||
|
val: time.Time{},
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// unparsable: type
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": struct{}{},
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: time.Time{},
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
// unparsable: nil value
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": nil,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: time.Time{},
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
val, ok, err := tt.cl.TimeClaim(tt.key)
|
||||||
|
|
||||||
|
if tt.err && err == nil {
|
||||||
|
t.Errorf("case %d: want err=non-nil, got err=nil", i)
|
||||||
|
} else if !tt.err && err != nil {
|
||||||
|
t.Errorf("case %d: want err=nil, got err=%v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.ok != ok {
|
||||||
|
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.val != val {
|
||||||
|
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringArray(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
cl Claims
|
||||||
|
key string
|
||||||
|
ok bool
|
||||||
|
err bool
|
||||||
|
val []string
|
||||||
|
}{
|
||||||
|
// ok, no err, claim exists
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": []string{"bar", "faf"},
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: []string{"bar", "faf"},
|
||||||
|
ok: true,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// ok, no err, []interface{}
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": []interface{}{"bar", "faf"},
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: []string{"bar", "faf"},
|
||||||
|
ok: true,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// no claims
|
||||||
|
{
|
||||||
|
cl: Claims{},
|
||||||
|
key: "foo",
|
||||||
|
val: nil,
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// missing claim
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "xxx",
|
||||||
|
val: nil,
|
||||||
|
ok: false,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
// unparsable: type
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": struct{}{},
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: nil,
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
// unparsable: nil value
|
||||||
|
{
|
||||||
|
cl: Claims{
|
||||||
|
"foo": nil,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
val: nil,
|
||||||
|
ok: false,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
val, ok, err := tt.cl.StringsClaim(tt.key)
|
||||||
|
|
||||||
|
if tt.err && err == nil {
|
||||||
|
t.Errorf("case %d: want err=non-nil, got err=nil", i)
|
||||||
|
} else if !tt.err && err != nil {
|
||||||
|
t.Errorf("case %d: want err=nil, got err=%v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.ok != ok {
|
||||||
|
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tt.val, val) {
|
||||||
|
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
vendor/github.com/coreos/go-oidc/jose/doc.go
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/jose/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package jose is DEPRECATED. Use gopkg.in/square/go-jose.v2 instead.
|
||||||
|
package jose
|
112
vendor/github.com/coreos/go-oidc/jose/jose.go
generated
vendored
Normal file
112
vendor/github.com/coreos/go-oidc/jose/jose.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderMediaType = "typ"
|
||||||
|
HeaderKeyAlgorithm = "alg"
|
||||||
|
HeaderKeyID = "kid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Encryption Algorithm Header Parameter Values for JWS
|
||||||
|
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
|
||||||
|
AlgHS256 = "HS256"
|
||||||
|
AlgHS384 = "HS384"
|
||||||
|
AlgHS512 = "HS512"
|
||||||
|
AlgRS256 = "RS256"
|
||||||
|
AlgRS384 = "RS384"
|
||||||
|
AlgRS512 = "RS512"
|
||||||
|
AlgES256 = "ES256"
|
||||||
|
AlgES384 = "ES384"
|
||||||
|
AlgES512 = "ES512"
|
||||||
|
AlgPS256 = "PS256"
|
||||||
|
AlgPS384 = "PS384"
|
||||||
|
AlgPS512 = "PS512"
|
||||||
|
AlgNone = "none"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Algorithm Header Parameter Values for JWE
|
||||||
|
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
|
||||||
|
AlgRSA15 = "RSA1_5"
|
||||||
|
AlgRSAOAEP = "RSA-OAEP"
|
||||||
|
AlgRSAOAEP256 = "RSA-OAEP-256"
|
||||||
|
AlgA128KW = "A128KW"
|
||||||
|
AlgA192KW = "A192KW"
|
||||||
|
AlgA256KW = "A256KW"
|
||||||
|
AlgDir = "dir"
|
||||||
|
AlgECDHES = "ECDH-ES"
|
||||||
|
AlgECDHESA128KW = "ECDH-ES+A128KW"
|
||||||
|
AlgECDHESA192KW = "ECDH-ES+A192KW"
|
||||||
|
AlgECDHESA256KW = "ECDH-ES+A256KW"
|
||||||
|
AlgA128GCMKW = "A128GCMKW"
|
||||||
|
AlgA192GCMKW = "A192GCMKW"
|
||||||
|
AlgA256GCMKW = "A256GCMKW"
|
||||||
|
AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
|
||||||
|
AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
|
||||||
|
AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Encryption Algorithm Header Parameter Values for JWE
|
||||||
|
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
|
||||||
|
EncA128CBCHS256 = "A128CBC-HS256"
|
||||||
|
EncA128CBCHS384 = "A128CBC-HS384"
|
||||||
|
EncA256CBCHS512 = "A256CBC-HS512"
|
||||||
|
EncA128GCM = "A128GCM"
|
||||||
|
EncA192GCM = "A192GCM"
|
||||||
|
EncA256GCM = "A256GCM"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JOSEHeader map[string]string
|
||||||
|
|
||||||
|
func (j JOSEHeader) Validate() error {
|
||||||
|
if _, exists := j[HeaderKeyAlgorithm]; !exists {
|
||||||
|
return fmt.Errorf("header missing %q parameter", HeaderKeyAlgorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHeader(seg string) (JOSEHeader, error) {
|
||||||
|
b, err := decodeSegment(seg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var h JOSEHeader
|
||||||
|
err = json.Unmarshal(b, &h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeHeader(h JOSEHeader) (string, error) {
|
||||||
|
b, err := json.Marshal(h)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodeSegment(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode JWT specific base64url encoding with padding stripped
|
||||||
|
func decodeSegment(seg string) ([]byte, error) {
|
||||||
|
if l := len(seg) % 4; l != 0 {
|
||||||
|
seg += strings.Repeat("=", 4-l)
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.DecodeString(seg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode JWT specific base64url encoding with padding stripped
|
||||||
|
func encodeSegment(seg []byte) string {
|
||||||
|
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
|
||||||
|
}
|
135
vendor/github.com/coreos/go-oidc/jose/jwk.go
generated
vendored
Normal file
135
vendor/github.com/coreos/go-oidc/jose/jwk.go
generated
vendored
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSON Web Key
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5
|
||||||
|
type JWK struct {
|
||||||
|
ID string
|
||||||
|
Type string
|
||||||
|
Alg string
|
||||||
|
Use string
|
||||||
|
Exponent int
|
||||||
|
Modulus *big.Int
|
||||||
|
Secret []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwkJSON struct {
|
||||||
|
ID string `json:"kid"`
|
||||||
|
Type string `json:"kty"`
|
||||||
|
Alg string `json:"alg"`
|
||||||
|
Use string `json:"use"`
|
||||||
|
Exponent string `json:"e"`
|
||||||
|
Modulus string `json:"n"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWK) MarshalJSON() ([]byte, error) {
|
||||||
|
t := jwkJSON{
|
||||||
|
ID: j.ID,
|
||||||
|
Type: j.Type,
|
||||||
|
Alg: j.Alg,
|
||||||
|
Use: j.Use,
|
||||||
|
Exponent: encodeExponent(j.Exponent),
|
||||||
|
Modulus: encodeModulus(j.Modulus),
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(&t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWK) UnmarshalJSON(data []byte) error {
|
||||||
|
var t jwkJSON
|
||||||
|
err := json.Unmarshal(data, &t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := decodeExponent(t.Exponent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := decodeModulus(t.Modulus)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.ID = t.ID
|
||||||
|
j.Type = t.Type
|
||||||
|
j.Alg = t.Alg
|
||||||
|
j.Use = t.Use
|
||||||
|
j.Exponent = e
|
||||||
|
j.Modulus = n
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWKSet struct {
|
||||||
|
Keys []JWK `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeExponent(e string) (int, error) {
|
||||||
|
decE, err := decodeBase64URLPaddingOptional(e)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var eBytes []byte
|
||||||
|
if len(decE) < 8 {
|
||||||
|
eBytes = make([]byte, 8-len(decE), 8)
|
||||||
|
eBytes = append(eBytes, decE...)
|
||||||
|
} else {
|
||||||
|
eBytes = decE
|
||||||
|
}
|
||||||
|
eReader := bytes.NewReader(eBytes)
|
||||||
|
var E uint64
|
||||||
|
err = binary.Read(eReader, binary.BigEndian, &E)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(E), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeExponent(e int) string {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, uint64(e))
|
||||||
|
var idx int
|
||||||
|
for ; idx < 8; idx++ {
|
||||||
|
if b[idx] != 0x0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.EncodeToString(b[idx:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turns a URL encoded modulus of a key into a big int.
|
||||||
|
func decodeModulus(n string) (*big.Int, error) {
|
||||||
|
decN, err := decodeBase64URLPaddingOptional(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
N := big.NewInt(0)
|
||||||
|
N.SetBytes(decN)
|
||||||
|
return N, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeModulus(n *big.Int) string {
|
||||||
|
return base64.URLEncoding.EncodeToString(n.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not.
|
||||||
|
// The stdlib version currently doesn't handle this.
|
||||||
|
// We can get rid of this is if this bug:
|
||||||
|
// https://github.com/golang/go/issues/4237
|
||||||
|
// ever closes.
|
||||||
|
func decodeBase64URLPaddingOptional(e string) ([]byte, error) {
|
||||||
|
if m := len(e) % 4; m != 0 {
|
||||||
|
e += strings.Repeat("=", 4-m)
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.DecodeString(e)
|
||||||
|
}
|
64
vendor/github.com/coreos/go-oidc/jose/jwk_test.go
generated
vendored
Normal file
64
vendor/github.com/coreos/go-oidc/jose/jwk_test.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeBase64URLPaddingOptional(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
encoded string
|
||||||
|
decoded string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// With padding
|
||||||
|
encoded: "VGVjdG9uaWM=",
|
||||||
|
decoded: "Tectonic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Without padding
|
||||||
|
encoded: "VGVjdG9uaWM",
|
||||||
|
decoded: "Tectonic",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Even More padding
|
||||||
|
encoded: "VGVjdG9uaQ==",
|
||||||
|
decoded: "Tectoni",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// And take it away!
|
||||||
|
encoded: "VGVjdG9uaQ",
|
||||||
|
decoded: "Tectoni",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Too much padding.
|
||||||
|
encoded: "VGVjdG9uaWNh=",
|
||||||
|
decoded: "",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Too much padding.
|
||||||
|
encoded: "VGVjdG9uaWNh=",
|
||||||
|
decoded: "",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got, err := decodeBase64URLPaddingOptional(tt.encoded)
|
||||||
|
if tt.err {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: expected non-nil err", i)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: want nil err, got: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(got) != tt.decoded {
|
||||||
|
t.Errorf("case %d: want=%q, got=%q", i, tt.decoded, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
vendor/github.com/coreos/go-oidc/jose/jws.go
generated
vendored
Normal file
51
vendor/github.com/coreos/go-oidc/jose/jws.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JWS struct {
|
||||||
|
RawHeader string
|
||||||
|
Header JOSEHeader
|
||||||
|
RawPayload string
|
||||||
|
Payload []byte
|
||||||
|
Signature []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a raw encoded JWS token parses it and verifies the structure.
|
||||||
|
func ParseJWS(raw string) (JWS, error) {
|
||||||
|
parts := strings.Split(raw, ".")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return JWS{}, fmt.Errorf("malformed JWS, only %d segments", len(parts))
|
||||||
|
}
|
||||||
|
|
||||||
|
rawSig := parts[2]
|
||||||
|
jws := JWS{
|
||||||
|
RawHeader: parts[0],
|
||||||
|
RawPayload: parts[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := decodeHeader(jws.RawHeader)
|
||||||
|
if err != nil {
|
||||||
|
return JWS{}, fmt.Errorf("malformed JWS, unable to decode header, %s", err)
|
||||||
|
}
|
||||||
|
if err = header.Validate(); err != nil {
|
||||||
|
return JWS{}, fmt.Errorf("malformed JWS, %s", err)
|
||||||
|
}
|
||||||
|
jws.Header = header
|
||||||
|
|
||||||
|
payload, err := decodeSegment(jws.RawPayload)
|
||||||
|
if err != nil {
|
||||||
|
return JWS{}, fmt.Errorf("malformed JWS, unable to decode payload: %s", err)
|
||||||
|
}
|
||||||
|
jws.Payload = payload
|
||||||
|
|
||||||
|
sig, err := decodeSegment(rawSig)
|
||||||
|
if err != nil {
|
||||||
|
return JWS{}, fmt.Errorf("malformed JWS, unable to decode signature: %s", err)
|
||||||
|
}
|
||||||
|
jws.Signature = sig
|
||||||
|
|
||||||
|
return jws, nil
|
||||||
|
}
|
74
vendor/github.com/coreos/go-oidc/jose/jws_test.go
generated
vendored
Normal file
74
vendor/github.com/coreos/go-oidc/jose/jws_test.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCase struct{ t string }
|
||||||
|
|
||||||
|
var validInput []testCase
|
||||||
|
|
||||||
|
var invalidInput []testCase
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
validInput = []testCase{
|
||||||
|
{
|
||||||
|
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidInput = []testCase{
|
||||||
|
// empty
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
// undecodeable
|
||||||
|
{
|
||||||
|
"aaa.bbb.ccc",
|
||||||
|
},
|
||||||
|
// missing parts
|
||||||
|
{
|
||||||
|
"aaa",
|
||||||
|
},
|
||||||
|
// missing parts
|
||||||
|
{
|
||||||
|
"aaa.bbb",
|
||||||
|
},
|
||||||
|
// too many parts
|
||||||
|
{
|
||||||
|
"aaa.bbb.ccc.ddd",
|
||||||
|
},
|
||||||
|
// invalid header
|
||||||
|
// EncodeHeader(map[string]string{"foo": "bar"})
|
||||||
|
{
|
||||||
|
"eyJmb28iOiJiYXIifQ.bbb.ccc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseJWS(t *testing.T) {
|
||||||
|
for i, tt := range validInput {
|
||||||
|
jws, err := ParseJWS(tt.t)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test: %d. expected: valid, actual: invalid", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHeader := strings.Split(tt.t, ".")[0]
|
||||||
|
if jws.RawHeader != expectedHeader {
|
||||||
|
t.Errorf("test: %d. expected: %s, actual: %s", i, expectedHeader, jws.RawHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPayload := strings.Split(tt.t, ".")[1]
|
||||||
|
if jws.RawPayload != expectedPayload {
|
||||||
|
t.Errorf("test: %d. expected: %s, actual: %s", i, expectedPayload, jws.RawPayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range invalidInput {
|
||||||
|
_, err := ParseJWS(tt.t)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("test: %d. expected: invalid, actual: valid", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
vendor/github.com/coreos/go-oidc/jose/jwt.go
generated
vendored
Normal file
82
vendor/github.com/coreos/go-oidc/jose/jwt.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type JWT JWS
|
||||||
|
|
||||||
|
func ParseJWT(token string) (jwt JWT, err error) {
|
||||||
|
jws, err := ParseJWS(token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return JWT(jws), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJWT(header JOSEHeader, claims Claims) (jwt JWT, err error) {
|
||||||
|
jwt = JWT{}
|
||||||
|
|
||||||
|
jwt.Header = header
|
||||||
|
jwt.Header[HeaderMediaType] = "JWT"
|
||||||
|
|
||||||
|
claimBytes, err := marshalClaims(claims)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jwt.Payload = claimBytes
|
||||||
|
|
||||||
|
eh, err := encodeHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jwt.RawHeader = eh
|
||||||
|
|
||||||
|
ec, err := encodeClaims(claims)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jwt.RawPayload = ec
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) KeyID() (string, bool) {
|
||||||
|
kID, ok := j.Header[HeaderKeyID]
|
||||||
|
return kID, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) Claims() (Claims, error) {
|
||||||
|
return decodeClaims(j.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoded data part of the token which may be signed.
|
||||||
|
func (j *JWT) Data() string {
|
||||||
|
return strings.Join([]string{j.RawHeader, j.RawPayload}, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full encoded JWT token string in format: header.claims.signature
|
||||||
|
func (j *JWT) Encode() string {
|
||||||
|
d := j.Data()
|
||||||
|
s := encodeSegment(j.Signature)
|
||||||
|
return strings.Join([]string{d, s}, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSignedJWT(claims Claims, s Signer) (*JWT, error) {
|
||||||
|
header := JOSEHeader{
|
||||||
|
HeaderKeyAlgorithm: s.Alg(),
|
||||||
|
HeaderKeyID: s.ID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt, err := NewJWT(header, claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := s.Sign([]byte(jwt.Data()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jwt.Signature = sig
|
||||||
|
|
||||||
|
return &jwt, nil
|
||||||
|
}
|
94
vendor/github.com/coreos/go-oidc/jose/jwt_test.go
generated
vendored
Normal file
94
vendor/github.com/coreos/go-oidc/jose/jwt_test.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseJWT(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
r string
|
||||||
|
h JOSEHeader
|
||||||
|
c Claims
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Example from JWT spec:
|
||||||
|
// http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#ExampleJWT
|
||||||
|
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
|
||||||
|
JOSEHeader{
|
||||||
|
HeaderMediaType: "JWT",
|
||||||
|
HeaderKeyAlgorithm: "HS256",
|
||||||
|
},
|
||||||
|
Claims{
|
||||||
|
"iss": "joe",
|
||||||
|
// NOTE: test numbers must be floats for equality checks to work since values are converted form interface{} to float64 by default.
|
||||||
|
"exp": 1300819380.0,
|
||||||
|
"http://example.com/is_root": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
jwt, err := ParseJWT(tt.r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("raw token should parse. test: %d. expected: valid, actual: invalid. err=%v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tt.h, jwt.Header) {
|
||||||
|
t.Errorf("JOSE headers should match. test: %d. expected: %v, actual: %v", i, tt.h, jwt.Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := jwt.Claims()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test: %d. expected: valid claim parsing. err=%v", i, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.c, claims) {
|
||||||
|
t.Errorf("claims should match. test: %d. expected: %v, actual: %v", i, tt.c, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := jwt.Encode()
|
||||||
|
if enc != tt.r {
|
||||||
|
t.Errorf("encoded jwt should match raw jwt. test: %d. expected: %v, actual: %v", i, tt.r, enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewJWTHeaderType(t *testing.T) {
|
||||||
|
jwt, err := NewJWT(JOSEHeader{}, Claims{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := "JWT"
|
||||||
|
got := jwt.Header[HeaderMediaType]
|
||||||
|
if want != got {
|
||||||
|
t.Fatalf("Header %q incorrect: want=%s got=%s", HeaderMediaType, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewJWTHeaderKeyID(t *testing.T) {
|
||||||
|
jwt, err := NewJWT(JOSEHeader{HeaderKeyID: "foo"}, Claims{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := "foo"
|
||||||
|
got, ok := jwt.KeyID()
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("KeyID not set")
|
||||||
|
} else if want != got {
|
||||||
|
t.Fatalf("KeyID incorrect: want=%s got=%s", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewJWTHeaderKeyIDNotSet(t *testing.T) {
|
||||||
|
jwt, err := NewJWT(JOSEHeader{}, Claims{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := jwt.KeyID(); ok {
|
||||||
|
t.Fatalf("KeyID set, but should not be")
|
||||||
|
}
|
||||||
|
}
|
24
vendor/github.com/coreos/go-oidc/jose/sig.go
generated
vendored
Executable file
24
vendor/github.com/coreos/go-oidc/jose/sig.go
generated
vendored
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Verifier interface {
|
||||||
|
ID() string
|
||||||
|
Alg() string
|
||||||
|
Verify(sig []byte, data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Signer interface {
|
||||||
|
Verifier
|
||||||
|
Sign(data []byte) (sig []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifier(jwk JWK) (Verifier, error) {
|
||||||
|
if jwk.Type != "RSA" {
|
||||||
|
return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewVerifierRSA(jwk)
|
||||||
|
}
|
67
vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
generated
vendored
Executable file
67
vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
generated
vendored
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifierRSA struct {
|
||||||
|
KeyID string
|
||||||
|
Hash crypto.Hash
|
||||||
|
PublicKey rsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignerRSA struct {
|
||||||
|
PrivateKey rsa.PrivateKey
|
||||||
|
VerifierRSA
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifierRSA(jwk JWK) (*VerifierRSA, error) {
|
||||||
|
if jwk.Alg != "" && jwk.Alg != "RS256" {
|
||||||
|
return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := VerifierRSA{
|
||||||
|
KeyID: jwk.ID,
|
||||||
|
PublicKey: rsa.PublicKey{
|
||||||
|
N: jwk.Modulus,
|
||||||
|
E: jwk.Exponent,
|
||||||
|
},
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSignerRSA(kid string, key rsa.PrivateKey) *SignerRSA {
|
||||||
|
return &SignerRSA{
|
||||||
|
PrivateKey: key,
|
||||||
|
VerifierRSA: VerifierRSA{
|
||||||
|
KeyID: kid,
|
||||||
|
PublicKey: key.PublicKey,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VerifierRSA) ID() string {
|
||||||
|
return v.KeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VerifierRSA) Alg() string {
|
||||||
|
return "RS256"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VerifierRSA) Verify(sig []byte, data []byte) error {
|
||||||
|
h := v.Hash.New()
|
||||||
|
h.Write(data)
|
||||||
|
return rsa.VerifyPKCS1v15(&v.PublicKey, v.Hash, h.Sum(nil), sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SignerRSA) Sign(data []byte) ([]byte, error) {
|
||||||
|
h := s.Hash.New()
|
||||||
|
h.Write(data)
|
||||||
|
return rsa.SignPKCS1v15(rand.Reader, &s.PrivateKey, s.Hash, h.Sum(nil))
|
||||||
|
}
|
405
vendor/github.com/coreos/go-oidc/jose_test.go
generated
vendored
Normal file
405
vendor/github.com/coreos/go-oidc/jose_test.go
generated
vendored
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
// +build !golint
|
||||||
|
|
||||||
|
// This file contains statically created JWKs for tests created by gen.go
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustLoadJWK(s string) jose.JSONWebKey {
|
||||||
|
var jwk jose.JSONWebKey
|
||||||
|
if err := json.Unmarshal([]byte(s), &jwk); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return jwk
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
testKeyECDSA_256_0 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "bd06f3e11f523e310f8c2c8b20a892727fb558e0e23602312568b20a41e11188",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "xK5N69f0-SAgWbjw2otcQeCGs3qqYMyqOWk4Os5Z_Xc",
|
||||||
|
"y": "AXSaOPcMklJY9UKZhkGzVevqhAIUEzE3cfZ8o-ML5xE"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_256_0_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "bd06f3e11f523e310f8c2c8b20a892727fb558e0e23602312568b20a41e11188",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "xK5N69f0-SAgWbjw2otcQeCGs3qqYMyqOWk4Os5Z_Xc",
|
||||||
|
"y": "AXSaOPcMklJY9UKZhkGzVevqhAIUEzE3cfZ8o-ML5xE",
|
||||||
|
"d": "L7jynYt-fMRPqw1e9vgXCGTg4yhGU4tlLxiFyNVimG4"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_256_1 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "27ebd2f5bf6723e4de06caaab03703be458a37bf28a9fa748576a0826acb6ec2",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "KZisP6wLCph4q6056jr7BH_asiX9RcLcS3HrNjdCpkw",
|
||||||
|
"y": "5DrW-kEge0sePHlKmh1d2kqd10r32JEW6eyyewy18j8"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_256_1_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "27ebd2f5bf6723e4de06caaab03703be458a37bf28a9fa748576a0826acb6ec2",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "KZisP6wLCph4q6056jr7BH_asiX9RcLcS3HrNjdCpkw",
|
||||||
|
"y": "5DrW-kEge0sePHlKmh1d2kqd10r32JEW6eyyewy18j8",
|
||||||
|
"d": "r6CiIpv0icIq5U4LYO39nBDVhCHCLObDFYC5IG9Y8Hk"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_256_2 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "2c7d179db6006c90ece4d91f554791ff35156693c693102c00f66f473d15df17",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "oDwcKp7SqgeRvycK5GgYjrlW4fbHn2Ybfd5iG7kDiPc",
|
||||||
|
"y": "qazib9UwdUdbHSFzdy_HN10xZEItLvufPw0v7nIJOWA"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_256_2_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "2c7d179db6006c90ece4d91f554791ff35156693c693102c00f66f473d15df17",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "oDwcKp7SqgeRvycK5GgYjrlW4fbHn2Ybfd5iG7kDiPc",
|
||||||
|
"y": "qazib9UwdUdbHSFzdy_HN10xZEItLvufPw0v7nIJOWA",
|
||||||
|
"d": "p79U6biKrOyrKzg-i3C7FVJiqzlqBhYQqmyOiZ9bhVM"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_256_3 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "aba0a256999af470c8b2449103ae75b257d907da4d1cbfc73c1d45ab1098f544",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "CKRYWVt1R7FzuJ43vEprfIzgB-KgIhRDhxLmd5ixiXY",
|
||||||
|
"y": "QCxTVmK31ee710OYkNqdqEgHH3rqRQNQj3Wyq0xtYq0"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_256_3_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "aba0a256999af470c8b2449103ae75b257d907da4d1cbfc73c1d45ab1098f544",
|
||||||
|
"crv": "P-256",
|
||||||
|
"x": "CKRYWVt1R7FzuJ43vEprfIzgB-KgIhRDhxLmd5ixiXY",
|
||||||
|
"y": "QCxTVmK31ee710OYkNqdqEgHH3rqRQNQj3Wyq0xtYq0",
|
||||||
|
"d": "c_WflwwUxT_B5izk4iET49qoob0RH5hccnEScfBS9Qk"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_384_0 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "bc271d0a68251171e0c5edfd384b32d154e1717af27c5089bda8fb598a474b7b",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "FZEhxw06oB86xCAZCtKTfX3ze9tgxkdN199g-cWrpQTWF-2m6Blg1MN5D60Z9KoX",
|
||||||
|
"y": "EyWjZ46gLlqfoU08iv0zcDWut1nbfoUoTd-7La2VY4PqQeDSFrxPCOyplxFMGJIW"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_384_0_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "bc271d0a68251171e0c5edfd384b32d154e1717af27c5089bda8fb598a474b7b",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "FZEhxw06oB86xCAZCtKTfX3ze9tgxkdN199g-cWrpQTWF-2m6Blg1MN5D60Z9KoX",
|
||||||
|
"y": "EyWjZ46gLlqfoU08iv0zcDWut1nbfoUoTd-7La2VY4PqQeDSFrxPCOyplxFMGJIW",
|
||||||
|
"d": "t6oJHJBH2rvH3uyQkK_JwoUEwE1QHjwTnGLSDMZbNEDrEaR8BBiAo3s0p8rzGz5-"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_384_1 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "58ef375d6e63cedc637fed59f4012a779cd749a83373237cf3972bba74ebd738",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "Gj3zkiRR4RCJb0Tke3lD2spG2jzuXgX50fEwDZTRjlQIzz3Rc96Fw32FCSDectxQ",
|
||||||
|
"y": "6alqN7ilqJAmqCU1BFrJJeivJiM0s1-RqBewRoNkTQinOLLbaiZlBAQxS4iq2dRv"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_384_1_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "58ef375d6e63cedc637fed59f4012a779cd749a83373237cf3972bba74ebd738",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "Gj3zkiRR4RCJb0Tke3lD2spG2jzuXgX50fEwDZTRjlQIzz3Rc96Fw32FCSDectxQ",
|
||||||
|
"y": "6alqN7ilqJAmqCU1BFrJJeivJiM0s1-RqBewRoNkTQinOLLbaiZlBAQxS4iq2dRv",
|
||||||
|
"d": "-4eaoElyb_YANRownMtId_-glX2o45oc4_L_vgo3YOW5hxCq3KIBIdrhvBx1nw8B"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_384_2 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "a8bab6f438b6cd2080711404549c978d1c00dd77a3c532bba12c964c4883b2ec",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "CBKwYgZFLdTBBFnWD20q2YNUnRnOsDgTxG3y-dzCUrb65kOKm0ZFaZQPe5ZPjvDS",
|
||||||
|
"y": "WlubxLv2qH-Aw-LsESKXAwm4HF3l4H1rVn3DZRpqcac6p-QSrXmfKCtxJVHDaTNi"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_384_2_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "a8bab6f438b6cd2080711404549c978d1c00dd77a3c532bba12c964c4883b2ec",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "CBKwYgZFLdTBBFnWD20q2YNUnRnOsDgTxG3y-dzCUrb65kOKm0ZFaZQPe5ZPjvDS",
|
||||||
|
"y": "WlubxLv2qH-Aw-LsESKXAwm4HF3l4H1rVn3DZRpqcac6p-QSrXmfKCtxJVHDaTNi",
|
||||||
|
"d": "XfoOcxV3yfoAcRquJ9eaBvcY-H71B0XzXwx2eidolHDKLK7GHygEt9ToYSY_DvxO"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_384_3 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "32f0cfc686455840530d727fb029002b9757ffff15272f98f53be9f77560718a",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "xxED1z8EUCOUQ_jwS9nuVUDVzxs-U1rl19y8jMWrv4TdPeGHTRTgNUE57-YAL3ly",
|
||||||
|
"y": "OsCN6-HmM-LP1itE5eW15WsSQoe3dZBX7AoHyKJUKtjzWKCJNUcyu3Np07xta1Cr"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_384_3_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "32f0cfc686455840530d727fb029002b9757ffff15272f98f53be9f77560718a",
|
||||||
|
"crv": "P-384",
|
||||||
|
"x": "xxED1z8EUCOUQ_jwS9nuVUDVzxs-U1rl19y8jMWrv4TdPeGHTRTgNUE57-YAL3ly",
|
||||||
|
"y": "OsCN6-HmM-LP1itE5eW15WsSQoe3dZBX7AoHyKJUKtjzWKCJNUcyu3Np07xta1Cr",
|
||||||
|
"d": "gfQA6wn4brWVT3OkeGiaCXGsQyTybZB9SdqTULsiSg8n6FS2T8hvK0doPwMTT5Gw"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_521_0 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "f7b325c848a6c9fc5b72f131f179d2f37296837262797983c632597a4927726e",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AUaLqCAMEWPqiYgd-D_6F5kxpOgqnbQnjIHZ-NhjRnKKYuij9Iz7bq9pZU4F79wsODFpWxMFfISrveUfgEGt4Hy2",
|
||||||
|
"y": "AeAKfmFsfcFttwQqv2B-fUfgLhw837YoGoNWh5qNE_LqTBxbYKRUkbSLxVRHEcVNnU1t3z9yMMdYXtuMlfJ0bhjr"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_521_0_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "f7b325c848a6c9fc5b72f131f179d2f37296837262797983c632597a4927726e",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AUaLqCAMEWPqiYgd-D_6F5kxpOgqnbQnjIHZ-NhjRnKKYuij9Iz7bq9pZU4F79wsODFpWxMFfISrveUfgEGt4Hy2",
|
||||||
|
"y": "AeAKfmFsfcFttwQqv2B-fUfgLhw837YoGoNWh5qNE_LqTBxbYKRUkbSLxVRHEcVNnU1t3z9yMMdYXtuMlfJ0bhjr",
|
||||||
|
"d": "AbD3guIvlVd8CZD0xUNuebgnQkE24XJnxCQ69P5VL3etEMmdr4HPJLPMQfMs10Gz8RTmrGKo-bdsU-cjS3d7dKIC"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_521_1 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "4ea260ba2c9ed5fe9d6adeb998bb68b3edbab839d8bfd2be09fa628680793b90",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AE8-53o64GiywneanVZAHb96NcPbq0Ml6zynIcgLdKUiHGVs2te7SABq_9keFZJC6wooACeNWWT7VDK3kY77fjdh",
|
||||||
|
"y": "AAnrIu0-D7JEd3-mqTR8Rdz53kw7DLAIypv0-_u4rqn0glwTZCkMpQ17wFH71bMInXaTi2Z_uq67NuVxFvUCaTvS"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_521_1_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "4ea260ba2c9ed5fe9d6adeb998bb68b3edbab839d8bfd2be09fa628680793b90",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AE8-53o64GiywneanVZAHb96NcPbq0Ml6zynIcgLdKUiHGVs2te7SABq_9keFZJC6wooACeNWWT7VDK3kY77fjdh",
|
||||||
|
"y": "AAnrIu0-D7JEd3-mqTR8Rdz53kw7DLAIypv0-_u4rqn0glwTZCkMpQ17wFH71bMInXaTi2Z_uq67NuVxFvUCaTvS",
|
||||||
|
"d": "AVVL9TIWcJCYH5r3QUhHXtsbkVXhLQSpp4sL1ta_H2_3bpRb9ZdVv10YOA-xN7Yz2wa-FMhIhj1ULe9z18ZM8dDF"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_521_2 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "d69caddc821807ea63a46519b538a7d2f3135dbdda1ba628781f8567a47893d8",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AO618rH8GP78-mi9Z5FaPGqpyc_OVMK-BajZK-pwL89ZQdOvFa0fY0ENpB_KaRf5ELw9IP17lQh3T-O9O7jePDFj",
|
||||||
|
"y": "AT1x9pCMbY-BXqAJPQDQxp6j8Gca7IpdxL8OS4td_XPiwXtX4JKcj_VKxtOw6k64yr_VuFYCs6wImOc72jNRBjA7"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_521_2_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "d69caddc821807ea63a46519b538a7d2f3135dbdda1ba628781f8567a47893d8",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AO618rH8GP78-mi9Z5FaPGqpyc_OVMK-BajZK-pwL89ZQdOvFa0fY0ENpB_KaRf5ELw9IP17lQh3T-O9O7jePDFj",
|
||||||
|
"y": "AT1x9pCMbY-BXqAJPQDQxp6j8Gca7IpdxL8OS4td_XPiwXtX4JKcj_VKxtOw6k64yr_VuFYCs6wImOc72jNRBjA7",
|
||||||
|
"d": "AYmtaW9ojb0Gb8zSqrlRnEKzRVIBIM5dsb1qWkd3mfr4Wl5tbPuiEctGLN9s6LDtY0JOL3nukOVoDbrmS4qCW64"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyECDSA_521_3 = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "7193cba4fd9c72153133ce5cffc423857517ab8eb176a827e1a002eb5621edca",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AQfpLehZCER3ZTw1V1pN0RsX8-4WEW4IDxFDSGwrULQ79YHiLNmubrOSlSxiOSv2S-tHq-hxgma1PZlQRghJfemx",
|
||||||
|
"y": "AAF41XT7jptLsy8FAaVRnex2WcSfdebcjjXMGO4rn6IlD3u9qnvrpR8MBp1gz5G1C5S7_NVgIeLSIYbdfd_wjVkd"
|
||||||
|
}`)
|
||||||
|
testKeyECDSA_521_3_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "7193cba4fd9c72153133ce5cffc423857517ab8eb176a827e1a002eb5621edca",
|
||||||
|
"crv": "P-521",
|
||||||
|
"x": "AQfpLehZCER3ZTw1V1pN0RsX8-4WEW4IDxFDSGwrULQ79YHiLNmubrOSlSxiOSv2S-tHq-hxgma1PZlQRghJfemx",
|
||||||
|
"y": "AAF41XT7jptLsy8FAaVRnex2WcSfdebcjjXMGO4rn6IlD3u9qnvrpR8MBp1gz5G1C5S7_NVgIeLSIYbdfd_wjVkd",
|
||||||
|
"d": "hzh0loqrhYFUik86SYBv3CC_GKNYankLMK95-Cfr1gLBD1l0M6W-7gn3XlyaQEInz0TBIbaQ1fL78HHjbVsNLrY"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_1024_0 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "979d62e7114052027b11bcb51282d8228790134ef34e746f6372fa5aadaa9bf2",
|
||||||
|
"n": "6zxmGE5X74SUBWfDEo8Tl-YxrkVfvxljQG9vKmNPQ2RhEmJ8eplZpKn9_nlxVDHGLzfJMqqUBS19EarIfDnOqyFBRkyKRbsQdUVU9XLjDIXqImJ8aN-UmShAYoeOClJVDsBJuxBGgS3pdgG7u3YQpvTV_hGuMoJr7UYgIrqb0qc",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_1024_0_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "979d62e7114052027b11bcb51282d8228790134ef34e746f6372fa5aadaa9bf2",
|
||||||
|
"n": "6zxmGE5X74SUBWfDEo8Tl-YxrkVfvxljQG9vKmNPQ2RhEmJ8eplZpKn9_nlxVDHGLzfJMqqUBS19EarIfDnOqyFBRkyKRbsQdUVU9XLjDIXqImJ8aN-UmShAYoeOClJVDsBJuxBGgS3pdgG7u3YQpvTV_hGuMoJr7UYgIrqb0qc",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "IARggP5oyZjp7LJqwqPmrs4OBQI8Pe5eq-5-2u4ZY7rN24q8FpO4t8jLYU92NVdw-gxFvjepXesLEtSD5SSZFD7s-5rqmX9tW_t5t1a1sBY6IKz_YBSf2p2LtdTyrqgo4nRD0nYH3sMvGtOKCTV4K_vwfWPD3elXq752trG-HwE",
|
||||||
|
"p": "9tgo12L3earNNrJfyIIDD2dqgKuNfWhQzhbe-Ju17dF37uGF6xnLqR0WXU-agGpUdYTMOd7IQi_bX_xkjQBYlw",
|
||||||
|
"q": "8_YDvufPdccjGM2rAXs-2LbeP_LrzLUk1uGNpEjIt2lXEm6sQKjIfNLcAfVKh9zsqwqroveL2iI1ijsDgNAIcQ"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_1024_1 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "4a3a37dc10aec5f427509e4014683102a0fafa32bec2ff9921ff19e1a55c79e8",
|
||||||
|
"n": "sn7sU7dBEkU1RBIz_LKgrswSS7-68vTlOe7n-lanAqAlczm01_6IWrvcIC7lPv1iHQqWngusskANZirCZGTv6kK8kJLHBVRSROB9VkPTPNFYnSB6dacyPa0ty0otsaYOVM2RFvcCX7lBKKat2Tmst8vITdpvnoEzTgT_eGOKGAs",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_1024_1_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "4a3a37dc10aec5f427509e4014683102a0fafa32bec2ff9921ff19e1a55c79e8",
|
||||||
|
"n": "sn7sU7dBEkU1RBIz_LKgrswSS7-68vTlOe7n-lanAqAlczm01_6IWrvcIC7lPv1iHQqWngusskANZirCZGTv6kK8kJLHBVRSROB9VkPTPNFYnSB6dacyPa0ty0otsaYOVM2RFvcCX7lBKKat2Tmst8vITdpvnoEzTgT_eGOKGAs",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "KTXqpE1sBaba7HNzc0Vemdzd4IVMyWlHPz_saTz2ZEHLQ7YwDapjmudCpF-PaCKiM2hNbAHwBluJfGwk4372cPHcJyri3Gs4GGj-cOlbXJh9aUp5o8fqn904B1UcPxMZ2DrZAKjseuLj3zyJgCsSJOGU1kJFRAkM8UN4a9cE8sE",
|
||||||
|
"p": "3ntHMGDJEXwqldLNR-DucKGp32aJVAaKnn4j9qy1Tj6KhH70o3HyFVO_TKYxGg-pG3zTV-VzyMqmeh9hDdnyKw",
|
||||||
|
"q": "zWMxEjyxvp-P-mNxhWQe1pJXZi6iPpcnt0SZLfweT3AtAzEaoSs4-4D1jT911LD4xDI8_a7-gYHgD8ME62PhoQ"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_1024_2 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "d1c32aaea6c9527038e589dd94a546ca1beedb90f90e073228986cc258cf1fda",
|
||||||
|
"n": "3mZrDaB5-7e29zok9XkGuu6FXYB00FqFgnGTJAGCMpql5uHz1h9p0DljZL4vsGkkYOZUMvqFS1pCEuzdSsupPNClf0NKMRux6yLv6iIR9C4pE9RBKPUrinzJuYs634rq5JOEP4IpP_fJfKxMw4Na85otd9KposKwP14cCkOibYM",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_1024_2_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "d1c32aaea6c9527038e589dd94a546ca1beedb90f90e073228986cc258cf1fda",
|
||||||
|
"n": "3mZrDaB5-7e29zok9XkGuu6FXYB00FqFgnGTJAGCMpql5uHz1h9p0DljZL4vsGkkYOZUMvqFS1pCEuzdSsupPNClf0NKMRux6yLv6iIR9C4pE9RBKPUrinzJuYs634rq5JOEP4IpP_fJfKxMw4Na85otd9KposKwP14cCkOibYM",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "Ft2M0B_ZqsmepBh0SFCjIoD3cT-NwwYrh9fJewA0tKM1v2EnwrIEHQZpc6giGw8UUGod6gfbwH2NIYj8z33U7lyrKWPR7F8gJRm1KR5NLCBCnAnz0ukhsg24ktB25LZLZRCStRRhtCev95Vvmew0ip5081hv730Z2T_PsEyOU6E",
|
||||||
|
"p": "8moFzLKJSQuhaIYTo1qNX0w8o4NBFb1atOlthHmq6Y6rdYTm_nyM9Q3mrNBolPS7LHTiBXtCEJ_m4V5hWDuGKw",
|
||||||
|
"q": "6t0_mZKOoZ5NAE--MxkuOCcRhuqNo9VoacfPEH39CeoKAam4v5k56R7aQcb_raQK396pgV-evM2dpfdUaasiCQ"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_1024_3 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "838742d0e3195b19f974fa30352db79070fa19aaaeb678ec95c499de7bd48d52",
|
||||||
|
"n": "17Uc89-QvCjqBLXDJSCWxUoohjwFPI63Gub8g-lH1GSK3flXqiohz33KfKhqrdKsLrpRjskGTMg3Vo0IcLBnMYdp1i1nceORghtYLQsDNS7tqlHiKx725fLmWldGgiuP_0Ak0Knisw-j_q7Jx3OVAJnuS1o3vPLfJxJdyq6yV1k",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_1024_3_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "838742d0e3195b19f974fa30352db79070fa19aaaeb678ec95c499de7bd48d52",
|
||||||
|
"n": "17Uc89-QvCjqBLXDJSCWxUoohjwFPI63Gub8g-lH1GSK3flXqiohz33KfKhqrdKsLrpRjskGTMg3Vo0IcLBnMYdp1i1nceORghtYLQsDNS7tqlHiKx725fLmWldGgiuP_0Ak0Knisw-j_q7Jx3OVAJnuS1o3vPLfJxJdyq6yV1k",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "B8cM-zIVauNixLa1CZKqPQTWfziMy8ktivfHJQ51O5BAfY5u_cC1JWEYuvPrnMba1Hh9VlOjOYOCk0lUg5OotMx5hxym6M5y_2rIrW90a8r3gGttPlZmyHQnFIgj2QZHlEyZGU1SIPTOtoECW5RGk7cYpA1s1_zfz8uyG-MiCPE",
|
||||||
|
"p": "2dwia5iWWLZwFcCVylPh_7QDr3Wxt3kW_TmHIbaRT10Rborj1lAwltyEx7aVpHq-aNfvrYIEBbOXWwUU8PDUrQ",
|
||||||
|
"q": "_XiDT55hn-Txi2C_peMVSDgXozf01qHKvdLirXM2uT_CI8kruYZYjWYn6_cSUptJ60eioM3WNTxjrSxWRS923Q"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_2048_0 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "13b668c7b4f5d46d53de65e4b9f9c522fad4870e4d1656e459c88de6e90984d6",
|
||||||
|
"n": "1j-CxBxkn20C_M24t6ueLp02T4MMAiPLXf5yaDcj7EcsXGbnsGEhrAUwCZwdEJAxeZ0PolmlE0XBOhQfRtsCVJmwu918aknptToyDbOUBr6WtPIK_c_BuGVanLCx3SnczV4jle9Bz7tGfpj2vAXytSBrfnZhdCHNEFeefQTQMnavfMhfWf_njiTa76BRyAHjb-XZIJHKovwBu0y3glmzhSKYNsUrW11RsWx6DbueWEbE3FpsHTiEdnJipcP3UKl3Z2z6t6n9ZYtFWkx4zCVVBQu-RWUQwjr2XnR1LFwXgL9xQocDBmS1O-wMTHqL3_oosNUdV3vuMPdxs_SEoys2rQ",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_2048_0_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "13b668c7b4f5d46d53de65e4b9f9c522fad4870e4d1656e459c88de6e90984d6",
|
||||||
|
"n": "1j-CxBxkn20C_M24t6ueLp02T4MMAiPLXf5yaDcj7EcsXGbnsGEhrAUwCZwdEJAxeZ0PolmlE0XBOhQfRtsCVJmwu918aknptToyDbOUBr6WtPIK_c_BuGVanLCx3SnczV4jle9Bz7tGfpj2vAXytSBrfnZhdCHNEFeefQTQMnavfMhfWf_njiTa76BRyAHjb-XZIJHKovwBu0y3glmzhSKYNsUrW11RsWx6DbueWEbE3FpsHTiEdnJipcP3UKl3Z2z6t6n9ZYtFWkx4zCVVBQu-RWUQwjr2XnR1LFwXgL9xQocDBmS1O-wMTHqL3_oosNUdV3vuMPdxs_SEoys2rQ",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "C8F4X2Jfcw_8Nfrjw9A64bvmmv5JzmRAaGvpwyYjZneRS5Cp7demjVXLiPtz7NC8pjuj-_iHQkN1ksY_4RdrTVERjX1dskdT94m17WKJIMWcZ1lQmRSpQIDvM-HOIKCHaQ1dToDOT6Oq_o9OGosJAj9BJrNALasdIWRtYda9xcb7roLl_U3AtOlK9RiFygtt5uVBIPh1rsxaT0Y1MvMk4EMbFnv7NXXk65UM_3p2leSoPpO7LvlYs3WoRRX9ABH7zI-ppwLGEDuxfnwKzAOaRBe9oUh5rTYdazaY9XqofvekBc9Xqa2HpjkYX-L4Zy3oj04u-u5zX8KTh25jFC2a0Q",
|
||||||
|
"p": "6L6icXqV7jLSXHrhMyLpW-tdFFp2XRQ_uKk972jmUJ-sEeh1YYSx_JpK6oaUnYshGbRLfEAOLy03iweAL4uMZJcASOR44TwSnn_qJZ72PkwhD42uKhE0eJRpxVht6jf5qhsynaMNUWc_FIW5ESpQIf6j4rqFV5WrKHAt0ypChTc",
|
||||||
|
"q": "66fAza4rLvyjqQAIGQ07RiRYW4no1h4cK8RH_CDUgI3CCFQSuqTB8ZqM38r2u0tZoKzCNgHcnde6Jk07X6-LSQW2kypMXt3idbgaeVSYSbdZndgG_jTibJvoybxSjo6HfLpPvCrfsihXo7O5M13LE7VqBLbGTLI5iubOxcrfFTs"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_2048_1 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "6d2b0efff0011bbbbf694de684b5b97cf454fd4086fa53b9af92545f3fa65459",
|
||||||
|
"n": "1CmSBUbxU1jjnmaBG0r0cLLmyZgCOMbfpG6Z3HwfVDgCK6P-rU3F6QQazrOgGJJ0sz6vP50VK5u7BR6vSrPBBX5CicJPM2iNdz2JuV9ODEIkDQBLeI6TfIGhNOls-14tXKOPExY8b6JdyDMP31Hwo_0pF1FaunZ7yY1bgoKCtDV5-RKGd2EgylDGNPu0Ilr92MqCsAntBC8eQSkO4CcTcti4t9cX45VY5nPtwQRmp5zIgUHnU4LV3QVTLJnU-uidaAxRVQbS1pVql5xR6nYZHYvFk1IU-wTY6gGk5WvGWWQ440UTTaMAfnJP6VFDggUXeGSlKfKkDcz7JLT2Ma2KXQ",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_2048_1_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "6d2b0efff0011bbbbf694de684b5b97cf454fd4086fa53b9af92545f3fa65459",
|
||||||
|
"n": "1CmSBUbxU1jjnmaBG0r0cLLmyZgCOMbfpG6Z3HwfVDgCK6P-rU3F6QQazrOgGJJ0sz6vP50VK5u7BR6vSrPBBX5CicJPM2iNdz2JuV9ODEIkDQBLeI6TfIGhNOls-14tXKOPExY8b6JdyDMP31Hwo_0pF1FaunZ7yY1bgoKCtDV5-RKGd2EgylDGNPu0Ilr92MqCsAntBC8eQSkO4CcTcti4t9cX45VY5nPtwQRmp5zIgUHnU4LV3QVTLJnU-uidaAxRVQbS1pVql5xR6nYZHYvFk1IU-wTY6gGk5WvGWWQ440UTTaMAfnJP6VFDggUXeGSlKfKkDcz7JLT2Ma2KXQ",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "DYJcHuPmh-UYEUT7oY5DRE3P7jQ0qALZyLGWMHji0c0DLl4x4D0chfrR7il33zisH6G1LPrGl1FCNlA-3yXU-5GPkRADVQWqRFZxx5Du-k7X1tAW_iUt9PaYGjNm0hasEsMDYDbBQGZ5TD8cGp8wEHEVRbvTaB4VQb8zfXrr8ad8XCFdtYQF5Sw_VuvUBcn-50kdl7S0MmQiwLx5xkisJHkVdVsrWbq-JJPMYrjYzPGo07kt8pQH2ecxrQD2waTzkLiAg-nytPBkAyMIFQ-XCulWCNiI-V_HJ0pqNctgVpdaSihlPCYhfllxUSU8yg4UvcfFEAN2S5yjvPyrJeJLAQ",
|
||||||
|
"p": "4db4J-vxAyb_3otKidzYISIo8NuPxPXuYeImbvVbY6CWHdAYAkPgbwADh592D1vM6kwqZLot4VQ4XUVtX7Vf8tCaq5BAzcVAslK0rhK86kSmzuvtpS1ruzm1DGwJcQWOl_9qYnaAov8Ny0NG8fTm8iwvWJH7rVa3XNtPL0t_fx0",
|
||||||
|
"q": "8H7_sX4kY3_4t90HbA8IvVkTf3-OGaIjtcDn2J3HLq1wMKAtdxwsbMtjhLYP70PPBe_FBNpcWhE7p-tOVhH1i0N0wwFqwlRQy-3Z4oiLeSsy0Npl29cfP7teO5UTrbrqfmHB0j2u_nEqA4n4iDF5QhPr_9yzxSR4Pg56z4PgFEE"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_2048_2 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "7988e9dea9647281b04d094e4dc5737eaf739a412a570aa4d6deed7ee5640c30",
|
||||||
|
"n": "wWOFgHHiJ54ZjQEKxvfqYDuhYymbfvYxvreroqx7E-cAuU4QBsOvKV3HNnH_vDRQE1AlqihNWmPFLptp7wxdMrLEtDnRFqTxyTXJ7wLCaaaB6Wwx1dhpr9QEG5-8rxLGFYv2w5i0-o76JPHG0tuqf0YNmHp9oWcv524XnDBuji4-u6km1KynT1EQKHYgy57JWgUpukgJGImBEvf0hC2Y5s5mHvVby3xAD9-cFa2o9Vj3G-SBYFsrRIVR8GcVNwo88oj8F7-2nGD_wOOu_qT_5I3vfToUYGj5-2rAK1ja0bZpXFibrkPrW4w9paG-3F7I0sPeL3e_BDC_rQ8TgL03uQ",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_2048_2_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "7988e9dea9647281b04d094e4dc5737eaf739a412a570aa4d6deed7ee5640c30",
|
||||||
|
"n": "wWOFgHHiJ54ZjQEKxvfqYDuhYymbfvYxvreroqx7E-cAuU4QBsOvKV3HNnH_vDRQE1AlqihNWmPFLptp7wxdMrLEtDnRFqTxyTXJ7wLCaaaB6Wwx1dhpr9QEG5-8rxLGFYv2w5i0-o76JPHG0tuqf0YNmHp9oWcv524XnDBuji4-u6km1KynT1EQKHYgy57JWgUpukgJGImBEvf0hC2Y5s5mHvVby3xAD9-cFa2o9Vj3G-SBYFsrRIVR8GcVNwo88oj8F7-2nGD_wOOu_qT_5I3vfToUYGj5-2rAK1ja0bZpXFibrkPrW4w9paG-3F7I0sPeL3e_BDC_rQ8TgL03uQ",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "RPC4j-CJUbw_uY-Miv-oMuQvFU2o3Crh8u5BJn28ZozsKiMU_YRW9jUzJkqfczVm8muY8b7qTHXSvlmy-v_6XW9zRhhyXFMyypr9QNJIAifUmiTy4xwCGSdIy5w3RGY57UZ3EqVmpwe_TtpOGa8rabHMePX5wUcqwaLykcCGOPLOFKfPF93GqZYi2StS1IaiijLi2IAMhxQGqS6Ct3dA7yacQwegPiDzjb4Is3V9UQ9k_aS3I1w56tz5qspVmfDEuuWToc-2Qyk-eMKWl1tTDJuGTCiS5IFtMPerRpPq9GCpycV0csIW-6AnW79b038DxWjDAUsNnDPnX0y1dQ1G3Q",
|
||||||
|
"p": "yh2KH_JqU29L2SLG5-ul1EBbaSbt3F5UUZbCvm_MIsiH1TzafVyBd8FeG1d-pMR8bgGjaDSA-DNkOEDAeAlLx-UCTl2Uk2ySoMnZr5JjVLW2DTuF7e8ySKrKQSSLe63aQyKuIZh9P1RNrwrmFwvJgXdzudVjirmdTwxAN0lijE8",
|
||||||
|
"q": "9PJitAE_kk6SoKvJisXbDb7Xursj26xiYIdWuzlzQ1CbwYcJ1oORDpo0bEpy0BfIbZDiJJ79Xrh5Lo1BVysQ-IZJCXK7mavqiSa8g3B8rmqLXmAtjDbwLGsJOayCpYnsGgOLT7XIUYXCFH7C-tSSydhJ1j8JAbsJveMiBPKnUXc"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_2048_3 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "d214aea9b78d15dd2667f5d19c36df6647fa50023140fd4f5866d285718b897c",
|
||||||
|
"n": "qFnqWpBOZ_5jnIr_XkXnonmHII5gKzxPhUBfvWBhGN2eH8nmnGh6aUnKyKuCLP_qfYLU7cf9bah-e00451iGite8Tg9ZMYAPFX4NM5j0rGWNv9Z6lSn1xXezJv-FU9VUwXm0DG5eVcB8OV99JWxHivQUSBzY0Q3DUlgrD7FhMd7BrrcmTUO07KnW6SxN3oTbT7fNMxfJSTRxmvU-t-6sLhYP4Wbg39zEUgI8M7b7tvHp34klSqOn2DibVBhvWGF1-IgNT7ng6pS5iAZN7Cz7NjPc9ZM_IWyngPKZV49Tf-ikX2O8uiCb4ccgRur6znwkE7MPrDTXSMHALn10sUcO_w",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_2048_3_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "d214aea9b78d15dd2667f5d19c36df6647fa50023140fd4f5866d285718b897c",
|
||||||
|
"n": "qFnqWpBOZ_5jnIr_XkXnonmHII5gKzxPhUBfvWBhGN2eH8nmnGh6aUnKyKuCLP_qfYLU7cf9bah-e00451iGite8Tg9ZMYAPFX4NM5j0rGWNv9Z6lSn1xXezJv-FU9VUwXm0DG5eVcB8OV99JWxHivQUSBzY0Q3DUlgrD7FhMd7BrrcmTUO07KnW6SxN3oTbT7fNMxfJSTRxmvU-t-6sLhYP4Wbg39zEUgI8M7b7tvHp34klSqOn2DibVBhvWGF1-IgNT7ng6pS5iAZN7Cz7NjPc9ZM_IWyngPKZV49Tf-ikX2O8uiCb4ccgRur6znwkE7MPrDTXSMHALn10sUcO_w",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "cCIT2uarktD6gFaE6cIeGzZfLuwmWiX9wX-zRWxgwDM9E2dj12IvxtmD3E2Ak4CSK69tLEQ9JUFJnc89y7pHQ0uW_VdzzWjCo0omeOu0bO_njpPJanlcXn7wMVWY9NHvdj8eEfmhk_R1ybE0piyNKpyQtcehEv3bz4kyhW1ck936zafn5GWxCEWKIF-7OcotxtOl6z1GrJ7WqglMk8ooLucnAXzaPtcvD6seUhVH0vG60yI2AEInI_jMHR-mfDxrebG2xPPJLsqH3GChqTsxG5vjuaq71sNBiY_TbaUDbmWZxV66zdjhN93Rw-lc3vCPwG5vmuA2igWH7SKsHmQOAQ",
|
||||||
|
"p": "xvTQi8nCJGyZQm3fr7HU23FkSUYkKFUN1FRDWqjyz7kFj5rJxq5dSsNrrgVBvIBNY4TwDDcYia4rvx7KSRRwfV_rsU2-TCOxBebqVlbeV0HI7xMcVZXsv61Eugz-dznUU4wqmP_rCQ8vyNcVvCBbBLBW92n-IRQXsfuSUfuHnT8",
|
||||||
|
"q": "2J64YbgvH62PB4h-0iYuaykgUzWVSrXENg3_GaF8877AMTe98gjcH7HzTDW1fOhIcNZtmYTwlA2SgFudLsIJLoOn0-kmHzZjZJTWcsZsfTVwKhHD8Lzic7QkrrlbTIZlVu4mfT2f0kvaQiZp6zDCke3SLvVjlD-ebqTFCDIlXkE"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_4096_0 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "4941c1e95df518587e54ddde3510c084fedf92098473537fb3fe022eaece9b42",
|
||||||
|
"n": "ypJYaH9_PLdEgGN3WFPL1V9qhC2BRXr24f1kGocaQe7wORFlZGX6gRpOCQ1lLmziLafERoSqZuBWF4vSGLeanCRQ7UKObYalMRR_rTJk6D_VD95LNOmtR4DrucdZvjQrAuLXhM5dNLGVoSq_zQowLDMeLWDUBB_TK4WlofoxI0RVoUOt4UXnSev8M_6yIYASpOAk8gG4FAqRfmU-3JhgkZOQCD0BhKYZVw_kEhRBrS3aik-1pQkan9hYIxWwc4KKtrLHNls7--vk5xDk2Vod5sZYRLlimbqHavN2IBeAeJGJqt1grXOVlnagKqmyq2MoPKufwptJBiVrVsTplyUB9FZe9FxUfxrkkxYJHufwP-3wXhaXpPFdL86TCUuOz-jTfng1rsTpwzmwm3RqFzz01yMc1RhaeWininQqj42bhW4bgFGVRP2QqSFbCbRZ4WMW3qWr3aR2QpJ7b8ac7JUhAJoh7-UtMmyGx9UJP0gjA8Wm-O81UENoDjMMdNUaVTfpSUKB7xZppg244jNZDIm1ptq4QyvvWkr0brp9Ymxpu0e3g2HyywyPjbeyNrIelb4E2tU5vCR4Fs_zGFG71AieruJGfzPTXE7ZaiLhP8n1652NCzIH76Y7OGz4ap2D55-RKpUkSI3KqWxUKxkM0tuZ7LS2F-1wEVs8P01K6RuQpxE",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_4096_0_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "4941c1e95df518587e54ddde3510c084fedf92098473537fb3fe022eaece9b42",
|
||||||
|
"n": "ypJYaH9_PLdEgGN3WFPL1V9qhC2BRXr24f1kGocaQe7wORFlZGX6gRpOCQ1lLmziLafERoSqZuBWF4vSGLeanCRQ7UKObYalMRR_rTJk6D_VD95LNOmtR4DrucdZvjQrAuLXhM5dNLGVoSq_zQowLDMeLWDUBB_TK4WlofoxI0RVoUOt4UXnSev8M_6yIYASpOAk8gG4FAqRfmU-3JhgkZOQCD0BhKYZVw_kEhRBrS3aik-1pQkan9hYIxWwc4KKtrLHNls7--vk5xDk2Vod5sZYRLlimbqHavN2IBeAeJGJqt1grXOVlnagKqmyq2MoPKufwptJBiVrVsTplyUB9FZe9FxUfxrkkxYJHufwP-3wXhaXpPFdL86TCUuOz-jTfng1rsTpwzmwm3RqFzz01yMc1RhaeWininQqj42bhW4bgFGVRP2QqSFbCbRZ4WMW3qWr3aR2QpJ7b8ac7JUhAJoh7-UtMmyGx9UJP0gjA8Wm-O81UENoDjMMdNUaVTfpSUKB7xZppg244jNZDIm1ptq4QyvvWkr0brp9Ymxpu0e3g2HyywyPjbeyNrIelb4E2tU5vCR4Fs_zGFG71AieruJGfzPTXE7ZaiLhP8n1652NCzIH76Y7OGz4ap2D55-RKpUkSI3KqWxUKxkM0tuZ7LS2F-1wEVs8P01K6RuQpxE",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "VQagPRxm16FFC260hUqG4ASwvNIs1HEMd0bYYZobl1knU4zNthpnzxCveHU65wWk2ez1IXRF4fB_slpp0R4fszI7FZs-FRLS-4rTHGtul11TnNl9T7RVmxGt38ihDojvFMMKGyBTVu7DE2bSIsoH9kVugTWHSEPjav0pzJcrUNY56vpxXYDt18VJkrlxI0aSjMnYOAwoq6DT-O2eORFsVy5M4mhY3sipEjYFUOFXv8zjUfKrF55-omE4fWF5MsK0XoMjwtkAkHkvFx2sMN72dgsCubXmgQgeFvIhvs6eifzsf99z2NoPC5y3FbEs4Ws5VF3lLNXpDL9gEoeMVHigHKNoztIzbJnFLjzEag4wOBqbig4nWIlYEcLwd5-E0oQ8GHAhWtJn5AJQKOwTcasRWiUnRAulCcI8f9R2ATTSjAGU-juFUfhProp4w3ZlnUbO6U3tbh90KdFSJa-Bapts6ijPgEp6MHubVCHIxh1KGmod_CuGGjze1GuISEwI_4Yh0SrFlp4Wy6ecg7mKNnXvkBMd6h90LJaTzubnts33cCs-5UzCtYqmWHwXrMA6dRJuicY6su3NXxStuZn2bxU_Dn8LrS1vx_lhDU__NqMNbYj5ZvHkPvZiJNBI6Z1R5SY3pQzpH6vh9W2KRKF8cKOkushIaHNnHo7W5o4eZ9HjJFE",
|
||||||
|
"p": "7Bxd2wTrXfeSZAEGZEnfoJaMh6hKFG-fx0wLmF9U-Myq1fCIQ-gdl_Atqy0xQxYtQl2lOPUWfySegJSxpdRF8pwvLq9Yt7xFQDrV0B8d5-FjYJ_Mk2bbyHv_PhNmnfcYHqE_VdADeSaU-MRV1iknvjdbxSxYUF-U2C6llfJhn-ZbGPmZJyz8fTWHSh8aKLPJkuYV84wcNL04hpFPJq7Bhy7HAFhRYNpxXyDO3I36DCU94uvUCY758VKLl8IYMMSqjh-thluKJSkWibo85ZOJR38XFksMr70pH-DkT0WMv3PpLtNu6ac6Ry4CEkZexSWmbVk-pduRooo0On1CMsrsLw",
|
||||||
|
"q": "26K4gFJ0Ygqr4KaJ4QcRGN29XcjDpX5XTlHy3hCs_wEWc5wedydVgIH4tadiZcu0WinTtl0I6FYewWNpI8nFh8XJXsLOLepdKKR798zW8kEhzxdYdP1MTtGfy12KUCMeS2RQRsBJlmNdgrI0_m0JvW2LlGeK0EqvrqeS06L8bKGXkfY2aFGHl6ipSfUd5OpuSUqgfrj4-HgB8zywm-yAvn7UK-ySyQrP3K4PuOiY5QZmcPDxl8yqDAjorStrjPieYrlMqREHMsyq2FtAXhx4FzpKN7OljbE3EpgyFKkFN-wDTWRGxu3au0hQJx0-XthZ9i5r8zGcEbArQ5jZH7CQvw"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_4096_1 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "d01929d205e83facdc5080a0a4ade80853978298a5c73780e863c035d32127c9",
|
||||||
|
"n": "ve_rSTi1vwfV2Oj7YNu1kv8Xz2ImAEqf7qo2RehTYW4FJpbj0wkMBVGmAulm4dz-1knnaPdpbrrIwkEYr1XR6kSIeB8aXkCZqDGZATxLSRqGEzu2J8Xt9z_qTQLWYD-NuMEf9H3B1CiP3Q2RnoWUlGvOr_KI5h6anzeqgZxlQLgqQujXscwqWpX7MVh3sheZ5SbyTkDcFWvQS_NEPaBGso-Au9X-yhZsHU1Ky02Nd_DPOrrRzl7uymE07hy3bIbxmrh4ZeEz6P2ixsHHYbd15GNMRlr1cWbg91RBB-akSxX0VYoLjjuqwo33UHk1hBbSATbobfpOKRruuZPZ_xOiPi-m0tUdD1Pj-h6xRcA5sZ1d55IdL8IY_9kgLc_WyU2RWFTXV6zA2SDDdE6EdEB_8q6U0oLU5T-YRxd-V2qOTqW1388qUaL5BalSvqM0g8kVfBYIM3uwLqQI_hCer4CL7_0DGlUtheye3qcoyqWVJd9iqfcWC-1S8NljAJwM9sehx6kOSM85UuUTH89VM35oAVp0rSPcLy20dPzEVQ0LAcy_iRHTkqy9nZ1z6mo-IWQE2ZyPCuESEfG7nFJ3YlUfwBJ5uZnk0N1FVYX5zgEKdkP322vShrv1blB_JtUedpbCcuS-qCaywpwjL0pzTDKpJMEqHds0-DFT7AbULcujw_U",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_4096_1_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "d01929d205e83facdc5080a0a4ade80853978298a5c73780e863c035d32127c9",
|
||||||
|
"n": "ve_rSTi1vwfV2Oj7YNu1kv8Xz2ImAEqf7qo2RehTYW4FJpbj0wkMBVGmAulm4dz-1knnaPdpbrrIwkEYr1XR6kSIeB8aXkCZqDGZATxLSRqGEzu2J8Xt9z_qTQLWYD-NuMEf9H3B1CiP3Q2RnoWUlGvOr_KI5h6anzeqgZxlQLgqQujXscwqWpX7MVh3sheZ5SbyTkDcFWvQS_NEPaBGso-Au9X-yhZsHU1Ky02Nd_DPOrrRzl7uymE07hy3bIbxmrh4ZeEz6P2ixsHHYbd15GNMRlr1cWbg91RBB-akSxX0VYoLjjuqwo33UHk1hBbSATbobfpOKRruuZPZ_xOiPi-m0tUdD1Pj-h6xRcA5sZ1d55IdL8IY_9kgLc_WyU2RWFTXV6zA2SDDdE6EdEB_8q6U0oLU5T-YRxd-V2qOTqW1388qUaL5BalSvqM0g8kVfBYIM3uwLqQI_hCer4CL7_0DGlUtheye3qcoyqWVJd9iqfcWC-1S8NljAJwM9sehx6kOSM85UuUTH89VM35oAVp0rSPcLy20dPzEVQ0LAcy_iRHTkqy9nZ1z6mo-IWQE2ZyPCuESEfG7nFJ3YlUfwBJ5uZnk0N1FVYX5zgEKdkP322vShrv1blB_JtUedpbCcuS-qCaywpwjL0pzTDKpJMEqHds0-DFT7AbULcujw_U",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "mCjZ3wDVaMJIKMsMhx28KpS9aGACfX1K_pHRhNOH6KeQ7Mc4oFnBDYnJas-8ofi_FsCB6G88QX7VUfmAYwZncjuQ8FpKb3NlJX8GSh0ZWukqu8G8PcSszMShWSyKvPRs_rOIe_87BlGwXrB-FfaBfx2WqRGtZlziFecsa0T1QJHJGW0bTs52p7c7Ut7ClSOfIBrBRrtjFK4YYp_x7US3HlkkElZvFUo9NoQzBQeN66Y4_Z2ocqFOv0Z8drz-nKzGZOKfYU62nVKD0qJurfOhOGPsOPipZD28v6b5qfC1cYmXAefjNgDK3a2JkShpHPaDKoHoViKN9xQiZvzxSQ1bjQCgB6xQWk61YwmiB44gFLshXvXkx_wcccO2XC4m-Pk0r-Uj9Ugvgizk8HYp1H9eqnp8A6sqU44mrmdt14vM68SGzEqYpLoUkcxPttHvNoORAzgQfuK3SOIFyuwFqKTMBupR4A42XvxLj_PuKEKFekS2JEK8fON2t_LuUDVemjMxwXD0gjljHSKD9HnguihI5IEd6Kgr-HlTwiiDQfzcmWQdoFUs_Je4tVCSbGRrJYju01gLouDu7gzN14pZcEyoBLPkj-_MDiiymvlBLOBU2s5nQRZCaC_0IKgHLaT_rzQaXwmVhU-OURdwdoViMVc_cdXtle4TqU1g41nNE_0tiwE",
|
||||||
|
"p": "2h_9HZCOpf3S9qpAkNS2ncMsRjBlWCpRltoCs-XBF_IgOJlCkJ-42k_V1zaf6YrgMy00XD4Ij5MMiUOharvc3w_WKJF-AwGu9JEpi38Wzj1nkY86sdB1ZPu4ihSRZm81jIjuLZ2siILqkupXaY0S_0CeNLKPoVpvfPWCb3psCWuLVRDhYECPCx9FYg2tzisbFpVAPPqNCFswMYNvhyJ2NQ0hzueKDc1uHBUanCSG6gIbeY1bQvYYFGySYkWZcQg98RSeDbLysHalCSQCgPjss3likTT9OjWLv23vpXURFhgywv6Phx4He0yWB8ZRrnskfW0S76kZ325SPUlnOhf31Q",
|
||||||
|
"q": "3urwELOa7TDpwLMeMEOf2aspz1gkMjoN-V4SMzRGxIoPy93-bQ62Z8nPduma1gK0zHOIs0gU8dDqjy8P8Z7vudJqRFJIrzjD-RnDqQDz7KM_6x0kbEN4GLQiNGVAckRhoEmYjDXXkG3teD2ST5NqlYwxGvFmCvw5RCJZG7eNjXY3KnJQrDyx61NIFEvSm3sBlJkOVhSanuqHBL6T-efaBkUcQ7mY7_iYtCt7cjMG8qQtF0KovVsMF6BUc9KDobpUvDsvPrSTAzsYJvVNkzGJtF8Strr-61VA6qZnRcDW3ukYvmJp_SdAcv3KzSkWwEmJEaAtaiOh2jWkRG9QpV_LoQ"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_4096_2 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "822512e8098b9fa56c9152a56d2607bda912c89f6f4fe1b6ccf41627bf06de29",
|
||||||
|
"n": "3bcg0diA0TPvK283P02o5UT8cBf5q48uUsfuCkIbLqNFtskGuoft8OJLmik7c7pLNSK3Jad_-xRlfsrcV1fDBs8EJDFnoY-4r_xk_-GQl-OsHzuz7HMvjVYUSYeVZMtzPAfCIFJxSIaiIW9MEwCwYyrtRefedfKUV7Ax6hjOUXtyRZ5DRHj2FnNl6MjE8mTSJ8OQI9ETErg0wF448fJb6hYCidVHfeBSCOceljXMg8a9iDYHVnzsUb9ETl7xlRZDvUpIoG0jU-aC3oZdM0YgXspvw4FcXg1-ad-TbgRjEm_iJ-4KUhAYso6-Y6eimDT_TMhbKBLGE86pUjRPIXVNl6odjkXmcmh7bz9v0-gIfHB9zVbrqE8i8iIwkux-Uhq_VMiIFikjtjmDo5W-7qA_cjvq1dA1QqOJ54ijbdvglUtneuA5CnwfGHAvf94lEPebgP5eCMZjz_Dtl0FTX0u5lOVBs4qeyEfV1XANOq_h1gHmwJDlspGZVuPfSk4-YMCeBy5Ua_gNDqOIQo3EQbmjyN5yD9byFh3WDSQJN1kKT3BZSou-Q8-KT2lu9KT_CpTfVMaNXnPpXJ_H0S9Q7sbUzA6dF1h0Q_mu_bOlWTRlH3vXfZ3_3Vikjk-jhQdaRv8yjTdhX33wAVHk7omKlo76G3QJdnvjysJ6aagFPZ3qhmc",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_4096_2_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "822512e8098b9fa56c9152a56d2607bda912c89f6f4fe1b6ccf41627bf06de29",
|
||||||
|
"n": "3bcg0diA0TPvK283P02o5UT8cBf5q48uUsfuCkIbLqNFtskGuoft8OJLmik7c7pLNSK3Jad_-xRlfsrcV1fDBs8EJDFnoY-4r_xk_-GQl-OsHzuz7HMvjVYUSYeVZMtzPAfCIFJxSIaiIW9MEwCwYyrtRefedfKUV7Ax6hjOUXtyRZ5DRHj2FnNl6MjE8mTSJ8OQI9ETErg0wF448fJb6hYCidVHfeBSCOceljXMg8a9iDYHVnzsUb9ETl7xlRZDvUpIoG0jU-aC3oZdM0YgXspvw4FcXg1-ad-TbgRjEm_iJ-4KUhAYso6-Y6eimDT_TMhbKBLGE86pUjRPIXVNl6odjkXmcmh7bz9v0-gIfHB9zVbrqE8i8iIwkux-Uhq_VMiIFikjtjmDo5W-7qA_cjvq1dA1QqOJ54ijbdvglUtneuA5CnwfGHAvf94lEPebgP5eCMZjz_Dtl0FTX0u5lOVBs4qeyEfV1XANOq_h1gHmwJDlspGZVuPfSk4-YMCeBy5Ua_gNDqOIQo3EQbmjyN5yD9byFh3WDSQJN1kKT3BZSou-Q8-KT2lu9KT_CpTfVMaNXnPpXJ_H0S9Q7sbUzA6dF1h0Q_mu_bOlWTRlH3vXfZ3_3Vikjk-jhQdaRv8yjTdhX33wAVHk7omKlo76G3QJdnvjysJ6aagFPZ3qhmc",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "wcO4mAxJUAu-OsxgkR9SusPWhjQ9y5Q_XLNDso1hahng5ES9b6k55mouvlTIk3Q9I_vp6auAKrMBnJS3ilG1rK6hJOxUcBrFwm-m6QV9s3CSzV0E-mEULsYKxtQKWOOBGvaAznSeck7PRL8a0gSpIpGyeYSRo6zTverLRJZXQVjMXlFY4m-ASdCiQJWtoVVBYOUFhHfE3nhECdaOl8xCTcrcfw75AuZXa1ZpIcd0q7m1jGQDd6-HbE3m6UMKiEvD-ZsA68tVs45h0w3ER_pCcfUjRc45Ji1OzEJLezu0RbmoAVOEi4FrxCkB9N_dNn4inD0BhX0axNa4nZH_kfMNUh06081FaBiBq5bNBPQF7lWIYxAhtd2VgsUTZtNxMfHDTllt5fvbCogYngU5-Wj3UM33R_es_aakt_CFndJPmyenP0J7KEBmf4qnlrbxun-jQNV_cbEzgQA_Pt5RIhB5jgofYzT72Ib-lViZUolXpxg8mFQ5BPghqriuaX8eSc85y5rbu-nEvheDkIC7xjTtDBShXMvKKokx6t4d-x_W4sWDuLKsVNn3Veg6mbbl3O74Fr-joEM9unMg-9KCapAuBKmMv4GejOVOIj43i0lm8dsuMslNKsThWpjApE50-VBuySi1f0jD2HNEzjMTt5ZrlNi9bRFzAht46kdc_g4TWLE",
|
||||||
|
"p": "7cl5QebQldiOrfe2Z7E2aP4d1GIuX4b3u-kXUDa73HI4S2u9TooJF8lfsbrtzJIG60iIzio9Pej8R4ss2_1TPC_F7gQS67ST5yY2zuwUH6jwpt8KtVO3NKd9SNfVJse_mZLp7eAeH8CMq4bqkDig8ZI_Sp1LQtWXvhvhju40i0h1ef-MD36wbc26BE1L6tQMAoJIU-pMDawmWMGG3Q5_LxKHb1cxvV8gVfFgsJbAHMPXxcmpCdp1FqeqpvRs3bch3AMxHwjy1SFRK6lhvliS2ZvD7fdupA09PGJ-cNO3S9iHdCvsnyOHg9pMbxxcYW8Io_t1ihk6JYFvZlDIYvukXw",
|
||||||
|
"q": "7rKFf6190MlunvlLSdMvN_E2Xuow35_EThaxQ9e_7FS32LKPy847vFreXzVivVW1IVXrRSDgU8VIEyBQzftliLSriefKYdx2jtd_A84baha4p6CAK9SwfZcVEcIuU2Ul8XcUpbg53_bxUWDv4mYewZW_AqaZ0ZR1Ug14gCGHELvjs7MmWkCkJB6lEenRQZvzwcmA8tWzZ6Dlqb_RyLtxcVPbeSuc3t6YxbB1-pquwXoQLB9-XvgR_4L6vuGZrp1D-C0lzboUPoftkC_hKYb_xY6LPJTsL8LUypATcTrAnD0TBrqj5-Oy_4dMin6OcsXrfdxflxaJqkh7B-kz20oa-Q"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testKeyRSA_4096_3 = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "ad60df152305985e6bdd8cb1d1f496f766aed097b7464b8b821a09b27f054dcb",
|
||||||
|
"n": "q8mHub9-A7LH2ykklO52CRQ9KYeM9FlHaoLJCMokmwdb8FF8cAMOPDQ4lx7rB107vwX5oHS6T4uhOUH6ppgZnvB083-f5cBfvZPSwvKg1csOPI3Llp-hIO3vnu7v4M-dXRJevmNdTAoHINXP8mJ1KfGL6BuxckxLEOoIX0suSmyudAE_lc6ioMAfwehkH7qTWwllIuReGwxHHYapM0AFIYysJ0z96l-WNkO3ssLm6Q7txpumM_b0d58OkdezBLujEfn0t5H8hsml_LUSE4TfWcGwm3oDBB2q5tsI2_QmVwdV6BcP43Sm8R7EbJvTQq3YXKLlC-Et4-3CDGaMV6z1eFmHBs_FMGzto-kg9MUFockOe0vkfjHj6AafNw7AFyBJRvV3EdMM-O1ysiC5cs45aowbfQIz_9H9RVbR9iMiEjq6nM34bhgfu-FvOmAcliCOJnDlBy2wWe2XyXTLQVkmn1C_w2mitcQAkP-LiktypUwSIbyDqfBai1_imO1SIVzomRLvzSju5qGrcm0ROV6o9zKXCVc546g9la9FUTTPZsI0_s3hcVRVMaF3fa122hE848serjCqNJ0Nb3CYOlVCc6JrkUdeuCj0Y-on6V6aeCFAQxFb1z_p88STsNJkrcNZp87f1JUaSB4POF01dLNQ2HTe7xJCMRmK-fpNuDCOzNM",
|
||||||
|
"e": "AQAB"
|
||||||
|
}`)
|
||||||
|
testKeyRSA_4096_3_Priv = mustLoadJWK(`{
|
||||||
|
"kty": "RSA",
|
||||||
|
"kid": "ad60df152305985e6bdd8cb1d1f496f766aed097b7464b8b821a09b27f054dcb",
|
||||||
|
"n": "q8mHub9-A7LH2ykklO52CRQ9KYeM9FlHaoLJCMokmwdb8FF8cAMOPDQ4lx7rB107vwX5oHS6T4uhOUH6ppgZnvB083-f5cBfvZPSwvKg1csOPI3Llp-hIO3vnu7v4M-dXRJevmNdTAoHINXP8mJ1KfGL6BuxckxLEOoIX0suSmyudAE_lc6ioMAfwehkH7qTWwllIuReGwxHHYapM0AFIYysJ0z96l-WNkO3ssLm6Q7txpumM_b0d58OkdezBLujEfn0t5H8hsml_LUSE4TfWcGwm3oDBB2q5tsI2_QmVwdV6BcP43Sm8R7EbJvTQq3YXKLlC-Et4-3CDGaMV6z1eFmHBs_FMGzto-kg9MUFockOe0vkfjHj6AafNw7AFyBJRvV3EdMM-O1ysiC5cs45aowbfQIz_9H9RVbR9iMiEjq6nM34bhgfu-FvOmAcliCOJnDlBy2wWe2XyXTLQVkmn1C_w2mitcQAkP-LiktypUwSIbyDqfBai1_imO1SIVzomRLvzSju5qGrcm0ROV6o9zKXCVc546g9la9FUTTPZsI0_s3hcVRVMaF3fa122hE848serjCqNJ0Nb3CYOlVCc6JrkUdeuCj0Y-on6V6aeCFAQxFb1z_p88STsNJkrcNZp87f1JUaSB4POF01dLNQ2HTe7xJCMRmK-fpNuDCOzNM",
|
||||||
|
"e": "AQAB",
|
||||||
|
"d": "QFcw8I8aQYRaemlEfEt8BhaAeed9EZ_GscveQ96CK1ZsRuweMU3TrRTaBS_dU1rGH9u7DS_rABQKBIoDuRXKss7Y3sJ0Pvb4ZObSz5VUS_7LjD6HfBi5nr2_O8W-LnNUOyHAPoq0zOAMn221ftEFlPoVLpAAvBB7JRCiph5gbhuak3RMPm2wV4jd3CCQL5oPys8QBCuIW5UTpalkAf_-a_xmFiouB_RZLGXcjaWWGsAuqm5tp5TdJ1h5eoJRWHp2ryrxTzfsXwdzldyzsn_Xr6Rt4y2lp4r9EY4EGW2uVnY25MCOgOCWDkU5yHvselLmcHvKUdK6_11zinV2Jvhuz1FaW10P6lRXxhjuuT4bT7XmY6WkJCUvWf8w_NYrGwDbRvQa6YZcZZWKZ1l2Enkgd_P4qwtrnROQSCe0dJdNG3lSq90lrAwuvb8AhKhpQ8nJSaSQc_h2pa4NJZ-R__9m0_7CRUuEEg9k47AtgdIe09KLSpcACc3W5cBXEw2pL8ihqcf9AVgM0TFQQVrUdIst3YjUiB6r2zLVCRx8KjtT8Pmz6YtMQwDIFTrUopwwai5PEP3QEdxdNF39W1iwkqjA8uw4IlXgZAr4-9s3x617XUL0BQ4TgMpTsEghpV1U8U2HQfYGUKBcAlSqIlAi0MU4QRwcsAJrcoTIi-e34NFMbHE",
|
||||||
|
"p": "xvZ1BKzaRfrmQT2mmbK1MzKdr4amHrx4EJ_4fRsCRBj8MqB92y4Bp0iuO8rZ3iK_yOOgQQTcr4UP5UEACLOqGDPNa92UUpHu1U5XR3MiIY0kGcdovpkkGU8fq78VRpyofc1b9kvZsy4eL0e7W7jApjGu479D_evnpT7lqSOGRl1Z0QHdI3ctKmKw48-AmQWL4BibXTyaXOfRgTHm4AtbrvFshwCZ43QgzrCrwbhZlOfiIIW9wXa_OqqOhV11BLF72qTglecfTgUOxY0W5GWgbwXCVZ8Lwr6cxYKGrKjmny9UNOIjABGsJjZm5egwDNUhhoaEeXXyzXbCKynvUCDxZw",
|
||||||
|
"q": "3Qi1bRzj3TYW3Iw9KSDCuuHWBJRO_3Ke-JLB7PHEXc1hsPwF_XDqXPaBbIUtgKaqNzUehYAQbtHHhJVGqZadTKjsNkO4sO_r5nRoxUpFuGgUEmKNN0QeJa_llHzAZ9JpvUrtoyzVZX-2Em_3aAhtV5pZ6t22YToPDjZIBgqL96MQRCyKsv2FS_dbpoYKdXlG6phzkzKPn3oaWkh-VwmgAsFg7uXdiquQEsXYcOq3-77Umiuke6SBbaHLryLflTzOV7YvY7Kw3s3NycS9MDBESKYXqqX3YKvS25ZFFjYbrVOx5AluLiwajZu2biIPZb9rNbSXE77Hll3tAbmA5syJtQ"
|
||||||
|
}`)
|
||||||
|
)
|
199
vendor/github.com/coreos/go-oidc/jwks.go
generated
vendored
Normal file
199
vendor/github.com/coreos/go-oidc/jwks.go
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pquerna/cachecontrol"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/net/context/ctxhttp"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// keysExpiryDelta is the allowed clock skew between a client and the OpenID Connect
|
||||||
|
// server.
|
||||||
|
//
|
||||||
|
// When keys expire, they are valid for this amount of time after.
|
||||||
|
//
|
||||||
|
// If the keys have not expired, and an ID Token claims it was signed by a key not in
|
||||||
|
// the cache, if and only if the keys expire in this amount of time, the keys will be
|
||||||
|
// updated.
|
||||||
|
const keysExpiryDelta = 30 * time.Second
|
||||||
|
|
||||||
|
func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *remoteKeySet {
|
||||||
|
if now == nil {
|
||||||
|
now = time.Now
|
||||||
|
}
|
||||||
|
return &remoteKeySet{jwksURL: jwksURL, ctx: ctx, now: now}
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteKeySet struct {
|
||||||
|
jwksURL string
|
||||||
|
ctx context.Context
|
||||||
|
now func() time.Time
|
||||||
|
|
||||||
|
// guard all other fields
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// inflightCtx is the context of the current HTTP request to update the keys.
|
||||||
|
// Its Err() method returns any errors encountered during that attempt.
|
||||||
|
//
|
||||||
|
// If nil, there is no inflight request.
|
||||||
|
inflightCtx context.Context
|
||||||
|
|
||||||
|
// A set of cached keys and their expiry.
|
||||||
|
cachedKeys []jose.JSONWebKey
|
||||||
|
expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// errContext is a context with a customizable Err() return value.
|
||||||
|
type errContext struct {
|
||||||
|
context.Context
|
||||||
|
|
||||||
|
cf context.CancelFunc
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrContext(parent context.Context) *errContext {
|
||||||
|
ctx, cancel := context.WithCancel(parent)
|
||||||
|
return &errContext{ctx, cancel, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errContext) Err() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel cancels the errContext causing listeners on Done() to return.
|
||||||
|
func (e errContext) cancel(err error) {
|
||||||
|
e.err = err
|
||||||
|
e.cf()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) keysWithIDFromCache(keyIDs []string) ([]jose.JSONWebKey, bool) {
|
||||||
|
r.mu.Lock()
|
||||||
|
keys, expiry := r.cachedKeys, r.expiry
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
// Have the keys expired?
|
||||||
|
if expiry.Add(keysExpiryDelta).Before(r.now()) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var signingKeys []jose.JSONWebKey
|
||||||
|
for _, key := range keys {
|
||||||
|
if contains(keyIDs, key.KeyID) {
|
||||||
|
signingKeys = append(signingKeys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(signingKeys) == 0 {
|
||||||
|
// Are the keys about to expire?
|
||||||
|
if r.now().Add(keysExpiryDelta).After(expiry) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signingKeys, true
|
||||||
|
}
|
||||||
|
func (r *remoteKeySet) keysWithID(ctx context.Context, keyIDs []string) ([]jose.JSONWebKey, error) {
|
||||||
|
keys, ok := r.keysWithIDFromCache(keyIDs)
|
||||||
|
if ok {
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inflightCtx context.Context
|
||||||
|
func() {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
// If there's not a current inflight request, create one.
|
||||||
|
if r.inflightCtx == nil {
|
||||||
|
// Use the remoteKeySet's context instead of the requests context
|
||||||
|
// because a re-sync is unique to the keys set and will span multiple
|
||||||
|
// requests.
|
||||||
|
errCtx := newErrContext(r.ctx)
|
||||||
|
r.inflightCtx = errCtx
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// TODO(ericchiang): Upstream Kubernetes request that we recover every time
|
||||||
|
// we spawn a goroutine, because panics in a goroutine will bring down the
|
||||||
|
// entire program. There's no way to recover from another goroutine's panic.
|
||||||
|
//
|
||||||
|
// Most users actually want to let the panic propagate and bring down the
|
||||||
|
// program because it implies some unrecoverable state.
|
||||||
|
//
|
||||||
|
// Add a context key to allow the recover behavior.
|
||||||
|
//
|
||||||
|
// See: https://github.com/coreos/go-oidc/issues/89
|
||||||
|
|
||||||
|
// Sync keys and close inflightCtx when that's done.
|
||||||
|
errCtx.cancel(r.updateKeys(r.inflightCtx))
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.inflightCtx = nil
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
inflightCtx = r.inflightCtx
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-inflightCtx.Done():
|
||||||
|
if err := inflightCtx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we've just updated keys, we don't care about the cache miss.
|
||||||
|
keys, _ = r.keysWithIDFromCache(keyIDs)
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) updateKeys(ctx context.Context) error {
|
||||||
|
req, err := http.NewRequest("GET", r.jwksURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("oidc: can't create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := ctxhttp.Do(ctx, clientFromContext(ctx), req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("oidc: get keys failed %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("oidc: read response body: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keySet jose.JSONWebKeySet
|
||||||
|
if err := json.Unmarshal(body, &keySet); err != nil {
|
||||||
|
return fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server doesn't provide cache control headers, assume the
|
||||||
|
// keys expire immediately.
|
||||||
|
expiry := r.now()
|
||||||
|
|
||||||
|
_, e, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{})
|
||||||
|
if err == nil && e.After(expiry) {
|
||||||
|
expiry = e
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.cachedKeys = keySet.Keys
|
||||||
|
r.expiry = expiry
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
99
vendor/github.com/coreos/go-oidc/jwks_test.go
generated
vendored
Normal file
99
vendor/github.com/coreos/go-oidc/jwks_test.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type keyServer struct {
|
||||||
|
keys jose.JSONWebKeySet
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKeyServer(keys ...jose.JSONWebKey) keyServer {
|
||||||
|
return keyServer{
|
||||||
|
keys: jose.JSONWebKeySet{Keys: keys},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := json.NewEncoder(w).Encode(k.keys); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeysFormID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
keys []jose.JSONWebKey
|
||||||
|
keyIDs []string
|
||||||
|
wantKeys []jose.JSONWebKey
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single key",
|
||||||
|
keys: []jose.JSONWebKey{
|
||||||
|
testKeyRSA_2048_0,
|
||||||
|
testKeyECDSA_256_0,
|
||||||
|
},
|
||||||
|
keyIDs: []string{
|
||||||
|
testKeyRSA_2048_0.KeyID,
|
||||||
|
},
|
||||||
|
wantKeys: []jose.JSONWebKey{
|
||||||
|
testKeyRSA_2048_0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one key id matches",
|
||||||
|
keys: []jose.JSONWebKey{
|
||||||
|
testKeyRSA_2048_0,
|
||||||
|
testKeyECDSA_256_0,
|
||||||
|
},
|
||||||
|
keyIDs: []string{
|
||||||
|
testKeyRSA_2048_0.KeyID,
|
||||||
|
testKeyRSA_2048_1.KeyID,
|
||||||
|
},
|
||||||
|
wantKeys: []jose.JSONWebKey{
|
||||||
|
testKeyRSA_2048_0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no valid keys",
|
||||||
|
keys: []jose.JSONWebKey{
|
||||||
|
testKeyRSA_2048_1,
|
||||||
|
testKeyECDSA_256_0,
|
||||||
|
},
|
||||||
|
keyIDs: []string{
|
||||||
|
testKeyRSA_2048_0.KeyID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t0 := time.Now()
|
||||||
|
now := func() time.Time { return t0 }
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
func() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
server := httptest.NewServer(newKeyServer(test.keys...))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
keySet := newRemoteKeySet(ctx, server.URL, now)
|
||||||
|
gotKeys, err := keySet.keysWithID(ctx, test.keyIDs)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", test.name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotKeys, test.wantKeys) {
|
||||||
|
t.Errorf("%s: expected keys=%#v, got=%#v", test.name, test.wantKeys, gotKeys)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
2
vendor/github.com/coreos/go-oidc/key/doc.go
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/key/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package key is DEPRECATED. Use github.com/coreos/go-oidc instead.
|
||||||
|
package key
|
153
vendor/github.com/coreos/go-oidc/key/key.go
generated
vendored
Normal file
153
vendor/github.com/coreos/go-oidc/key/key.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPublicKey(jwk jose.JWK) *PublicKey {
|
||||||
|
return &PublicKey{jwk: jwk}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PublicKey struct {
|
||||||
|
jwk jose.JWK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *PublicKey) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(&k.jwk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *PublicKey) UnmarshalJSON(data []byte) error {
|
||||||
|
var jwk jose.JWK
|
||||||
|
if err := json.Unmarshal(data, &jwk); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
k.jwk = jwk
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *PublicKey) ID() string {
|
||||||
|
return k.jwk.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *PublicKey) Verifier() (jose.Verifier, error) {
|
||||||
|
return jose.NewVerifierRSA(k.jwk)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivateKey struct {
|
||||||
|
KeyID string
|
||||||
|
PrivateKey *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *PrivateKey) ID() string {
|
||||||
|
return k.KeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *PrivateKey) Signer() jose.Signer {
|
||||||
|
return jose.NewSignerRSA(k.ID(), *k.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *PrivateKey) JWK() jose.JWK {
|
||||||
|
return jose.JWK{
|
||||||
|
ID: k.KeyID,
|
||||||
|
Type: "RSA",
|
||||||
|
Alg: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
Exponent: k.PrivateKey.PublicKey.E,
|
||||||
|
Modulus: k.PrivateKey.PublicKey.N,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySet interface {
|
||||||
|
ExpiresAt() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type PublicKeySet struct {
|
||||||
|
keys []PublicKey
|
||||||
|
index map[string]*PublicKey
|
||||||
|
expiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPublicKeySet(jwks []jose.JWK, exp time.Time) *PublicKeySet {
|
||||||
|
keys := make([]PublicKey, len(jwks))
|
||||||
|
index := make(map[string]*PublicKey)
|
||||||
|
for i, jwk := range jwks {
|
||||||
|
keys[i] = *NewPublicKey(jwk)
|
||||||
|
index[keys[i].ID()] = &keys[i]
|
||||||
|
}
|
||||||
|
return &PublicKeySet{
|
||||||
|
keys: keys,
|
||||||
|
index: index,
|
||||||
|
expiresAt: exp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicKeySet) ExpiresAt() time.Time {
|
||||||
|
return s.expiresAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicKeySet) Keys() []PublicKey {
|
||||||
|
return s.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicKeySet) Key(id string) *PublicKey {
|
||||||
|
return s.index[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivateKeySet struct {
|
||||||
|
keys []*PrivateKey
|
||||||
|
ActiveKeyID string
|
||||||
|
expiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateKeySet(keys []*PrivateKey, exp time.Time) *PrivateKeySet {
|
||||||
|
return &PrivateKeySet{
|
||||||
|
keys: keys,
|
||||||
|
ActiveKeyID: keys[0].ID(),
|
||||||
|
expiresAt: exp.UTC(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PrivateKeySet) Keys() []*PrivateKey {
|
||||||
|
return s.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PrivateKeySet) ExpiresAt() time.Time {
|
||||||
|
return s.expiresAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PrivateKeySet) Active() *PrivateKey {
|
||||||
|
for i, k := range s.keys {
|
||||||
|
if k.ID() == s.ActiveKeyID {
|
||||||
|
return s.keys[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeneratePrivateKeyFunc func() (*PrivateKey, error)
|
||||||
|
|
||||||
|
func GeneratePrivateKey() (*PrivateKey, error) {
|
||||||
|
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keyID := make([]byte, 20)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, keyID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k := PrivateKey{
|
||||||
|
KeyID: hex.EncodeToString(keyID),
|
||||||
|
PrivateKey: pk,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &k, nil
|
||||||
|
}
|
103
vendor/github.com/coreos/go-oidc/key/key_test.go
generated
vendored
Normal file
103
vendor/github.com/coreos/go-oidc/key/key_test.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrivateRSAKeyJWK(t *testing.T) {
|
||||||
|
n := big.NewInt(int64(17))
|
||||||
|
if n == nil {
|
||||||
|
panic("NewInt returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
k := &PrivateKey{
|
||||||
|
KeyID: "foo",
|
||||||
|
PrivateKey: &rsa.PrivateKey{
|
||||||
|
PublicKey: rsa.PublicKey{N: n, E: 65537},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
want := jose.JWK{
|
||||||
|
ID: "foo",
|
||||||
|
Type: "RSA",
|
||||||
|
Alg: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
Modulus: n,
|
||||||
|
Exponent: 65537,
|
||||||
|
}
|
||||||
|
|
||||||
|
got := k.JWK()
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Fatalf("JWK mismatch: want=%#v got=%#v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeySetKey(t *testing.T) {
|
||||||
|
n := big.NewInt(int64(17))
|
||||||
|
if n == nil {
|
||||||
|
panic("NewInt returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
k := jose.JWK{
|
||||||
|
ID: "foo",
|
||||||
|
Type: "RSA",
|
||||||
|
Alg: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
Modulus: n,
|
||||||
|
Exponent: 65537,
|
||||||
|
}
|
||||||
|
now := time.Now().UTC()
|
||||||
|
ks := NewPublicKeySet([]jose.JWK{k}, now)
|
||||||
|
|
||||||
|
want := &PublicKey{jwk: k}
|
||||||
|
got := ks.Key("foo")
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Errorf("Unexpected response from PublicKeySet.Key: want=%#v got=%#v", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got = ks.Key("bar")
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("Expected nil response from PublicKeySet.Key, got %#v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyMarshalJSON(t *testing.T) {
|
||||||
|
k := jose.JWK{
|
||||||
|
ID: "foo",
|
||||||
|
Type: "RSA",
|
||||||
|
Alg: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
Modulus: big.NewInt(int64(17)),
|
||||||
|
Exponent: 65537,
|
||||||
|
}
|
||||||
|
want := `{"kid":"foo","kty":"RSA","alg":"RS256","use":"sig","e":"AQAB","n":"EQ=="}`
|
||||||
|
pubKey := NewPublicKey(k)
|
||||||
|
gotBytes, err := pubKey.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal public key: %v", err)
|
||||||
|
}
|
||||||
|
got := string(gotBytes)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got != want:\n%s\n%s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneratePrivateKeyIDs(t *testing.T) {
|
||||||
|
key1, err := GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GeneratePrivateKey(): %v", err)
|
||||||
|
}
|
||||||
|
key2, err := GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GeneratePrivateKey(): %v", err)
|
||||||
|
}
|
||||||
|
if key1.KeyID == key2.KeyID {
|
||||||
|
t.Fatalf("expected different keys to have different key IDs")
|
||||||
|
}
|
||||||
|
}
|
99
vendor/github.com/coreos/go-oidc/key/manager.go
generated
vendored
Normal file
99
vendor/github.com/coreos/go-oidc/key/manager.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
"github.com/coreos/pkg/health"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrivateKeyManager interface {
|
||||||
|
ExpiresAt() time.Time
|
||||||
|
Signer() (jose.Signer, error)
|
||||||
|
JWKs() ([]jose.JWK, error)
|
||||||
|
PublicKeys() ([]PublicKey, error)
|
||||||
|
|
||||||
|
WritableKeySetRepo
|
||||||
|
health.Checkable
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateKeyManager() PrivateKeyManager {
|
||||||
|
return &privateKeyManager{
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type privateKeyManager struct {
|
||||||
|
keySet *PrivateKeySet
|
||||||
|
clock clockwork.Clock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *privateKeyManager) ExpiresAt() time.Time {
|
||||||
|
if m.keySet == nil {
|
||||||
|
return m.clock.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.keySet.ExpiresAt()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *privateKeyManager) Signer() (jose.Signer, error) {
|
||||||
|
if err := m.Healthy(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.keySet.Active().Signer(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *privateKeyManager) JWKs() ([]jose.JWK, error) {
|
||||||
|
if err := m.Healthy(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := m.keySet.Keys()
|
||||||
|
jwks := make([]jose.JWK, len(keys))
|
||||||
|
for i, k := range keys {
|
||||||
|
jwks[i] = k.JWK()
|
||||||
|
}
|
||||||
|
return jwks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *privateKeyManager) PublicKeys() ([]PublicKey, error) {
|
||||||
|
jwks, err := m.JWKs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keys := make([]PublicKey, len(jwks))
|
||||||
|
for i, jwk := range jwks {
|
||||||
|
keys[i] = *NewPublicKey(jwk)
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *privateKeyManager) Healthy() error {
|
||||||
|
if m.keySet == nil {
|
||||||
|
return errors.New("private key manager uninitialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.keySet.Keys()) == 0 {
|
||||||
|
return errors.New("private key manager zero keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.keySet.ExpiresAt().Before(m.clock.Now().UTC()) {
|
||||||
|
return errors.New("private key manager keys expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *privateKeyManager) Set(keySet KeySet) error {
|
||||||
|
privKeySet, ok := keySet.(*PrivateKeySet)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unable to cast to PrivateKeySet")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.keySet = privKeySet
|
||||||
|
return nil
|
||||||
|
}
|
225
vendor/github.com/coreos/go-oidc/key/manager_test.go
generated
vendored
Normal file
225
vendor/github.com/coreos/go-oidc/key/manager_test.go
generated
vendored
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
jwk1 jose.JWK
|
||||||
|
jwk2 jose.JWK
|
||||||
|
jwk3 jose.JWK
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
jwk1 = jose.JWK{
|
||||||
|
ID: "1",
|
||||||
|
Type: "RSA",
|
||||||
|
Alg: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
Modulus: big.NewInt(1),
|
||||||
|
Exponent: 65537,
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk2 = jose.JWK{
|
||||||
|
ID: "2",
|
||||||
|
Type: "RSA",
|
||||||
|
Alg: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
Modulus: big.NewInt(2),
|
||||||
|
Exponent: 65537,
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk3 = jose.JWK{
|
||||||
|
ID: "3",
|
||||||
|
Type: "RSA",
|
||||||
|
Alg: "RS256",
|
||||||
|
Use: "sig",
|
||||||
|
Modulus: big.NewInt(3),
|
||||||
|
Exponent: 65537,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatePrivateKeyStatic(t *testing.T, idAndN int) *PrivateKey {
|
||||||
|
n := big.NewInt(int64(idAndN))
|
||||||
|
if n == nil {
|
||||||
|
t.Fatalf("Call to NewInt(%d) failed", idAndN)
|
||||||
|
}
|
||||||
|
|
||||||
|
pk := &rsa.PrivateKey{
|
||||||
|
PublicKey: rsa.PublicKey{N: n, E: 65537},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PrivateKey{
|
||||||
|
KeyID: strconv.Itoa(idAndN),
|
||||||
|
PrivateKey: pk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyManagerJWKsRotate(t *testing.T) {
|
||||||
|
k1 := generatePrivateKeyStatic(t, 1)
|
||||||
|
k2 := generatePrivateKeyStatic(t, 2)
|
||||||
|
k3 := generatePrivateKeyStatic(t, 3)
|
||||||
|
km := NewPrivateKeyManager()
|
||||||
|
err := km.Set(&PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1, k2, k3},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: time.Now().Add(time.Minute),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []jose.JWK{jwk1, jwk2, jwk3}
|
||||||
|
got, err := km.JWKs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Fatalf("JWK mismatch: want=%#v got=%#v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyManagerSigner(t *testing.T) {
|
||||||
|
k := generatePrivateKeyStatic(t, 13)
|
||||||
|
|
||||||
|
km := NewPrivateKeyManager()
|
||||||
|
err := km.Set(&PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k},
|
||||||
|
ActiveKeyID: k.KeyID,
|
||||||
|
expiresAt: time.Now().Add(time.Minute),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := km.Signer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantID := "13"
|
||||||
|
gotID := signer.ID()
|
||||||
|
if wantID != gotID {
|
||||||
|
t.Fatalf("Signer has incorrect ID: want=%s got=%s", wantID, gotID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyManagerHealthyFail(t *testing.T) {
|
||||||
|
keyFixture := generatePrivateKeyStatic(t, 1)
|
||||||
|
tests := []*privateKeyManager{
|
||||||
|
// keySet nil
|
||||||
|
&privateKeyManager{
|
||||||
|
keySet: nil,
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
},
|
||||||
|
// zero keys
|
||||||
|
&privateKeyManager{
|
||||||
|
keySet: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{},
|
||||||
|
expiresAt: time.Now().Add(time.Minute),
|
||||||
|
},
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
},
|
||||||
|
// key set expired
|
||||||
|
&privateKeyManager{
|
||||||
|
keySet: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{keyFixture},
|
||||||
|
expiresAt: time.Now().Add(-1 * time.Minute),
|
||||||
|
},
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
if err := tt.Healthy(); err == nil {
|
||||||
|
t.Errorf("case %d: nil error", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyManagerHealthyFailsOtherMethods(t *testing.T) {
|
||||||
|
km := NewPrivateKeyManager()
|
||||||
|
if _, err := km.JWKs(); err == nil {
|
||||||
|
t.Fatalf("Expected non-nil error")
|
||||||
|
}
|
||||||
|
if _, err := km.Signer(); err == nil {
|
||||||
|
t.Fatalf("Expected non-nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyManagerExpiresAt(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
|
||||||
|
k := generatePrivateKeyStatic(t, 17)
|
||||||
|
km := &privateKeyManager{
|
||||||
|
clock: fc,
|
||||||
|
}
|
||||||
|
|
||||||
|
want := fc.Now().UTC()
|
||||||
|
got := km.ExpiresAt()
|
||||||
|
if want != got {
|
||||||
|
t.Fatalf("Incorrect expiration time: want=%v got=%v", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := km.Set(&PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k},
|
||||||
|
ActiveKeyID: k.KeyID,
|
||||||
|
expiresAt: now.Add(2 * time.Minute),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want = fc.Now().UTC().Add(2 * time.Minute)
|
||||||
|
got = km.ExpiresAt()
|
||||||
|
if want != got {
|
||||||
|
t.Fatalf("Incorrect expiration time: want=%v got=%v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeys(t *testing.T) {
|
||||||
|
km := NewPrivateKeyManager()
|
||||||
|
k1 := generatePrivateKeyStatic(t, 1)
|
||||||
|
k2 := generatePrivateKeyStatic(t, 2)
|
||||||
|
k3 := generatePrivateKeyStatic(t, 3)
|
||||||
|
|
||||||
|
tests := [][]*PrivateKey{
|
||||||
|
[]*PrivateKey{k1},
|
||||||
|
[]*PrivateKey{k1, k2},
|
||||||
|
[]*PrivateKey{k1, k2, k3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
ks := &PrivateKeySet{
|
||||||
|
keys: tt,
|
||||||
|
expiresAt: time.Now().Add(time.Hour),
|
||||||
|
}
|
||||||
|
km.Set(ks)
|
||||||
|
|
||||||
|
jwks, err := km.JWKs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pks := NewPublicKeySet(jwks, time.Now().Add(time.Hour))
|
||||||
|
want := pks.Keys()
|
||||||
|
got, err := km.PublicKeys()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Errorf("case %d: Invalid public keys: want=%v got=%v", i, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
vendor/github.com/coreos/go-oidc/key/repo.go
generated
vendored
Normal file
55
vendor/github.com/coreos/go-oidc/key/repo.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrorNoKeys = errors.New("no keys found")
|
||||||
|
|
||||||
|
type WritableKeySetRepo interface {
|
||||||
|
Set(KeySet) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReadableKeySetRepo interface {
|
||||||
|
Get() (KeySet, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivateKeySetRepo interface {
|
||||||
|
WritableKeySetRepo
|
||||||
|
ReadableKeySetRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateKeySetRepo() PrivateKeySetRepo {
|
||||||
|
return &memPrivateKeySetRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type memPrivateKeySetRepo struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
pks PrivateKeySet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *memPrivateKeySetRepo) Set(ks KeySet) error {
|
||||||
|
pks, ok := ks.(*PrivateKeySet)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unable to cast to PrivateKeySet")
|
||||||
|
} else if pks == nil {
|
||||||
|
return errors.New("nil KeySet")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
r.pks = *pks
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *memPrivateKeySetRepo) Get() (KeySet, error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
if r.pks.keys == nil {
|
||||||
|
return nil, ErrorNoKeys
|
||||||
|
}
|
||||||
|
return KeySet(&r.pks), nil
|
||||||
|
}
|
159
vendor/github.com/coreos/go-oidc/key/rotate.go
generated
vendored
Normal file
159
vendor/github.com/coreos/go-oidc/key/rotate.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ptime "github.com/coreos/pkg/timeutil"
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorPrivateKeysExpired = errors.New("private keys have expired")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPrivateKeyRotator(repo PrivateKeySetRepo, ttl time.Duration) *PrivateKeyRotator {
|
||||||
|
return &PrivateKeyRotator{
|
||||||
|
repo: repo,
|
||||||
|
ttl: ttl,
|
||||||
|
|
||||||
|
keep: 2,
|
||||||
|
generateKey: GeneratePrivateKey,
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivateKeyRotator struct {
|
||||||
|
repo PrivateKeySetRepo
|
||||||
|
generateKey GeneratePrivateKeyFunc
|
||||||
|
clock clockwork.Clock
|
||||||
|
keep int
|
||||||
|
ttl time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PrivateKeyRotator) expiresAt() time.Time {
|
||||||
|
return r.clock.Now().UTC().Add(r.ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PrivateKeyRotator) Healthy() error {
|
||||||
|
pks, err := r.privateKeySet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.clock.Now().After(pks.ExpiresAt()) {
|
||||||
|
return ErrorPrivateKeysExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PrivateKeyRotator) privateKeySet() (*PrivateKeySet, error) {
|
||||||
|
ks, err := r.repo.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pks, ok := ks.(*PrivateKeySet)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("unable to cast to PrivateKeySet")
|
||||||
|
}
|
||||||
|
return pks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PrivateKeyRotator) nextRotation() (time.Duration, error) {
|
||||||
|
pks, err := r.privateKeySet()
|
||||||
|
if err == ErrorNoKeys {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := r.clock.Now()
|
||||||
|
|
||||||
|
// Ideally, we want to rotate after half the TTL has elapsed.
|
||||||
|
idealRotationTime := pks.ExpiresAt().Add(-r.ttl / 2)
|
||||||
|
|
||||||
|
// If we are past the ideal rotation time, rotate immediatly.
|
||||||
|
return max(0, idealRotationTime.Sub(now)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b time.Duration) time.Duration {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PrivateKeyRotator) Run() chan struct{} {
|
||||||
|
attempt := func() {
|
||||||
|
k, err := r.generateKey()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("go-oidc: failed generating signing key: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := r.expiresAt()
|
||||||
|
if err := rotatePrivateKeys(r.repo, k, r.keep, exp); err != nil {
|
||||||
|
log.Printf("go-oidc: key rotation failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var nextRotation time.Duration
|
||||||
|
var sleep time.Duration
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
if nextRotation, err = r.nextRotation(); err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sleep = ptime.ExpBackoff(sleep, time.Minute)
|
||||||
|
log.Printf("go-oidc: error getting nextRotation, retrying in %v: %v", sleep, err)
|
||||||
|
time.Sleep(sleep)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-r.clock.After(nextRotation):
|
||||||
|
attempt()
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return stop
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotatePrivateKeys(repo PrivateKeySetRepo, k *PrivateKey, keep int, exp time.Time) error {
|
||||||
|
ks, err := repo.Get()
|
||||||
|
if err != nil && err != ErrorNoKeys {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys []*PrivateKey
|
||||||
|
if ks != nil {
|
||||||
|
pks, ok := ks.(*PrivateKeySet)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unable to cast to PrivateKeySet")
|
||||||
|
}
|
||||||
|
keys = pks.Keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = append([]*PrivateKey{k}, keys...)
|
||||||
|
if l := len(keys); l > keep {
|
||||||
|
keys = keys[0:keep]
|
||||||
|
}
|
||||||
|
|
||||||
|
nks := PrivateKeySet{
|
||||||
|
keys: keys,
|
||||||
|
ActiveKeyID: k.ID(),
|
||||||
|
expiresAt: exp,
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.Set(KeySet(&nks))
|
||||||
|
}
|
311
vendor/github.com/coreos/go-oidc/key/rotate_test.go
generated
vendored
Normal file
311
vendor/github.com/coreos/go-oidc/key/rotate_test.go
generated
vendored
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generatePrivateKeySerialFunc(t *testing.T) GeneratePrivateKeyFunc {
|
||||||
|
var n int
|
||||||
|
return func() (*PrivateKey, error) {
|
||||||
|
n++
|
||||||
|
return generatePrivateKeyStatic(t, n), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRotate(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
k1 := generatePrivateKeyStatic(t, 1)
|
||||||
|
k2 := generatePrivateKeyStatic(t, 2)
|
||||||
|
k3 := generatePrivateKeyStatic(t, 3)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
start *PrivateKeySet
|
||||||
|
key *PrivateKey
|
||||||
|
keep int
|
||||||
|
exp time.Time
|
||||||
|
want *PrivateKeySet
|
||||||
|
}{
|
||||||
|
// start with nil keys
|
||||||
|
{
|
||||||
|
start: nil,
|
||||||
|
key: k1,
|
||||||
|
keep: 2,
|
||||||
|
exp: now.Add(time.Second),
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// start with zero keys
|
||||||
|
{
|
||||||
|
start: &PrivateKeySet{},
|
||||||
|
key: k1,
|
||||||
|
keep: 2,
|
||||||
|
exp: now.Add(time.Second),
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// add second key
|
||||||
|
{
|
||||||
|
start: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now,
|
||||||
|
},
|
||||||
|
key: k2,
|
||||||
|
keep: 2,
|
||||||
|
exp: now.Add(time.Second),
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// rotate in third key
|
||||||
|
{
|
||||||
|
start: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now,
|
||||||
|
},
|
||||||
|
key: k3,
|
||||||
|
keep: 2,
|
||||||
|
exp: now.Add(time.Second),
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k3, k2},
|
||||||
|
ActiveKeyID: k3.KeyID,
|
||||||
|
expiresAt: now.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
repo := NewPrivateKeySetRepo()
|
||||||
|
if tt.start != nil {
|
||||||
|
err := repo.Set(tt.start)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rotatePrivateKeys(repo, tt.key, tt.keep, tt.exp)
|
||||||
|
got, err := repo.Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.want, got) {
|
||||||
|
t.Errorf("case %d: unexpected result: want=%#v got=%#v", i, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyRotatorRun(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
|
||||||
|
k1 := generatePrivateKeyStatic(t, 1)
|
||||||
|
k2 := generatePrivateKeyStatic(t, 2)
|
||||||
|
k3 := generatePrivateKeyStatic(t, 3)
|
||||||
|
k4 := generatePrivateKeyStatic(t, 4)
|
||||||
|
|
||||||
|
kRepo := NewPrivateKeySetRepo()
|
||||||
|
krot := NewPrivateKeyRotator(kRepo, 4*time.Second)
|
||||||
|
krot.clock = fc
|
||||||
|
krot.generateKey = generatePrivateKeySerialFunc(t)
|
||||||
|
|
||||||
|
steps := []*PrivateKeySet{
|
||||||
|
&PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now.Add(4 * time.Second),
|
||||||
|
},
|
||||||
|
&PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(6 * time.Second),
|
||||||
|
},
|
||||||
|
&PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k3, k2},
|
||||||
|
ActiveKeyID: k3.KeyID,
|
||||||
|
expiresAt: now.Add(8 * time.Second),
|
||||||
|
},
|
||||||
|
&PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k4, k3},
|
||||||
|
ActiveKeyID: k4.KeyID,
|
||||||
|
expiresAt: now.Add(10 * time.Second),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
stop := krot.Run()
|
||||||
|
defer close(stop)
|
||||||
|
|
||||||
|
for i, st := range steps {
|
||||||
|
// wait for the rotater to get sleepy
|
||||||
|
fc.BlockUntil(1)
|
||||||
|
|
||||||
|
got, err := kRepo.Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("step %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(st, got) {
|
||||||
|
t.Fatalf("step %d: unexpected state: want=%#v got=%#v", i, st, got)
|
||||||
|
}
|
||||||
|
fc.Advance(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyRotatorExpiresAt(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
krot := &PrivateKeyRotator{
|
||||||
|
clock: fc,
|
||||||
|
ttl: time.Minute,
|
||||||
|
}
|
||||||
|
got := krot.expiresAt()
|
||||||
|
want := fc.Now().UTC().Add(time.Minute)
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Errorf("Incorrect expiration time: want=%v got=%v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextRotation(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
expiresAt time.Time
|
||||||
|
ttl time.Duration
|
||||||
|
numKeys int
|
||||||
|
expected time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// closest to prod
|
||||||
|
expiresAt: now.Add(time.Hour * 24),
|
||||||
|
ttl: time.Hour * 24,
|
||||||
|
numKeys: 2,
|
||||||
|
expected: time.Hour * 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expiresAt: now.Add(time.Hour * 2),
|
||||||
|
ttl: time.Hour * 4,
|
||||||
|
numKeys: 2,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// No keys.
|
||||||
|
expiresAt: now.Add(time.Hour * 2),
|
||||||
|
ttl: time.Hour * 4,
|
||||||
|
numKeys: 0,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nil keyset.
|
||||||
|
expiresAt: now.Add(time.Hour * 2),
|
||||||
|
ttl: time.Hour * 4,
|
||||||
|
numKeys: -1,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// KeySet expired.
|
||||||
|
expiresAt: now.Add(time.Hour * -2),
|
||||||
|
ttl: time.Hour * 4,
|
||||||
|
numKeys: 2,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Expiry past now + TTL
|
||||||
|
expiresAt: now.Add(time.Hour * 5),
|
||||||
|
ttl: time.Hour * 4,
|
||||||
|
numKeys: 2,
|
||||||
|
expected: 3 * time.Hour,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
kRepo := NewPrivateKeySetRepo()
|
||||||
|
krot := NewPrivateKeyRotator(kRepo, tt.ttl)
|
||||||
|
krot.clock = fc
|
||||||
|
pks := &PrivateKeySet{
|
||||||
|
expiresAt: tt.expiresAt,
|
||||||
|
}
|
||||||
|
if tt.numKeys != -1 {
|
||||||
|
for n := 0; n < tt.numKeys; n++ {
|
||||||
|
pks.keys = append(pks.keys, generatePrivateKeyStatic(t, n))
|
||||||
|
}
|
||||||
|
err := kRepo.Set(pks)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
actual, err := krot.nextRotation()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: error calling shouldRotate(): %v", i, err)
|
||||||
|
}
|
||||||
|
if actual != tt.expected {
|
||||||
|
t.Errorf("case %d: actual == %v, want %v", i, actual, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthy(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
expiresAt time.Time
|
||||||
|
numKeys int
|
||||||
|
expected error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
expiresAt: now.Add(time.Hour),
|
||||||
|
numKeys: 2,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expiresAt: now.Add(time.Hour),
|
||||||
|
numKeys: -1,
|
||||||
|
expected: ErrorNoKeys,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expiresAt: now.Add(time.Hour),
|
||||||
|
numKeys: 0,
|
||||||
|
expected: ErrorNoKeys,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expiresAt: now.Add(-time.Hour),
|
||||||
|
numKeys: 2,
|
||||||
|
expected: ErrorPrivateKeysExpired,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
kRepo := NewPrivateKeySetRepo()
|
||||||
|
krot := NewPrivateKeyRotator(kRepo, time.Hour)
|
||||||
|
krot.clock = fc
|
||||||
|
pks := &PrivateKeySet{
|
||||||
|
expiresAt: tt.expiresAt,
|
||||||
|
}
|
||||||
|
if tt.numKeys != -1 {
|
||||||
|
for n := 0; n < tt.numKeys; n++ {
|
||||||
|
pks.keys = append(pks.keys, generatePrivateKeyStatic(t, n))
|
||||||
|
}
|
||||||
|
err := kRepo.Set(pks)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if err := krot.Healthy(); err != tt.expected {
|
||||||
|
t.Errorf("case %d: got==%q, want==%q", i, err, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
vendor/github.com/coreos/go-oidc/key/sync.go
generated
vendored
Normal file
91
vendor/github.com/coreos/go-oidc/key/sync.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewKeySetSyncer(r ReadableKeySetRepo, w WritableKeySetRepo) *KeySetSyncer {
|
||||||
|
return &KeySetSyncer{
|
||||||
|
readable: r,
|
||||||
|
writable: w,
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySetSyncer struct {
|
||||||
|
readable ReadableKeySetRepo
|
||||||
|
writable WritableKeySetRepo
|
||||||
|
clock clockwork.Clock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KeySetSyncer) Run() chan struct{} {
|
||||||
|
stop := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
var failing bool
|
||||||
|
var next time.Duration
|
||||||
|
for {
|
||||||
|
exp, err := syncKeySet(s.readable, s.writable, s.clock)
|
||||||
|
if err != nil || exp == 0 {
|
||||||
|
if !failing {
|
||||||
|
failing = true
|
||||||
|
next = time.Second
|
||||||
|
} else {
|
||||||
|
next = timeutil.ExpBackoff(next, time.Minute)
|
||||||
|
}
|
||||||
|
if exp == 0 {
|
||||||
|
log.Printf("Synced to already expired key set, retrying in %v: %v", next, err)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed syncing key set, retrying in %v: %v", next, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failing = false
|
||||||
|
next = exp / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.clock.After(next):
|
||||||
|
continue
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return stop
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sync(r ReadableKeySetRepo, w WritableKeySetRepo) (time.Duration, error) {
|
||||||
|
return syncKeySet(r, w, clockwork.NewRealClock())
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncKeySet copies the keyset from r to the KeySet at w and returns the duration in which the KeySet will expire.
|
||||||
|
// If keyset has already expired, returns a zero duration.
|
||||||
|
func syncKeySet(r ReadableKeySetRepo, w WritableKeySetRepo, clock clockwork.Clock) (exp time.Duration, err error) {
|
||||||
|
var ks KeySet
|
||||||
|
ks, err = r.Get()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ks == nil {
|
||||||
|
err = errors.New("no source KeySet")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = w.Set(ks); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
now := clock.Now()
|
||||||
|
if ks.ExpiresAt().After(now) {
|
||||||
|
exp = ks.ExpiresAt().Sub(now)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
214
vendor/github.com/coreos/go-oidc/key/sync_test.go
generated
vendored
Normal file
214
vendor/github.com/coreos/go-oidc/key/sync_test.go
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
)
|
||||||
|
|
||||||
|
type staticReadableKeySetRepo struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
ks KeySet
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *staticReadableKeySetRepo) Get() (KeySet, error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
return r.ks, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *staticReadableKeySetRepo) set(ks KeySet, err error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.ks, r.err = ks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeySyncerSync(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
|
||||||
|
k1 := generatePrivateKeyStatic(t, 1)
|
||||||
|
k2 := generatePrivateKeyStatic(t, 2)
|
||||||
|
k3 := generatePrivateKeyStatic(t, 3)
|
||||||
|
|
||||||
|
steps := []struct {
|
||||||
|
fromKS KeySet
|
||||||
|
fromErr error
|
||||||
|
advance time.Duration
|
||||||
|
want *PrivateKeySet
|
||||||
|
}{
|
||||||
|
// on startup, first sync should trigger within a second
|
||||||
|
{
|
||||||
|
fromKS: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now.Add(10 * time.Second),
|
||||||
|
},
|
||||||
|
advance: time.Second,
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now.Add(10 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// advance halfway into TTL, triggering sync
|
||||||
|
{
|
||||||
|
fromKS: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(15 * time.Second),
|
||||||
|
},
|
||||||
|
advance: 5 * time.Second,
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(15 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// advance halfway into TTL, triggering sync that fails
|
||||||
|
{
|
||||||
|
fromErr: errors.New("fail!"),
|
||||||
|
advance: 10 * time.Second,
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(15 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// sync retries quickly, and succeeds with fixed data
|
||||||
|
{
|
||||||
|
fromKS: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k3, k2, k1},
|
||||||
|
ActiveKeyID: k3.KeyID,
|
||||||
|
expiresAt: now.Add(25 * time.Second),
|
||||||
|
},
|
||||||
|
advance: 3 * time.Second,
|
||||||
|
want: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k3, k2, k1},
|
||||||
|
ActiveKeyID: k3.KeyID,
|
||||||
|
expiresAt: now.Add(25 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
from := &staticReadableKeySetRepo{}
|
||||||
|
to := NewPrivateKeySetRepo()
|
||||||
|
|
||||||
|
syncer := NewKeySetSyncer(from, to)
|
||||||
|
syncer.clock = fc
|
||||||
|
stop := syncer.Run()
|
||||||
|
defer close(stop)
|
||||||
|
|
||||||
|
for i, st := range steps {
|
||||||
|
from.set(st.fromKS, st.fromErr)
|
||||||
|
|
||||||
|
fc.Advance(st.advance)
|
||||||
|
fc.BlockUntil(1)
|
||||||
|
|
||||||
|
ks, err := to.Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("step %d: unable to get keys: %v", i, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(st.want, ks) {
|
||||||
|
t.Fatalf("step %d: incorrect state: want=%#v got=%#v", i, st.want, ks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSync(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
|
||||||
|
k1 := generatePrivateKeyStatic(t, 1)
|
||||||
|
k2 := generatePrivateKeyStatic(t, 2)
|
||||||
|
k3 := generatePrivateKeyStatic(t, 3)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
keySet *PrivateKeySet
|
||||||
|
want time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
keySet: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now.Add(time.Minute),
|
||||||
|
},
|
||||||
|
want: time.Minute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keySet: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(time.Minute),
|
||||||
|
},
|
||||||
|
want: time.Minute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keySet: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k3, k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(time.Minute),
|
||||||
|
},
|
||||||
|
want: time.Minute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keySet: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k2, k1},
|
||||||
|
ActiveKeyID: k2.KeyID,
|
||||||
|
expiresAt: now.Add(time.Hour),
|
||||||
|
},
|
||||||
|
want: time.Hour,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keySet: &PrivateKeySet{
|
||||||
|
keys: []*PrivateKey{k1},
|
||||||
|
ActiveKeyID: k1.KeyID,
|
||||||
|
expiresAt: now.Add(-time.Hour),
|
||||||
|
},
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
from := NewPrivateKeySetRepo()
|
||||||
|
to := NewPrivateKeySetRepo()
|
||||||
|
|
||||||
|
err := from.Set(tt.keySet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
exp, err := syncKeySet(from, to, fc)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.want != exp {
|
||||||
|
t.Errorf("case %d: want=%v got=%v", i, tt.want, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncFail(t *testing.T) {
|
||||||
|
tests := []error{
|
||||||
|
nil,
|
||||||
|
errors.New("fail!"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
from := &staticReadableKeySetRepo{ks: nil, err: tt}
|
||||||
|
to := NewPrivateKeySetRepo()
|
||||||
|
|
||||||
|
if _, err := syncKeySet(from, to, clockwork.NewFakeClock()); err == nil {
|
||||||
|
t.Errorf("case %d: expected non-nil error", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
vendor/github.com/coreos/go-oidc/oauth2/doc.go
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/oauth2/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package oauth2 is DEPRECATED. Use golang.org/x/oauth instead.
|
||||||
|
package oauth2
|
29
vendor/github.com/coreos/go-oidc/oauth2/error.go
generated
vendored
Normal file
29
vendor/github.com/coreos/go-oidc/oauth2/error.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorAccessDenied = "access_denied"
|
||||||
|
ErrorInvalidClient = "invalid_client"
|
||||||
|
ErrorInvalidGrant = "invalid_grant"
|
||||||
|
ErrorInvalidRequest = "invalid_request"
|
||||||
|
ErrorServerError = "server_error"
|
||||||
|
ErrorUnauthorizedClient = "unauthorized_client"
|
||||||
|
ErrorUnsupportedGrantType = "unsupported_grant_type"
|
||||||
|
ErrorUnsupportedResponseType = "unsupported_response_type"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Type string `json:"error"`
|
||||||
|
Description string `json:"error_description,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
if e.Description != "" {
|
||||||
|
return e.Type + ": " + e.Description
|
||||||
|
}
|
||||||
|
return e.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(typ string) *Error {
|
||||||
|
return &Error{Type: typ}
|
||||||
|
}
|
416
vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
Normal file
416
vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
phttp "github.com/coreos/go-oidc/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseTypesEqual compares two response_type values. If either
|
||||||
|
// contains a space, it is treated as an unordered list. For example,
|
||||||
|
// comparing "code id_token" and "id_token code" would evaluate to true.
|
||||||
|
func ResponseTypesEqual(r1, r2 string) bool {
|
||||||
|
if !strings.Contains(r1, " ") || !strings.Contains(r2, " ") {
|
||||||
|
// fast route, no split needed
|
||||||
|
return r1 == r2
|
||||||
|
}
|
||||||
|
|
||||||
|
// split, sort, and compare
|
||||||
|
r1Fields := strings.Fields(r1)
|
||||||
|
r2Fields := strings.Fields(r2)
|
||||||
|
if len(r1Fields) != len(r2Fields) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sort.Strings(r1Fields)
|
||||||
|
sort.Strings(r2Fields)
|
||||||
|
for i, r1Field := range r1Fields {
|
||||||
|
if r1Field != r2Fields[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OAuth2.0 response types registered by OIDC.
|
||||||
|
//
|
||||||
|
// See: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#RegistryContents
|
||||||
|
ResponseTypeCode = "code"
|
||||||
|
ResponseTypeCodeIDToken = "code id_token"
|
||||||
|
ResponseTypeCodeIDTokenToken = "code id_token token"
|
||||||
|
ResponseTypeIDToken = "id_token"
|
||||||
|
ResponseTypeIDTokenToken = "id_token token"
|
||||||
|
ResponseTypeToken = "token"
|
||||||
|
ResponseTypeNone = "none"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GrantTypeAuthCode = "authorization_code"
|
||||||
|
GrantTypeClientCreds = "client_credentials"
|
||||||
|
GrantTypeUserCreds = "password"
|
||||||
|
GrantTypeImplicit = "implicit"
|
||||||
|
GrantTypeRefreshToken = "refresh_token"
|
||||||
|
|
||||||
|
AuthMethodClientSecretPost = "client_secret_post"
|
||||||
|
AuthMethodClientSecretBasic = "client_secret_basic"
|
||||||
|
AuthMethodClientSecretJWT = "client_secret_jwt"
|
||||||
|
AuthMethodPrivateKeyJWT = "private_key_jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Credentials ClientCredentials
|
||||||
|
Scope []string
|
||||||
|
RedirectURL string
|
||||||
|
AuthURL string
|
||||||
|
TokenURL string
|
||||||
|
|
||||||
|
// Must be one of the AuthMethodXXX methods above. Right now, only
|
||||||
|
// AuthMethodClientSecretPost and AuthMethodClientSecretBasic are supported.
|
||||||
|
AuthMethod string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
hc phttp.Client
|
||||||
|
creds ClientCredentials
|
||||||
|
scope []string
|
||||||
|
authURL *url.URL
|
||||||
|
redirectURL *url.URL
|
||||||
|
tokenURL *url.URL
|
||||||
|
authMethod string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientCredentials struct {
|
||||||
|
ID string
|
||||||
|
Secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(hc phttp.Client, cfg Config) (c *Client, err error) {
|
||||||
|
if len(cfg.Credentials.ID) == 0 {
|
||||||
|
err = errors.New("missing client id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Credentials.Secret) == 0 {
|
||||||
|
err = errors.New("missing client secret")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.AuthMethod == "" {
|
||||||
|
cfg.AuthMethod = AuthMethodClientSecretBasic
|
||||||
|
} else if cfg.AuthMethod != AuthMethodClientSecretPost && cfg.AuthMethod != AuthMethodClientSecretBasic {
|
||||||
|
err = fmt.Errorf("auth method %q is not supported", cfg.AuthMethod)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
au, err := phttp.ParseNonEmptyURL(cfg.AuthURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tu, err := phttp.ParseNonEmptyURL(cfg.TokenURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow empty redirect URL in the case where the client
|
||||||
|
// only needs to verify a given token.
|
||||||
|
ru, err := url.Parse(cfg.RedirectURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &Client{
|
||||||
|
creds: cfg.Credentials,
|
||||||
|
scope: cfg.Scope,
|
||||||
|
redirectURL: ru,
|
||||||
|
authURL: au,
|
||||||
|
tokenURL: tu,
|
||||||
|
hc: hc,
|
||||||
|
authMethod: cfg.AuthMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the embedded HTTP client
|
||||||
|
func (c *Client) HttpClient() phttp.Client {
|
||||||
|
return c.hc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the url for initial redirect to oauth provider.
|
||||||
|
func (c *Client) AuthCodeURL(state, accessType, prompt string) string {
|
||||||
|
v := c.commonURLValues()
|
||||||
|
v.Set("state", state)
|
||||||
|
if strings.ToLower(accessType) == "offline" {
|
||||||
|
v.Set("access_type", "offline")
|
||||||
|
}
|
||||||
|
|
||||||
|
if prompt != "" {
|
||||||
|
v.Set("prompt", prompt)
|
||||||
|
}
|
||||||
|
v.Set("response_type", "code")
|
||||||
|
|
||||||
|
q := v.Encode()
|
||||||
|
u := *c.authURL
|
||||||
|
if u.RawQuery == "" {
|
||||||
|
u.RawQuery = q
|
||||||
|
} else {
|
||||||
|
u.RawQuery += "&" + q
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) commonURLValues() url.Values {
|
||||||
|
return url.Values{
|
||||||
|
"redirect_uri": {c.redirectURL.String()},
|
||||||
|
"scope": {strings.Join(c.scope, " ")},
|
||||||
|
"client_id": {c.creds.ID},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newAuthenticatedRequest(urlToken string, values url.Values) (*http.Request, error) {
|
||||||
|
var req *http.Request
|
||||||
|
var err error
|
||||||
|
switch c.authMethod {
|
||||||
|
case AuthMethodClientSecretPost:
|
||||||
|
values.Set("client_secret", c.creds.Secret)
|
||||||
|
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case AuthMethodClientSecretBasic:
|
||||||
|
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encodedID := url.QueryEscape(c.creds.ID)
|
||||||
|
encodedSecret := url.QueryEscape(c.creds.Secret)
|
||||||
|
req.SetBasicAuth(encodedID, encodedSecret)
|
||||||
|
default:
|
||||||
|
panic("misconfigured client: auth method not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
return req, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientCredsToken posts the client id and secret to obtain a token scoped to the OAuth2 client via the "client_credentials" grant type.
|
||||||
|
// May not be supported by all OAuth2 servers.
|
||||||
|
func (c *Client) ClientCredsToken(scope []string) (result TokenResponse, err error) {
|
||||||
|
v := url.Values{
|
||||||
|
"scope": {strings.Join(scope, " ")},
|
||||||
|
"grant_type": {GrantTypeClientCreds},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return parseTokenResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserCredsToken posts the username and password to obtain a token scoped to the OAuth2 client via the "password" grant_type
|
||||||
|
// May not be supported by all OAuth2 servers.
|
||||||
|
func (c *Client) UserCredsToken(username, password string) (result TokenResponse, err error) {
|
||||||
|
v := url.Values{
|
||||||
|
"scope": {strings.Join(c.scope, " ")},
|
||||||
|
"grant_type": {GrantTypeUserCreds},
|
||||||
|
"username": {username},
|
||||||
|
"password": {password},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return parseTokenResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestToken requests a token from the Token Endpoint with the specified grantType.
|
||||||
|
// If 'grantType' == GrantTypeAuthCode, then 'value' should be the authorization code.
|
||||||
|
// If 'grantType' == GrantTypeRefreshToken, then 'value' should be the refresh token.
|
||||||
|
func (c *Client) RequestToken(grantType, value string) (result TokenResponse, err error) {
|
||||||
|
v := c.commonURLValues()
|
||||||
|
|
||||||
|
v.Set("grant_type", grantType)
|
||||||
|
v.Set("client_secret", c.creds.Secret)
|
||||||
|
switch grantType {
|
||||||
|
case GrantTypeAuthCode:
|
||||||
|
v.Set("code", value)
|
||||||
|
case GrantTypeRefreshToken:
|
||||||
|
v.Set("refresh_token", value)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported grant_type: %v", grantType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return parseTokenResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTokenResponse(resp *http.Response) (result TokenResponse, err error) {
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
badStatusCode := resp.StatusCode < 200 || resp.StatusCode > 299
|
||||||
|
|
||||||
|
contentType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result = TokenResponse{
|
||||||
|
RawBody: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
newError := func(typ, desc, state string) error {
|
||||||
|
if typ == "" {
|
||||||
|
return fmt.Errorf("unrecognized error %s", body)
|
||||||
|
}
|
||||||
|
return &Error{typ, desc, state}
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentType == "application/x-www-form-urlencoded" || contentType == "text/plain" {
|
||||||
|
var vals url.Values
|
||||||
|
vals, err = url.ParseQuery(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if error := vals.Get("error"); error != "" || badStatusCode {
|
||||||
|
err = newError(error, vals.Get("error_description"), vals.Get("state"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e := vals.Get("expires_in")
|
||||||
|
if e == "" {
|
||||||
|
e = vals.Get("expires")
|
||||||
|
}
|
||||||
|
if e != "" {
|
||||||
|
result.Expires, err = strconv.Atoi(e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.AccessToken = vals.Get("access_token")
|
||||||
|
result.TokenType = vals.Get("token_type")
|
||||||
|
result.IDToken = vals.Get("id_token")
|
||||||
|
result.RefreshToken = vals.Get("refresh_token")
|
||||||
|
result.Scope = vals.Get("scope")
|
||||||
|
} else {
|
||||||
|
var r struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
State string `json:"state"`
|
||||||
|
ExpiresIn json.Number `json:"expires_in"` // Azure AD returns string
|
||||||
|
Expires int `json:"expires"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
Desc string `json:"error_description"`
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(body, &r); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Error != "" || badStatusCode {
|
||||||
|
err = newError(r.Error, r.Desc, r.State)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result.AccessToken = r.AccessToken
|
||||||
|
result.TokenType = r.TokenType
|
||||||
|
result.IDToken = r.IDToken
|
||||||
|
result.RefreshToken = r.RefreshToken
|
||||||
|
result.Scope = r.Scope
|
||||||
|
if expiresIn, err := r.ExpiresIn.Int64(); err != nil {
|
||||||
|
result.Expires = r.Expires
|
||||||
|
} else {
|
||||||
|
result.Expires = int(expiresIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenResponse struct {
|
||||||
|
AccessToken string
|
||||||
|
TokenType string
|
||||||
|
Expires int
|
||||||
|
IDToken string
|
||||||
|
RefreshToken string // OPTIONAL.
|
||||||
|
Scope string // OPTIONAL, if identical to the scope requested by the client, otherwise, REQUIRED.
|
||||||
|
RawBody []byte // In case callers need some other non-standard info from the token response
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthCodeRequest struct {
|
||||||
|
ResponseType string
|
||||||
|
ClientID string
|
||||||
|
RedirectURL *url.URL
|
||||||
|
Scope []string
|
||||||
|
State string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseAuthCodeRequest(q url.Values) (AuthCodeRequest, error) {
|
||||||
|
acr := AuthCodeRequest{
|
||||||
|
ResponseType: q.Get("response_type"),
|
||||||
|
ClientID: q.Get("client_id"),
|
||||||
|
State: q.Get("state"),
|
||||||
|
Scope: make([]string, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
qs := strings.TrimSpace(q.Get("scope"))
|
||||||
|
if qs != "" {
|
||||||
|
acr.Scope = strings.Split(qs, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := func() error {
|
||||||
|
if acr.ClientID == "" {
|
||||||
|
return NewError(ErrorInvalidRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectURL := q.Get("redirect_uri")
|
||||||
|
if redirectURL != "" {
|
||||||
|
ru, err := url.Parse(redirectURL)
|
||||||
|
if err != nil {
|
||||||
|
return NewError(ErrorInvalidRequest)
|
||||||
|
}
|
||||||
|
acr.RedirectURL = ru
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
return acr, err
|
||||||
|
}
|
518
vendor/github.com/coreos/go-oidc/oauth2/oauth2_test.go
generated
vendored
Normal file
518
vendor/github.com/coreos/go-oidc/oauth2/oauth2_test.go
generated
vendored
Normal file
|
@ -0,0 +1,518 @@
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
phttp "github.com/coreos/go-oidc/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResponseTypesEqual(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
r1, r2 string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"code", "code", true},
|
||||||
|
{"id_token", "code", false},
|
||||||
|
{"code token", "token code", true},
|
||||||
|
{"code token", "code token", true},
|
||||||
|
{"foo", "bar code", false},
|
||||||
|
{"code token id_token", "token id_token code", true},
|
||||||
|
{"code token id_token", "token id_token code zoo", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got1 := ResponseTypesEqual(tt.r1, tt.r2)
|
||||||
|
got2 := ResponseTypesEqual(tt.r2, tt.r1)
|
||||||
|
if got1 != got2 {
|
||||||
|
t.Errorf("case %d: got different answers with different orders", i)
|
||||||
|
}
|
||||||
|
if tt.want != got1 {
|
||||||
|
t.Errorf("case %d: want=%t, got=%t", i, tt.want, got1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAuthCodeRequest(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
query url.Values
|
||||||
|
wantACR AuthCodeRequest
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
// no redirect_uri
|
||||||
|
{
|
||||||
|
query: url.Values{
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"scope": []string{"foo bar baz"},
|
||||||
|
"client_id": []string{"XXX"},
|
||||||
|
"state": []string{"pants"},
|
||||||
|
},
|
||||||
|
wantACR: AuthCodeRequest{
|
||||||
|
ResponseType: "code",
|
||||||
|
ClientID: "XXX",
|
||||||
|
Scope: []string{"foo", "bar", "baz"},
|
||||||
|
State: "pants",
|
||||||
|
RedirectURL: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// with redirect_uri
|
||||||
|
{
|
||||||
|
query: url.Values{
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"redirect_uri": []string{"https://127.0.0.1:5555/callback?foo=bar"},
|
||||||
|
"scope": []string{"foo bar baz"},
|
||||||
|
"client_id": []string{"XXX"},
|
||||||
|
"state": []string{"pants"},
|
||||||
|
},
|
||||||
|
wantACR: AuthCodeRequest{
|
||||||
|
ResponseType: "code",
|
||||||
|
ClientID: "XXX",
|
||||||
|
Scope: []string{"foo", "bar", "baz"},
|
||||||
|
State: "pants",
|
||||||
|
RedirectURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "127.0.0.1:5555",
|
||||||
|
Path: "/callback",
|
||||||
|
RawQuery: "foo=bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// unsupported response_type doesn't trigger error
|
||||||
|
{
|
||||||
|
query: url.Values{
|
||||||
|
"response_type": []string{"token"},
|
||||||
|
"redirect_uri": []string{"https://127.0.0.1:5555/callback?foo=bar"},
|
||||||
|
"scope": []string{"foo bar baz"},
|
||||||
|
"client_id": []string{"XXX"},
|
||||||
|
"state": []string{"pants"},
|
||||||
|
},
|
||||||
|
wantACR: AuthCodeRequest{
|
||||||
|
ResponseType: "token",
|
||||||
|
ClientID: "XXX",
|
||||||
|
Scope: []string{"foo", "bar", "baz"},
|
||||||
|
State: "pants",
|
||||||
|
RedirectURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "127.0.0.1:5555",
|
||||||
|
Path: "/callback",
|
||||||
|
RawQuery: "foo=bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// unparseable redirect_uri
|
||||||
|
{
|
||||||
|
query: url.Values{
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"redirect_uri": []string{":"},
|
||||||
|
"scope": []string{"foo bar baz"},
|
||||||
|
"client_id": []string{"XXX"},
|
||||||
|
"state": []string{"pants"},
|
||||||
|
},
|
||||||
|
wantACR: AuthCodeRequest{
|
||||||
|
ResponseType: "code",
|
||||||
|
ClientID: "XXX",
|
||||||
|
Scope: []string{"foo", "bar", "baz"},
|
||||||
|
State: "pants",
|
||||||
|
},
|
||||||
|
wantErr: NewError(ErrorInvalidRequest),
|
||||||
|
},
|
||||||
|
|
||||||
|
// no client_id, redirect_uri not parsed
|
||||||
|
{
|
||||||
|
query: url.Values{
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"redirect_uri": []string{"https://127.0.0.1:5555/callback?foo=bar"},
|
||||||
|
"scope": []string{"foo bar baz"},
|
||||||
|
"client_id": []string{},
|
||||||
|
"state": []string{"pants"},
|
||||||
|
},
|
||||||
|
wantACR: AuthCodeRequest{
|
||||||
|
ResponseType: "code",
|
||||||
|
ClientID: "",
|
||||||
|
Scope: []string{"foo", "bar", "baz"},
|
||||||
|
State: "pants",
|
||||||
|
RedirectURL: nil,
|
||||||
|
},
|
||||||
|
wantErr: NewError(ErrorInvalidRequest),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got, err := ParseAuthCodeRequest(tt.query)
|
||||||
|
if !reflect.DeepEqual(tt.wantErr, err) {
|
||||||
|
t.Errorf("case %d: incorrect error value: want=%q got=%q", i, tt.wantErr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tt.wantACR, got) {
|
||||||
|
t.Errorf("case %d: incorrect AuthCodeRequest value: want=%#v got=%#v", i, tt.wantACR, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeBadClient struct {
|
||||||
|
Request *http.Request
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeBadClient) Do(r *http.Request) (*http.Response, error) {
|
||||||
|
f.Request = r
|
||||||
|
return nil, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientCredsToken(t *testing.T) {
|
||||||
|
hc := &fakeBadClient{nil, errors.New("error")}
|
||||||
|
cfg := Config{
|
||||||
|
Credentials: ClientCredentials{ID: "c#id", Secret: "c secret"},
|
||||||
|
Scope: []string{"foo-scope", "bar-scope"},
|
||||||
|
TokenURL: "http://example.com/token",
|
||||||
|
AuthMethod: AuthMethodClientSecretBasic,
|
||||||
|
RedirectURL: "http://example.com/redirect",
|
||||||
|
AuthURL: "http://example.com/auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := NewClient(hc, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := []string{"openid"}
|
||||||
|
c.ClientCredsToken(scope)
|
||||||
|
if hc.Request == nil {
|
||||||
|
t.Error("request is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
tu := hc.Request.URL.String()
|
||||||
|
if cfg.TokenURL != tu {
|
||||||
|
t.Errorf("wrong token url, want=%v, got=%v", cfg.TokenURL, tu)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct := hc.Request.Header.Get("Content-Type")
|
||||||
|
if ct != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("wrong content-type, want=application/x-www-form-urlencoded, got=%v", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
cid, secret, ok := phttp.BasicAuth(hc.Request)
|
||||||
|
if !ok {
|
||||||
|
t.Error("unexpected error parsing basic auth")
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.QueryEscape(cfg.Credentials.ID) != cid {
|
||||||
|
t.Errorf("wrong client ID, want=%v, got=%v", cfg.Credentials.ID, cid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.QueryEscape(cfg.Credentials.Secret) != secret {
|
||||||
|
t.Errorf("wrong client secret, want=%v, got=%v", cfg.Credentials.Secret, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = hc.Request.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("unexpected error parsing form")
|
||||||
|
}
|
||||||
|
|
||||||
|
gt := hc.Request.PostForm.Get("grant_type")
|
||||||
|
if gt != GrantTypeClientCreds {
|
||||||
|
t.Errorf("wrong grant_type, want=%v, got=%v", GrantTypeClientCreds, gt)
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := strings.Split(hc.Request.PostForm.Get("scope"), " ")
|
||||||
|
if !reflect.DeepEqual(scope, sc) {
|
||||||
|
t.Errorf("wrong scope, want=%v, got=%v", scope, sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserCredsToken(t *testing.T) {
|
||||||
|
hc := &fakeBadClient{nil, errors.New("error")}
|
||||||
|
cfg := Config{
|
||||||
|
Credentials: ClientCredentials{ID: "c#id", Secret: "c secret"},
|
||||||
|
Scope: []string{"foo-scope", "bar-scope"},
|
||||||
|
TokenURL: "http://example.com/token",
|
||||||
|
AuthMethod: AuthMethodClientSecretBasic,
|
||||||
|
RedirectURL: "http://example.com/redirect",
|
||||||
|
AuthURL: "http://example.com/auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := NewClient(hc, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UserCredsToken("username", "password")
|
||||||
|
if hc.Request == nil {
|
||||||
|
t.Error("request is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
tu := hc.Request.URL.String()
|
||||||
|
if cfg.TokenURL != tu {
|
||||||
|
t.Errorf("wrong token url, want=%v, got=%v", cfg.TokenURL, tu)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct := hc.Request.Header.Get("Content-Type")
|
||||||
|
if ct != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("wrong content-type, want=application/x-www-form-urlencoded, got=%v", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
cid, secret, ok := phttp.BasicAuth(hc.Request)
|
||||||
|
if !ok {
|
||||||
|
t.Error("unexpected error parsing basic auth")
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.QueryEscape(cfg.Credentials.ID) != cid {
|
||||||
|
t.Errorf("wrong client ID, want=%v, got=%v", cfg.Credentials.ID, cid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.QueryEscape(cfg.Credentials.Secret) != secret {
|
||||||
|
t.Errorf("wrong client secret, want=%v, got=%v", cfg.Credentials.Secret, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = hc.Request.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("unexpected error parsing form")
|
||||||
|
}
|
||||||
|
|
||||||
|
gt := hc.Request.PostForm.Get("grant_type")
|
||||||
|
if gt != GrantTypeUserCreds {
|
||||||
|
t.Errorf("wrong grant_type, want=%v, got=%v", GrantTypeUserCreds, gt)
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := strings.Split(hc.Request.PostForm.Get("scope"), " ")
|
||||||
|
if !reflect.DeepEqual(c.scope, sc) {
|
||||||
|
t.Errorf("wrong scope, want=%v, got=%v", c.scope, sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAuthenticatedRequest(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
authMethod string
|
||||||
|
url string
|
||||||
|
values url.Values
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
authMethod: AuthMethodClientSecretBasic,
|
||||||
|
url: "http://example.com/token",
|
||||||
|
values: url.Values{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authMethod: AuthMethodClientSecretPost,
|
||||||
|
url: "http://example.com/token",
|
||||||
|
values: url.Values{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
cfg := Config{
|
||||||
|
Credentials: ClientCredentials{ID: "c#id", Secret: "c secret"},
|
||||||
|
Scope: []string{"foo-scope", "bar-scope"},
|
||||||
|
TokenURL: "http://example.com/token",
|
||||||
|
AuthURL: "http://example.com/auth",
|
||||||
|
RedirectURL: "http://example.com/redirect",
|
||||||
|
AuthMethod: tt.authMethod,
|
||||||
|
}
|
||||||
|
c, err := NewClient(nil, cfg)
|
||||||
|
req, err := c.newAuthenticatedRequest(tt.url, tt.values)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = req.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: want nil err, got %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.authMethod == AuthMethodClientSecretBasic {
|
||||||
|
cid, secret, ok := phttp.BasicAuth(req)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("case %d: !ok parsing Basic Auth headers", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cid != url.QueryEscape(cfg.Credentials.ID) {
|
||||||
|
t.Errorf("case %d: want CID == %q, got CID == %q", i, cfg.Credentials.ID, cid)
|
||||||
|
}
|
||||||
|
if secret != url.QueryEscape(cfg.Credentials.Secret) {
|
||||||
|
t.Errorf("case %d: want secret == %q, got secret == %q", i, cfg.Credentials.Secret, secret)
|
||||||
|
}
|
||||||
|
} else if tt.authMethod == AuthMethodClientSecretPost {
|
||||||
|
if req.PostFormValue("client_secret") != cfg.Credentials.Secret {
|
||||||
|
t.Errorf("case %d: want client_secret == %q, got client_secret == %q",
|
||||||
|
i, cfg.Credentials.Secret, req.PostFormValue("client_secret"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range tt.values {
|
||||||
|
if !reflect.DeepEqual(v, req.PostForm[k]) {
|
||||||
|
t.Errorf("case %d: key:%q want==%q, got==%q", i, k, v, req.PostForm[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.URL.String() != tt.url {
|
||||||
|
t.Errorf("case %d: want URL==%q, got URL==%q", i, tt.url, req.URL.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTokenResponse(t *testing.T) {
|
||||||
|
type response struct {
|
||||||
|
body string
|
||||||
|
contentType string
|
||||||
|
statusCode int // defaults to http.StatusOK
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
resp response
|
||||||
|
wantResp TokenResponse
|
||||||
|
wantError *Error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
resp: response{
|
||||||
|
body: "{ \"error\": \"invalid_client\", \"state\": \"foo\" }",
|
||||||
|
contentType: "application/json",
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
wantError: &Error{Type: "invalid_client", State: "foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resp: response{
|
||||||
|
body: "{ \"error\": \"invalid_request\", \"state\": \"bar\" }",
|
||||||
|
contentType: "application/json",
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
wantError: &Error{Type: "invalid_request", State: "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Actual response from bitbucket
|
||||||
|
resp: response{
|
||||||
|
body: `{"error_description": "Invalid OAuth client credentials", "error": "unauthorized_client"}`,
|
||||||
|
contentType: "application/json",
|
||||||
|
statusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
wantError: &Error{Type: "unauthorized_client", Description: "Invalid OAuth client credentials"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Actual response from github
|
||||||
|
resp: response{
|
||||||
|
body: `error=incorrect_client_credentials&error_description=The+client_id+and%2For+client_secret+passed+are+incorrect.&error_uri=https%3A%2F%2Fdeveloper.github.com%2Fv3%2Foauth%2F%23incorrect-client-credentials`,
|
||||||
|
contentType: "application/x-www-form-urlencoded; charset=utf-8",
|
||||||
|
},
|
||||||
|
wantError: &Error{Type: "incorrect_client_credentials", Description: "The client_id and/or client_secret passed are incorrect."},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resp: response{
|
||||||
|
body: `{"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"}`,
|
||||||
|
contentType: "application/json",
|
||||||
|
},
|
||||||
|
wantResp: TokenResponse{
|
||||||
|
AccessToken: "e72e16c7e42f292c6912e7710c838347ae178b4a",
|
||||||
|
TokenType: "bearer",
|
||||||
|
Scope: "repo,gist",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resp: response{
|
||||||
|
body: `access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer`,
|
||||||
|
contentType: "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
wantResp: TokenResponse{
|
||||||
|
AccessToken: "e72e16c7e42f292c6912e7710c838347ae178b4a",
|
||||||
|
TokenType: "bearer",
|
||||||
|
Scope: "user,gist",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resp: response{
|
||||||
|
body: `{"access_token":"foo","id_token":"bar","expires_in":200,"token_type":"bearer","refresh_token":"spam"}`,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
wantResp: TokenResponse{
|
||||||
|
AccessToken: "foo",
|
||||||
|
IDToken: "bar",
|
||||||
|
Expires: 200,
|
||||||
|
TokenType: "bearer",
|
||||||
|
RefreshToken: "spam",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Azure AD returns "expires_in" value as string
|
||||||
|
resp: response{
|
||||||
|
body: `{"access_token":"foo","id_token":"bar","expires_in":"300","token_type":"bearer","refresh_token":"spam"}`,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
wantResp: TokenResponse{
|
||||||
|
AccessToken: "foo",
|
||||||
|
IDToken: "bar",
|
||||||
|
Expires: 300,
|
||||||
|
TokenType: "bearer",
|
||||||
|
RefreshToken: "spam",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resp: response{
|
||||||
|
body: `{"access_token":"foo","id_token":"bar","expires":200,"token_type":"bearer","refresh_token":"spam"}`,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
wantResp: TokenResponse{
|
||||||
|
AccessToken: "foo",
|
||||||
|
IDToken: "bar",
|
||||||
|
Expires: 200,
|
||||||
|
TokenType: "bearer",
|
||||||
|
RefreshToken: "spam",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resp: response{
|
||||||
|
body: `access_token=foo&id_token=bar&expires_in=200&token_type=bearer&refresh_token=spam`,
|
||||||
|
contentType: "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
wantResp: TokenResponse{
|
||||||
|
AccessToken: "foo",
|
||||||
|
IDToken: "bar",
|
||||||
|
Expires: 200,
|
||||||
|
TokenType: "bearer",
|
||||||
|
RefreshToken: "spam",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
r := &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Header: http.Header{
|
||||||
|
"Content-Type": []string{tt.resp.contentType},
|
||||||
|
"Content-Length": []string{strconv.Itoa(len([]byte(tt.resp.body)))},
|
||||||
|
},
|
||||||
|
Body: ioutil.NopCloser(strings.NewReader(tt.resp.body)),
|
||||||
|
ContentLength: int64(len([]byte(tt.resp.body))),
|
||||||
|
}
|
||||||
|
if tt.resp.statusCode != 0 {
|
||||||
|
r.StatusCode = tt.resp.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := parseTokenResponse(r)
|
||||||
|
if err != nil {
|
||||||
|
if tt.wantError == nil {
|
||||||
|
t.Errorf("case %d: got error==%v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.wantError, err) {
|
||||||
|
t.Errorf("case %d: want=%+v, got=%+v", i, tt.wantError, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tt.wantError != nil {
|
||||||
|
t.Errorf("case %d: want error==%v, got==nil", i, tt.wantError)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// don't compare the raw body (it's really big and clogs error messages)
|
||||||
|
result.RawBody = tt.wantResp.RawBody
|
||||||
|
if !reflect.DeepEqual(tt.wantResp, result) {
|
||||||
|
t.Errorf("case %d: want=%+v, got=%+v", i, tt.wantResp, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
250
vendor/github.com/ericchiang/oidc/oidc.go → vendor/github.com/coreos/go-oidc/oidc.go
generated
vendored
250
vendor/github.com/ericchiang/oidc/oidc.go → vendor/github.com/coreos/go-oidc/oidc.go
generated
vendored
|
@ -1,3 +1,4 @@
|
||||||
|
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,13 +12,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrTokenExpired indicates that a token parsed by a verifier has expired.
|
|
||||||
ErrTokenExpired = errors.New("oidc: ID Token expired")
|
|
||||||
// ErrNotSupported indicates that the requested optional OpenID Connect endpoint is not supported by the provider.
|
|
||||||
ErrNotSupported = errors.New("oidc: endpoint not supported")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,23 +30,61 @@ const (
|
||||||
ScopeOfflineAccess = "offline_access"
|
ScopeOfflineAccess = "offline_access"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider contains the subset of the OpenID Connect provider metadata needed to request
|
// ClientContext returns a new Context that carries the provided HTTP client.
|
||||||
// and verify ID Tokens.
|
//
|
||||||
|
// This method sets the same context key used by the golang.org/x/oauth2 package,
|
||||||
|
// so the returned context works for that package too.
|
||||||
|
//
|
||||||
|
// myClient := &http.Client{}
|
||||||
|
// ctx := oidc.ClientContext(parentContext, myClient)
|
||||||
|
//
|
||||||
|
// // This will use the custom client
|
||||||
|
// provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
|
||||||
|
//
|
||||||
|
func ClientContext(ctx context.Context, client *http.Client) context.Context {
|
||||||
|
return context.WithValue(ctx, oauth2.HTTPClient, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientFromContext(ctx context.Context) *http.Client {
|
||||||
|
if client, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
return http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider represents an OpenID Connect server's configuration.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
|
issuer string
|
||||||
|
authURL string
|
||||||
|
tokenURL string
|
||||||
|
userInfoURL string
|
||||||
|
|
||||||
|
// Raw claims returned by the server.
|
||||||
|
rawClaims []byte
|
||||||
|
|
||||||
|
remoteKeySet *remoteKeySet
|
||||||
|
}
|
||||||
|
|
||||||
|
type cachedKeys struct {
|
||||||
|
keys []jose.JSONWebKey
|
||||||
|
expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerJSON struct {
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
AuthURL string `json:"authorization_endpoint"`
|
AuthURL string `json:"authorization_endpoint"`
|
||||||
TokenURL string `json:"token_endpoint"`
|
TokenURL string `json:"token_endpoint"`
|
||||||
JWKSURL string `json:"jwks_uri"`
|
JWKSURL string `json:"jwks_uri"`
|
||||||
UserInfoURL string `json:"userinfo_endpoint"`
|
UserInfoURL string `json:"userinfo_endpoint"`
|
||||||
|
|
||||||
// Raw claims returned by the server.
|
|
||||||
rawClaims []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProvider uses the OpenID Connect disovery mechanism to construct a Provider.
|
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
|
||||||
|
//
|
||||||
|
// The issuer is the URL identifier for the service. For example: "https://accounts.google.com"
|
||||||
|
// or "https://login.salesforce.com".
|
||||||
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
|
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
|
||||||
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
|
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
|
||||||
resp, err := contextClient(ctx).Get(wellKnown)
|
resp, err := clientFromContext(ctx).Get(wellKnown)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -63,18 +96,36 @@ func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
|
||||||
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var p Provider
|
var p providerJSON
|
||||||
if err := json.Unmarshal(body, &p); err != nil {
|
if err := json.Unmarshal(body, &p); err != nil {
|
||||||
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
|
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
|
||||||
}
|
}
|
||||||
p.rawClaims = body
|
|
||||||
if p.Issuer != issuer {
|
if p.Issuer != issuer {
|
||||||
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
|
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
|
||||||
}
|
}
|
||||||
return &p, nil
|
return &Provider{
|
||||||
|
issuer: p.Issuer,
|
||||||
|
authURL: p.AuthURL,
|
||||||
|
tokenURL: p.TokenURL,
|
||||||
|
userInfoURL: p.UserInfoURL,
|
||||||
|
rawClaims: body,
|
||||||
|
remoteKeySet: newRemoteKeySet(ctx, p.JWKSURL, time.Now),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Claims returns additional fields returned by the server during discovery.
|
// Claims unmarshals raw fields returned by the server during discovery.
|
||||||
|
//
|
||||||
|
// var claims struct {
|
||||||
|
// ScopesSupported []string `json:"scopes_supported"`
|
||||||
|
// ClaimsSupported []string `json:"claims_supported"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if err := provider.Claims(&claims); err != nil {
|
||||||
|
// // handle unmarshaling error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// For a list of fields defined by the OpenID Connect spec see:
|
||||||
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||||
func (p *Provider) Claims(v interface{}) error {
|
func (p *Provider) Claims(v interface{}) error {
|
||||||
if p.rawClaims == nil {
|
if p.rawClaims == nil {
|
||||||
return errors.New("oidc: claims not set")
|
return errors.New("oidc: claims not set")
|
||||||
|
@ -84,7 +135,7 @@ func (p *Provider) Claims(v interface{}) error {
|
||||||
|
|
||||||
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
|
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
|
||||||
func (p *Provider) Endpoint() oauth2.Endpoint {
|
func (p *Provider) Endpoint() oauth2.Endpoint {
|
||||||
return oauth2.Endpoint{AuthURL: p.AuthURL, TokenURL: p.TokenURL}
|
return oauth2.Endpoint{AuthURL: p.authURL, TokenURL: p.tokenURL}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserInfo represents the OpenID Connect userinfo claims.
|
// UserInfo represents the OpenID Connect userinfo claims.
|
||||||
|
@ -107,11 +158,10 @@ func (u *UserInfo) Claims(v interface{}) error {
|
||||||
|
|
||||||
// UserInfo uses the token source to query the provider's user info endpoint.
|
// UserInfo uses the token source to query the provider's user info endpoint.
|
||||||
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
|
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
|
||||||
if p.UserInfoURL == "" {
|
if p.userInfoURL == "" {
|
||||||
return nil, ErrNotSupported
|
return nil, errors.New("oidc: user info endpoint is not supported by this provider")
|
||||||
}
|
}
|
||||||
cli := oauth2.NewClient(ctx, tokenSource)
|
resp, err := clientFromContext(ctx).Get(p.userInfoURL)
|
||||||
resp, err := cli.Get(p.UserInfoURL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -137,19 +187,6 @@ func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource)
|
||||||
//
|
//
|
||||||
// The ID Token only holds fields OpenID Connect requires. To access additional
|
// The ID Token only holds fields OpenID Connect requires. To access additional
|
||||||
// claims returned by the server, use the Claims method.
|
// claims returned by the server, use the Claims method.
|
||||||
//
|
|
||||||
// idToken, err := idTokenVerifier.Verify(rawIDToken)
|
|
||||||
// if err != nil {
|
|
||||||
// // handle error
|
|
||||||
// }
|
|
||||||
// var claims struct {
|
|
||||||
// Email string `json:"email"`
|
|
||||||
// EmailVerified bool `json:"email_verified"`
|
|
||||||
// }
|
|
||||||
// if err := idToken.Claims(&claims); err != nil {
|
|
||||||
// // handle error
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
type IDToken struct {
|
type IDToken struct {
|
||||||
// The URL of the server which issued this token. This will always be the same
|
// The URL of the server which issued this token. This will always be the same
|
||||||
// as the URL used for initial discovery.
|
// as the URL used for initial discovery.
|
||||||
|
@ -165,10 +202,24 @@ type IDToken struct {
|
||||||
Expiry time.Time
|
Expiry time.Time
|
||||||
Nonce string
|
Nonce string
|
||||||
|
|
||||||
|
// Raw payload of the id_token.
|
||||||
claims []byte
|
claims []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Claims unmarshals the raw JSON payload of the ID Token into a provided struct.
|
// Claims unmarshals the raw JSON payload of the ID Token into a provided struct.
|
||||||
|
//
|
||||||
|
// idToken, err := idTokenVerifier.Verify(rawIDToken)
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
// var claims struct {
|
||||||
|
// Email string `json:"email"`
|
||||||
|
// EmailVerified bool `json:"email_verified"`
|
||||||
|
// }
|
||||||
|
// if err := idToken.Claims(&claims); err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
func (i *IDToken) Claims(v interface{}) error {
|
func (i *IDToken) Claims(v interface{}) error {
|
||||||
if i.claims == nil {
|
if i.claims == nil {
|
||||||
return errors.New("oidc: claims not set")
|
return errors.New("oidc: claims not set")
|
||||||
|
@ -176,6 +227,15 @@ func (i *IDToken) Claims(v interface{}) error {
|
||||||
return json.Unmarshal(i.claims, v)
|
return json.Unmarshal(i.claims, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type idToken struct {
|
||||||
|
Issuer string `json:"iss"`
|
||||||
|
Subject string `json:"sub"`
|
||||||
|
Audience audience `json:"aud"`
|
||||||
|
Expiry jsonTime `json:"exp"`
|
||||||
|
IssuedAt jsonTime `json:"iat"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
}
|
||||||
|
|
||||||
type audience []string
|
type audience []string
|
||||||
|
|
||||||
func (a *audience) UnmarshalJSON(b []byte) error {
|
func (a *audience) UnmarshalJSON(b []byte) error {
|
||||||
|
@ -192,6 +252,13 @@ func (a *audience) UnmarshalJSON(b []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a audience) MarshalJSON() ([]byte, error) {
|
||||||
|
if len(a) == 1 {
|
||||||
|
return json.Marshal(a[0])
|
||||||
|
}
|
||||||
|
return json.Marshal([]string(a))
|
||||||
|
}
|
||||||
|
|
||||||
type jsonTime time.Time
|
type jsonTime time.Time
|
||||||
|
|
||||||
func (j *jsonTime) UnmarshalJSON(b []byte) error {
|
func (j *jsonTime) UnmarshalJSON(b []byte) error {
|
||||||
|
@ -214,113 +281,6 @@ func (j *jsonTime) UnmarshalJSON(b []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type idToken struct {
|
func (j jsonTime) MarshalJSON() ([]byte, error) {
|
||||||
Issuer string `json:"iss"`
|
return json.Marshal(time.Time(j).Unix())
|
||||||
Subject string `json:"sub"`
|
|
||||||
Audience audience `json:"aud"`
|
|
||||||
Expiry jsonTime `json:"exp"`
|
|
||||||
IssuedAt jsonTime `json:"iat"`
|
|
||||||
Nonce string `json:"nonce"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDTokenVerifier provides verification for ID Tokens.
|
|
||||||
type IDTokenVerifier struct {
|
|
||||||
issuer string
|
|
||||||
keySet *remoteKeySet
|
|
||||||
options []VerificationOption
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify parse the raw ID Token, verifies it's been signed by the provider, preforms
|
|
||||||
// additional verification, and returns the claims.
|
|
||||||
func (v *IDTokenVerifier) Verify(rawIDToken string) (*IDToken, error) {
|
|
||||||
payload, err := v.keySet.verifyJWT(rawIDToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var token idToken
|
|
||||||
if err := json.Unmarshal(payload, &token); err != nil {
|
|
||||||
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
|
|
||||||
}
|
|
||||||
if v.issuer != token.Issuer {
|
|
||||||
return nil, fmt.Errorf("oidc: iss field did not match provider issuer")
|
|
||||||
}
|
|
||||||
t := &IDToken{
|
|
||||||
Issuer: token.Issuer,
|
|
||||||
Subject: token.Subject,
|
|
||||||
Audience: []string(token.Audience),
|
|
||||||
Expiry: time.Time(token.Expiry),
|
|
||||||
IssuedAt: time.Time(token.Expiry),
|
|
||||||
Nonce: token.Nonce,
|
|
||||||
claims: payload,
|
|
||||||
}
|
|
||||||
for _, option := range v.options {
|
|
||||||
if err := option.verifyIDToken(t); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVerifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
|
|
||||||
//
|
|
||||||
// The verifier queries the provider to update keys when a signature cannot be verified by the
|
|
||||||
// set of keys cached from the previous request.
|
|
||||||
func (p *Provider) NewVerifier(ctx context.Context, options ...VerificationOption) *IDTokenVerifier {
|
|
||||||
return &IDTokenVerifier{
|
|
||||||
issuer: p.Issuer,
|
|
||||||
keySet: newRemoteKeySet(ctx, p.JWKSURL),
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerificationOption is an option provided to Provider.NewVerifier.
|
|
||||||
type VerificationOption interface {
|
|
||||||
verifyIDToken(token *IDToken) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyAudience ensures that an ID Token was issued for the specific client.
|
|
||||||
//
|
|
||||||
// Note that a verified token may be valid for other clients, as OpenID Connect allows a token to have
|
|
||||||
// multiple audiences.
|
|
||||||
func VerifyAudience(clientID string) VerificationOption {
|
|
||||||
return clientVerifier{clientID}
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientVerifier struct {
|
|
||||||
clientID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c clientVerifier) verifyIDToken(token *IDToken) error {
|
|
||||||
for _, aud := range token.Audience {
|
|
||||||
if aud == c.clientID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("oidc: id token aud field did not match client_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyExpiry ensures that an ID Token has not expired.
|
|
||||||
func VerifyExpiry() VerificationOption {
|
|
||||||
return expiryVerifier{time.Now}
|
|
||||||
}
|
|
||||||
|
|
||||||
type expiryVerifier struct {
|
|
||||||
now func() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e expiryVerifier) verifyIDToken(token *IDToken) error {
|
|
||||||
if e.now().After(token.Expiry) {
|
|
||||||
return ErrTokenExpired
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method is internal to golang.org/x/oauth2. Just copy it.
|
|
||||||
func contextClient(ctx context.Context) *http.Client {
|
|
||||||
if ctx != nil {
|
|
||||||
if hc, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
|
|
||||||
return hc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return http.DefaultClient
|
|
||||||
}
|
}
|
846
vendor/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
Normal file
846
vendor/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
Normal file
|
@ -0,0 +1,846 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/mail"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
phttp "github.com/coreos/go-oidc/http"
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
"github.com/coreos/go-oidc/key"
|
||||||
|
"github.com/coreos/go-oidc/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// amount of time that must pass after the last key sync
|
||||||
|
// completes before another attempt may begin
|
||||||
|
keySyncWindow = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultScope = []string{"openid", "email", "profile"}
|
||||||
|
|
||||||
|
supportedAuthMethods = map[string]struct{}{
|
||||||
|
oauth2.AuthMethodClientSecretBasic: struct{}{},
|
||||||
|
oauth2.AuthMethodClientSecretPost: struct{}{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientCredentials oauth2.ClientCredentials
|
||||||
|
|
||||||
|
type ClientIdentity struct {
|
||||||
|
Credentials ClientCredentials
|
||||||
|
Metadata ClientMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWAOptions struct {
|
||||||
|
// SigningAlg specifies an JWA alg for signing JWTs.
|
||||||
|
//
|
||||||
|
// Specifying this field implies different actions depending on the context. It may
|
||||||
|
// require objects be serialized and signed as a JWT instead of plain JSON, or
|
||||||
|
// require an existing JWT object use the specified alg.
|
||||||
|
//
|
||||||
|
// See: http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
|
||||||
|
SigningAlg string
|
||||||
|
// EncryptionAlg, if provided, specifies that the returned or sent object be stored
|
||||||
|
// (or nested) within a JWT object and encrypted with the provided JWA alg.
|
||||||
|
EncryptionAlg string
|
||||||
|
// EncryptionEnc specifies the JWA enc algorithm to use with EncryptionAlg. If
|
||||||
|
// EncryptionAlg is provided and EncryptionEnc is omitted, this field defaults
|
||||||
|
// to A128CBC-HS256.
|
||||||
|
//
|
||||||
|
// If EncryptionEnc is provided EncryptionAlg must also be specified.
|
||||||
|
EncryptionEnc string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt JWAOptions) valid() error {
|
||||||
|
if opt.EncryptionEnc != "" && opt.EncryptionAlg == "" {
|
||||||
|
return errors.New("encryption encoding provided with no encryption algorithm")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt JWAOptions) defaults() JWAOptions {
|
||||||
|
if opt.EncryptionAlg != "" && opt.EncryptionEnc == "" {
|
||||||
|
opt.EncryptionEnc = jose.EncA128CBCHS256
|
||||||
|
}
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Ensure ClientMetadata satisfies these interfaces.
|
||||||
|
_ json.Marshaler = &ClientMetadata{}
|
||||||
|
_ json.Unmarshaler = &ClientMetadata{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientMetadata holds metadata that the authorization server associates
|
||||||
|
// with a client identifier. The fields range from human-facing display
|
||||||
|
// strings such as client name, to items that impact the security of the
|
||||||
|
// protocol, such as the list of valid redirect URIs.
|
||||||
|
//
|
||||||
|
// See http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
|
||||||
|
//
|
||||||
|
// TODO: support language specific claim representations
|
||||||
|
// http://openid.net/specs/openid-connect-registration-1_0.html#LanguagesAndScripts
|
||||||
|
type ClientMetadata struct {
|
||||||
|
RedirectURIs []url.URL // Required
|
||||||
|
|
||||||
|
// A list of OAuth 2.0 "response_type" values that the client wishes to restrict
|
||||||
|
// itself to. Either "code", "token", or another registered extension.
|
||||||
|
//
|
||||||
|
// If omitted, only "code" will be used.
|
||||||
|
ResponseTypes []string
|
||||||
|
// A list of OAuth 2.0 grant types the client wishes to restrict itself to.
|
||||||
|
// The grant type values used by OIDC are "authorization_code", "implicit",
|
||||||
|
// and "refresh_token".
|
||||||
|
//
|
||||||
|
// If ommitted, only "authorization_code" will be used.
|
||||||
|
GrantTypes []string
|
||||||
|
// "native" or "web". If omitted, "web".
|
||||||
|
ApplicationType string
|
||||||
|
|
||||||
|
// List of email addresses.
|
||||||
|
Contacts []mail.Address
|
||||||
|
// Name of client to be presented to the end-user.
|
||||||
|
ClientName string
|
||||||
|
// URL that references a logo for the Client application.
|
||||||
|
LogoURI *url.URL
|
||||||
|
// URL of the home page of the Client.
|
||||||
|
ClientURI *url.URL
|
||||||
|
// Profile data policies and terms of use to be provided to the end user.
|
||||||
|
PolicyURI *url.URL
|
||||||
|
TermsOfServiceURI *url.URL
|
||||||
|
|
||||||
|
// URL to or the value of the client's JSON Web Key Set document.
|
||||||
|
JWKSURI *url.URL
|
||||||
|
JWKS *jose.JWKSet
|
||||||
|
|
||||||
|
// URL referencing a flie with a single JSON array of redirect URIs.
|
||||||
|
SectorIdentifierURI *url.URL
|
||||||
|
|
||||||
|
SubjectType string
|
||||||
|
|
||||||
|
// Options to restrict the JWS alg and enc values used for server responses and requests.
|
||||||
|
IDTokenResponseOptions JWAOptions
|
||||||
|
UserInfoResponseOptions JWAOptions
|
||||||
|
RequestObjectOptions JWAOptions
|
||||||
|
|
||||||
|
// Client requested authorization method and signing options for the token endpoint.
|
||||||
|
//
|
||||||
|
// Defaults to "client_secret_basic"
|
||||||
|
TokenEndpointAuthMethod string
|
||||||
|
TokenEndpointAuthSigningAlg string
|
||||||
|
|
||||||
|
// DefaultMaxAge specifies the maximum amount of time in seconds before an authorized
|
||||||
|
// user must reauthroize.
|
||||||
|
//
|
||||||
|
// If 0, no limitation is placed on the maximum.
|
||||||
|
DefaultMaxAge int64
|
||||||
|
// RequireAuthTime specifies if the auth_time claim in the ID token is required.
|
||||||
|
RequireAuthTime bool
|
||||||
|
|
||||||
|
// Default Authentication Context Class Reference values for authentication requests.
|
||||||
|
DefaultACRValues []string
|
||||||
|
|
||||||
|
// URI that a third party can use to initiate a login by the relaying party.
|
||||||
|
//
|
||||||
|
// See: http://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
|
||||||
|
InitiateLoginURI *url.URL
|
||||||
|
// Pre-registered request_uri values that may be cached by the server.
|
||||||
|
RequestURIs []url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults returns a shallow copy of ClientMetadata with default
|
||||||
|
// values replacing omitted fields.
|
||||||
|
func (m ClientMetadata) Defaults() ClientMetadata {
|
||||||
|
if len(m.ResponseTypes) == 0 {
|
||||||
|
m.ResponseTypes = []string{oauth2.ResponseTypeCode}
|
||||||
|
}
|
||||||
|
if len(m.GrantTypes) == 0 {
|
||||||
|
m.GrantTypes = []string{oauth2.GrantTypeAuthCode}
|
||||||
|
}
|
||||||
|
if m.ApplicationType == "" {
|
||||||
|
m.ApplicationType = "web"
|
||||||
|
}
|
||||||
|
if m.TokenEndpointAuthMethod == "" {
|
||||||
|
m.TokenEndpointAuthMethod = oauth2.AuthMethodClientSecretBasic
|
||||||
|
}
|
||||||
|
m.IDTokenResponseOptions = m.IDTokenResponseOptions.defaults()
|
||||||
|
m.UserInfoResponseOptions = m.UserInfoResponseOptions.defaults()
|
||||||
|
m.RequestObjectOptions = m.RequestObjectOptions.defaults()
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ClientMetadata) MarshalJSON() ([]byte, error) {
|
||||||
|
e := m.toEncodableStruct()
|
||||||
|
return json.Marshal(&e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ClientMetadata) UnmarshalJSON(data []byte) error {
|
||||||
|
var e encodableClientMetadata
|
||||||
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
meta, err := e.toStruct()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := meta.Valid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = meta
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodableClientMetadata struct {
|
||||||
|
RedirectURIs []string `json:"redirect_uris"` // Required
|
||||||
|
ResponseTypes []string `json:"response_types,omitempty"`
|
||||||
|
GrantTypes []string `json:"grant_types,omitempty"`
|
||||||
|
ApplicationType string `json:"application_type,omitempty"`
|
||||||
|
Contacts []string `json:"contacts,omitempty"`
|
||||||
|
ClientName string `json:"client_name,omitempty"`
|
||||||
|
LogoURI string `json:"logo_uri,omitempty"`
|
||||||
|
ClientURI string `json:"client_uri,omitempty"`
|
||||||
|
PolicyURI string `json:"policy_uri,omitempty"`
|
||||||
|
TermsOfServiceURI string `json:"tos_uri,omitempty"`
|
||||||
|
JWKSURI string `json:"jwks_uri,omitempty"`
|
||||||
|
JWKS *jose.JWKSet `json:"jwks,omitempty"`
|
||||||
|
SectorIdentifierURI string `json:"sector_identifier_uri,omitempty"`
|
||||||
|
SubjectType string `json:"subject_type,omitempty"`
|
||||||
|
IDTokenSignedResponseAlg string `json:"id_token_signed_response_alg,omitempty"`
|
||||||
|
IDTokenEncryptedResponseAlg string `json:"id_token_encrypted_response_alg,omitempty"`
|
||||||
|
IDTokenEncryptedResponseEnc string `json:"id_token_encrypted_response_enc,omitempty"`
|
||||||
|
UserInfoSignedResponseAlg string `json:"userinfo_signed_response_alg,omitempty"`
|
||||||
|
UserInfoEncryptedResponseAlg string `json:"userinfo_encrypted_response_alg,omitempty"`
|
||||||
|
UserInfoEncryptedResponseEnc string `json:"userinfo_encrypted_response_enc,omitempty"`
|
||||||
|
RequestObjectSigningAlg string `json:"request_object_signing_alg,omitempty"`
|
||||||
|
RequestObjectEncryptionAlg string `json:"request_object_encryption_alg,omitempty"`
|
||||||
|
RequestObjectEncryptionEnc string `json:"request_object_encryption_enc,omitempty"`
|
||||||
|
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
|
||||||
|
TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg,omitempty"`
|
||||||
|
DefaultMaxAge int64 `json:"default_max_age,omitempty"`
|
||||||
|
RequireAuthTime bool `json:"require_auth_time,omitempty"`
|
||||||
|
DefaultACRValues []string `json:"default_acr_values,omitempty"`
|
||||||
|
InitiateLoginURI string `json:"initiate_login_uri,omitempty"`
|
||||||
|
RequestURIs []string `json:"request_uris,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *encodableClientMetadata) toStruct() (ClientMetadata, error) {
|
||||||
|
p := stickyErrParser{}
|
||||||
|
m := ClientMetadata{
|
||||||
|
RedirectURIs: p.parseURIs(c.RedirectURIs, "redirect_uris"),
|
||||||
|
ResponseTypes: c.ResponseTypes,
|
||||||
|
GrantTypes: c.GrantTypes,
|
||||||
|
ApplicationType: c.ApplicationType,
|
||||||
|
Contacts: p.parseEmails(c.Contacts, "contacts"),
|
||||||
|
ClientName: c.ClientName,
|
||||||
|
LogoURI: p.parseURI(c.LogoURI, "logo_uri"),
|
||||||
|
ClientURI: p.parseURI(c.ClientURI, "client_uri"),
|
||||||
|
PolicyURI: p.parseURI(c.PolicyURI, "policy_uri"),
|
||||||
|
TermsOfServiceURI: p.parseURI(c.TermsOfServiceURI, "tos_uri"),
|
||||||
|
JWKSURI: p.parseURI(c.JWKSURI, "jwks_uri"),
|
||||||
|
JWKS: c.JWKS,
|
||||||
|
SectorIdentifierURI: p.parseURI(c.SectorIdentifierURI, "sector_identifier_uri"),
|
||||||
|
SubjectType: c.SubjectType,
|
||||||
|
TokenEndpointAuthMethod: c.TokenEndpointAuthMethod,
|
||||||
|
TokenEndpointAuthSigningAlg: c.TokenEndpointAuthSigningAlg,
|
||||||
|
DefaultMaxAge: c.DefaultMaxAge,
|
||||||
|
RequireAuthTime: c.RequireAuthTime,
|
||||||
|
DefaultACRValues: c.DefaultACRValues,
|
||||||
|
InitiateLoginURI: p.parseURI(c.InitiateLoginURI, "initiate_login_uri"),
|
||||||
|
RequestURIs: p.parseURIs(c.RequestURIs, "request_uris"),
|
||||||
|
IDTokenResponseOptions: JWAOptions{
|
||||||
|
c.IDTokenSignedResponseAlg,
|
||||||
|
c.IDTokenEncryptedResponseAlg,
|
||||||
|
c.IDTokenEncryptedResponseEnc,
|
||||||
|
},
|
||||||
|
UserInfoResponseOptions: JWAOptions{
|
||||||
|
c.UserInfoSignedResponseAlg,
|
||||||
|
c.UserInfoEncryptedResponseAlg,
|
||||||
|
c.UserInfoEncryptedResponseEnc,
|
||||||
|
},
|
||||||
|
RequestObjectOptions: JWAOptions{
|
||||||
|
c.RequestObjectSigningAlg,
|
||||||
|
c.RequestObjectEncryptionAlg,
|
||||||
|
c.RequestObjectEncryptionEnc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if p.firstErr != nil {
|
||||||
|
return ClientMetadata{}, p.firstErr
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stickyErrParser parses URIs and email addresses. Once it encounters
|
||||||
|
// a parse error, subsequent calls become no-op.
|
||||||
|
type stickyErrParser struct {
|
||||||
|
firstErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *stickyErrParser) parseURI(s, field string) *url.URL {
|
||||||
|
if p.firstErr != nil || s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err == nil {
|
||||||
|
if u.Host == "" {
|
||||||
|
err = errors.New("no host in URI")
|
||||||
|
} else if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
|
err = errors.New("invalid URI scheme")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
p.firstErr = fmt.Errorf("failed to parse %s: %v", field, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *stickyErrParser) parseURIs(s []string, field string) []url.URL {
|
||||||
|
if p.firstErr != nil || len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
uris := make([]url.URL, len(s))
|
||||||
|
for i, val := range s {
|
||||||
|
if val == "" {
|
||||||
|
p.firstErr = fmt.Errorf("invalid URI in field %s", field)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if u := p.parseURI(val, field); u != nil {
|
||||||
|
uris[i] = *u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uris
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *stickyErrParser) parseEmails(s []string, field string) []mail.Address {
|
||||||
|
if p.firstErr != nil || len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
addrs := make([]mail.Address, len(s))
|
||||||
|
for i, addr := range s {
|
||||||
|
if addr == "" {
|
||||||
|
p.firstErr = fmt.Errorf("invalid email in field %s", field)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
a, err := mail.ParseAddress(addr)
|
||||||
|
if err != nil {
|
||||||
|
p.firstErr = fmt.Errorf("invalid email in field %s: %v", field, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
addrs[i] = *a
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ClientMetadata) toEncodableStruct() encodableClientMetadata {
|
||||||
|
return encodableClientMetadata{
|
||||||
|
RedirectURIs: urisToStrings(m.RedirectURIs),
|
||||||
|
ResponseTypes: m.ResponseTypes,
|
||||||
|
GrantTypes: m.GrantTypes,
|
||||||
|
ApplicationType: m.ApplicationType,
|
||||||
|
Contacts: emailsToStrings(m.Contacts),
|
||||||
|
ClientName: m.ClientName,
|
||||||
|
LogoURI: uriToString(m.LogoURI),
|
||||||
|
ClientURI: uriToString(m.ClientURI),
|
||||||
|
PolicyURI: uriToString(m.PolicyURI),
|
||||||
|
TermsOfServiceURI: uriToString(m.TermsOfServiceURI),
|
||||||
|
JWKSURI: uriToString(m.JWKSURI),
|
||||||
|
JWKS: m.JWKS,
|
||||||
|
SectorIdentifierURI: uriToString(m.SectorIdentifierURI),
|
||||||
|
SubjectType: m.SubjectType,
|
||||||
|
IDTokenSignedResponseAlg: m.IDTokenResponseOptions.SigningAlg,
|
||||||
|
IDTokenEncryptedResponseAlg: m.IDTokenResponseOptions.EncryptionAlg,
|
||||||
|
IDTokenEncryptedResponseEnc: m.IDTokenResponseOptions.EncryptionEnc,
|
||||||
|
UserInfoSignedResponseAlg: m.UserInfoResponseOptions.SigningAlg,
|
||||||
|
UserInfoEncryptedResponseAlg: m.UserInfoResponseOptions.EncryptionAlg,
|
||||||
|
UserInfoEncryptedResponseEnc: m.UserInfoResponseOptions.EncryptionEnc,
|
||||||
|
RequestObjectSigningAlg: m.RequestObjectOptions.SigningAlg,
|
||||||
|
RequestObjectEncryptionAlg: m.RequestObjectOptions.EncryptionAlg,
|
||||||
|
RequestObjectEncryptionEnc: m.RequestObjectOptions.EncryptionEnc,
|
||||||
|
TokenEndpointAuthMethod: m.TokenEndpointAuthMethod,
|
||||||
|
TokenEndpointAuthSigningAlg: m.TokenEndpointAuthSigningAlg,
|
||||||
|
DefaultMaxAge: m.DefaultMaxAge,
|
||||||
|
RequireAuthTime: m.RequireAuthTime,
|
||||||
|
DefaultACRValues: m.DefaultACRValues,
|
||||||
|
InitiateLoginURI: uriToString(m.InitiateLoginURI),
|
||||||
|
RequestURIs: urisToStrings(m.RequestURIs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uriToString(u *url.URL) string {
|
||||||
|
if u == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func urisToStrings(urls []url.URL) []string {
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sli := make([]string, len(urls))
|
||||||
|
for i, u := range urls {
|
||||||
|
sli[i] = u.String()
|
||||||
|
}
|
||||||
|
return sli
|
||||||
|
}
|
||||||
|
|
||||||
|
func emailsToStrings(addrs []mail.Address) []string {
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sli := make([]string, len(addrs))
|
||||||
|
for i, addr := range addrs {
|
||||||
|
sli[i] = addr.String()
|
||||||
|
}
|
||||||
|
return sli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid determines if a ClientMetadata conforms with the OIDC specification.
|
||||||
|
//
|
||||||
|
// Valid is called by UnmarshalJSON.
|
||||||
|
//
|
||||||
|
// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
|
||||||
|
// URLs fields where the OIDC spec requires it. This may change in future releases
|
||||||
|
// of this package. See: https://github.com/coreos/go-oidc/issues/34
|
||||||
|
func (m *ClientMetadata) Valid() error {
|
||||||
|
if len(m.RedirectURIs) == 0 {
|
||||||
|
return errors.New("zero redirect URLs")
|
||||||
|
}
|
||||||
|
|
||||||
|
validURI := func(u *url.URL, fieldName string) error {
|
||||||
|
if u.Host == "" {
|
||||||
|
return fmt.Errorf("no host for uri field %s", fieldName)
|
||||||
|
}
|
||||||
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
|
return fmt.Errorf("uri field %s scheme is not http or https", fieldName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uris := []struct {
|
||||||
|
val *url.URL
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{m.LogoURI, "logo_uri"},
|
||||||
|
{m.ClientURI, "client_uri"},
|
||||||
|
{m.PolicyURI, "policy_uri"},
|
||||||
|
{m.TermsOfServiceURI, "tos_uri"},
|
||||||
|
{m.JWKSURI, "jwks_uri"},
|
||||||
|
{m.SectorIdentifierURI, "sector_identifier_uri"},
|
||||||
|
{m.InitiateLoginURI, "initiate_login_uri"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, uri := range uris {
|
||||||
|
if uri.val == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := validURI(uri.val, uri.name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uriLists := []struct {
|
||||||
|
vals []url.URL
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{m.RedirectURIs, "redirect_uris"},
|
||||||
|
{m.RequestURIs, "request_uris"},
|
||||||
|
}
|
||||||
|
for _, list := range uriLists {
|
||||||
|
for _, uri := range list.vals {
|
||||||
|
if err := validURI(&uri, list.name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options := []struct {
|
||||||
|
option JWAOptions
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{m.IDTokenResponseOptions, "id_token response"},
|
||||||
|
{m.UserInfoResponseOptions, "userinfo response"},
|
||||||
|
{m.RequestObjectOptions, "request_object"},
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
if err := option.option.valid(); err != nil {
|
||||||
|
return fmt.Errorf("invalid JWA values for %s: %v", option.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientRegistrationResponse struct {
|
||||||
|
ClientID string // Required
|
||||||
|
ClientSecret string
|
||||||
|
RegistrationAccessToken string
|
||||||
|
RegistrationClientURI string
|
||||||
|
// If IsZero is true, unspecified.
|
||||||
|
ClientIDIssuedAt time.Time
|
||||||
|
// Time at which the client_secret will expire.
|
||||||
|
// If IsZero is true, it will not expire.
|
||||||
|
ClientSecretExpiresAt time.Time
|
||||||
|
|
||||||
|
ClientMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodableClientRegistrationResponse struct {
|
||||||
|
ClientID string `json:"client_id"` // Required
|
||||||
|
ClientSecret string `json:"client_secret,omitempty"`
|
||||||
|
RegistrationAccessToken string `json:"registration_access_token,omitempty"`
|
||||||
|
RegistrationClientURI string `json:"registration_client_uri,omitempty"`
|
||||||
|
ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
|
||||||
|
// Time at which the client_secret will expire, in seconds since the epoch.
|
||||||
|
// If 0 it will not expire.
|
||||||
|
ClientSecretExpiresAt int64 `json:"client_secret_expires_at"` // Required
|
||||||
|
|
||||||
|
encodableClientMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func unixToSec(t time.Time) int64 {
|
||||||
|
if t.IsZero() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return t.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientRegistrationResponse) MarshalJSON() ([]byte, error) {
|
||||||
|
e := encodableClientRegistrationResponse{
|
||||||
|
ClientID: c.ClientID,
|
||||||
|
ClientSecret: c.ClientSecret,
|
||||||
|
RegistrationAccessToken: c.RegistrationAccessToken,
|
||||||
|
RegistrationClientURI: c.RegistrationClientURI,
|
||||||
|
ClientIDIssuedAt: unixToSec(c.ClientIDIssuedAt),
|
||||||
|
ClientSecretExpiresAt: unixToSec(c.ClientSecretExpiresAt),
|
||||||
|
encodableClientMetadata: c.ClientMetadata.toEncodableStruct(),
|
||||||
|
}
|
||||||
|
return json.Marshal(&e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func secToUnix(sec int64) time.Time {
|
||||||
|
if sec == 0 {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return time.Unix(sec, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientRegistrationResponse) UnmarshalJSON(data []byte) error {
|
||||||
|
var e encodableClientRegistrationResponse
|
||||||
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.ClientID == "" {
|
||||||
|
return errors.New("no client_id in client registration response")
|
||||||
|
}
|
||||||
|
metadata, err := e.encodableClientMetadata.toStruct()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = ClientRegistrationResponse{
|
||||||
|
ClientID: e.ClientID,
|
||||||
|
ClientSecret: e.ClientSecret,
|
||||||
|
RegistrationAccessToken: e.RegistrationAccessToken,
|
||||||
|
RegistrationClientURI: e.RegistrationClientURI,
|
||||||
|
ClientIDIssuedAt: secToUnix(e.ClientIDIssuedAt),
|
||||||
|
ClientSecretExpiresAt: secToUnix(e.ClientSecretExpiresAt),
|
||||||
|
ClientMetadata: metadata,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
HTTPClient phttp.Client
|
||||||
|
Credentials ClientCredentials
|
||||||
|
Scope []string
|
||||||
|
RedirectURL string
|
||||||
|
ProviderConfig ProviderConfig
|
||||||
|
KeySet key.PublicKeySet
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(cfg ClientConfig) (*Client, error) {
|
||||||
|
// Allow empty redirect URL in the case where the client
|
||||||
|
// only needs to verify a given token.
|
||||||
|
ru, err := url.Parse(cfg.RedirectURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid redirect URL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := Client{
|
||||||
|
credentials: cfg.Credentials,
|
||||||
|
httpClient: cfg.HTTPClient,
|
||||||
|
scope: cfg.Scope,
|
||||||
|
redirectURL: ru.String(),
|
||||||
|
providerConfig: newProviderConfigRepo(cfg.ProviderConfig),
|
||||||
|
keySet: cfg.KeySet,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.httpClient == nil {
|
||||||
|
c.httpClient = http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.scope == nil {
|
||||||
|
c.scope = make([]string, len(DefaultScope))
|
||||||
|
copy(c.scope, DefaultScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
httpClient phttp.Client
|
||||||
|
providerConfig *providerConfigRepo
|
||||||
|
credentials ClientCredentials
|
||||||
|
redirectURL string
|
||||||
|
scope []string
|
||||||
|
keySet key.PublicKeySet
|
||||||
|
providerSyncer *ProviderConfigSyncer
|
||||||
|
|
||||||
|
keySetSyncMutex sync.RWMutex
|
||||||
|
lastKeySetSync time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Healthy() error {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
cfg := c.providerConfig.Get()
|
||||||
|
|
||||||
|
if cfg.Empty() {
|
||||||
|
return errors.New("oidc client provider config empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.ExpiresAt.IsZero() && cfg.ExpiresAt.Before(now) {
|
||||||
|
return errors.New("oidc client provider config expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) OAuthClient() (*oauth2.Client, error) {
|
||||||
|
cfg := c.providerConfig.Get()
|
||||||
|
authMethod, err := chooseAuthMethod(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ocfg := oauth2.Config{
|
||||||
|
Credentials: oauth2.ClientCredentials(c.credentials),
|
||||||
|
RedirectURL: c.redirectURL,
|
||||||
|
AuthURL: cfg.AuthEndpoint.String(),
|
||||||
|
TokenURL: cfg.TokenEndpoint.String(),
|
||||||
|
Scope: c.scope,
|
||||||
|
AuthMethod: authMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
return oauth2.NewClient(c.httpClient, ocfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func chooseAuthMethod(cfg ProviderConfig) (string, error) {
|
||||||
|
if len(cfg.TokenEndpointAuthMethodsSupported) == 0 {
|
||||||
|
return oauth2.AuthMethodClientSecretBasic, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, authMethod := range cfg.TokenEndpointAuthMethodsSupported {
|
||||||
|
if _, ok := supportedAuthMethods[authMethod]; ok {
|
||||||
|
return authMethod, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("no supported auth methods")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncProviderConfig starts the provider config syncer
|
||||||
|
func (c *Client) SyncProviderConfig(discoveryURL string) chan struct{} {
|
||||||
|
r := NewHTTPProviderConfigGetter(c.httpClient, discoveryURL)
|
||||||
|
s := NewProviderConfigSyncer(r, c.providerConfig)
|
||||||
|
stop := s.Run()
|
||||||
|
s.WaitUntilInitialSync()
|
||||||
|
return stop
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) maybeSyncKeys() error {
|
||||||
|
tooSoon := func() bool {
|
||||||
|
return time.Now().UTC().Before(c.lastKeySetSync.Add(keySyncWindow))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore request to sync keys if a sync operation has been
|
||||||
|
// attempted too recently
|
||||||
|
if tooSoon() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.keySetSyncMutex.Lock()
|
||||||
|
defer c.keySetSyncMutex.Unlock()
|
||||||
|
|
||||||
|
// check again, as another goroutine may have been holding
|
||||||
|
// the lock while updating the keys
|
||||||
|
if tooSoon() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := c.providerConfig.Get()
|
||||||
|
r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint.String())
|
||||||
|
w := &clientKeyRepo{client: c}
|
||||||
|
_, err := key.Sync(r, w)
|
||||||
|
c.lastKeySetSync = time.Now().UTC()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientKeyRepo struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientKeyRepo) Set(ks key.KeySet) error {
|
||||||
|
pks, ok := ks.(*key.PublicKeySet)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unable to cast to PublicKey")
|
||||||
|
}
|
||||||
|
r.client.keySet = *pks
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ClientCredsToken(scope []string) (jose.JWT, error) {
|
||||||
|
cfg := c.providerConfig.Get()
|
||||||
|
|
||||||
|
if !cfg.SupportsGrantType(oauth2.GrantTypeClientCreds) {
|
||||||
|
return jose.JWT{}, fmt.Errorf("%v grant type is not supported", oauth2.GrantTypeClientCreds)
|
||||||
|
}
|
||||||
|
|
||||||
|
oac, err := c.OAuthClient()
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := oac.ClientCredsToken(scope)
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt, err := jose.ParseJWT(t.IDToken)
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwt, c.VerifyJWT(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeAuthCode exchanges an OAuth2 auth code for an OIDC JWT ID token.
|
||||||
|
func (c *Client) ExchangeAuthCode(code string) (jose.JWT, error) {
|
||||||
|
oac, err := c.OAuthClient()
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := oac.RequestToken(oauth2.GrantTypeAuthCode, code)
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt, err := jose.ParseJWT(t.IDToken)
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwt, c.VerifyJWT(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshToken uses a refresh token to exchange for a new OIDC JWT ID Token.
|
||||||
|
func (c *Client) RefreshToken(refreshToken string) (jose.JWT, error) {
|
||||||
|
oac, err := c.OAuthClient()
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt, err := jose.ParseJWT(t.IDToken)
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwt, c.VerifyJWT(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) VerifyJWT(jwt jose.JWT) error {
|
||||||
|
var keysFunc func() []key.PublicKey
|
||||||
|
if kID, ok := jwt.KeyID(); ok {
|
||||||
|
keysFunc = c.keysFuncWithID(kID)
|
||||||
|
} else {
|
||||||
|
keysFunc = c.keysFuncAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
v := NewJWTVerifier(
|
||||||
|
c.providerConfig.Get().Issuer.String(),
|
||||||
|
c.credentials.ID,
|
||||||
|
c.maybeSyncKeys, keysFunc)
|
||||||
|
|
||||||
|
return v.Verify(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keysFuncWithID returns a function that retrieves at most unexpired
|
||||||
|
// public key from the Client that matches the provided ID
|
||||||
|
func (c *Client) keysFuncWithID(kID string) func() []key.PublicKey {
|
||||||
|
return func() []key.PublicKey {
|
||||||
|
c.keySetSyncMutex.RLock()
|
||||||
|
defer c.keySetSyncMutex.RUnlock()
|
||||||
|
|
||||||
|
if c.keySet.ExpiresAt().Before(time.Now()) {
|
||||||
|
return []key.PublicKey{}
|
||||||
|
}
|
||||||
|
|
||||||
|
k := c.keySet.Key(kID)
|
||||||
|
if k == nil {
|
||||||
|
return []key.PublicKey{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []key.PublicKey{*k}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keysFuncAll returns a function that retrieves all unexpired public
|
||||||
|
// keys from the Client
|
||||||
|
func (c *Client) keysFuncAll() func() []key.PublicKey {
|
||||||
|
return func() []key.PublicKey {
|
||||||
|
c.keySetSyncMutex.RLock()
|
||||||
|
defer c.keySetSyncMutex.RUnlock()
|
||||||
|
|
||||||
|
if c.keySet.ExpiresAt().Before(time.Now()) {
|
||||||
|
return []key.PublicKey{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.keySet.Keys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerConfigRepo struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
config ProviderConfig // do not access directly, use Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProviderConfigRepo(pc ProviderConfig) *providerConfigRepo {
|
||||||
|
return &providerConfigRepo{sync.RWMutex{}, pc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error to implement ProviderConfigSetter
|
||||||
|
func (r *providerConfigRepo) Set(cfg ProviderConfig) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.config = cfg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *providerConfigRepo) Get() ProviderConfig {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
return r.config
|
||||||
|
}
|
81
vendor/github.com/coreos/go-oidc/oidc/client_race_test.go
generated
vendored
Normal file
81
vendor/github.com/coreos/go-oidc/oidc/client_race_test.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// This file contains tests which depend on the race detector being enabled.
|
||||||
|
// +build race
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testProvider struct {
|
||||||
|
baseURL *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testProvider) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != discoveryConfigPath {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := ProviderConfig{
|
||||||
|
Issuer: p.baseURL,
|
||||||
|
ExpiresAt: time.Now().Add(time.Second),
|
||||||
|
}
|
||||||
|
cfg = fillRequiredProviderFields(cfg)
|
||||||
|
json.NewEncoder(w).Encode(&cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test fails by triggering the race detector, not by calling t.Error or t.Fatal.
|
||||||
|
func TestProviderSyncRace(t *testing.T) {
|
||||||
|
|
||||||
|
prov := &testProvider{}
|
||||||
|
|
||||||
|
s := httptest.NewServer(prov)
|
||||||
|
defer s.Close()
|
||||||
|
u, err := url.Parse(s.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
prov.baseURL = u
|
||||||
|
|
||||||
|
prevValue := minimumProviderConfigSyncInterval
|
||||||
|
defer func() { minimumProviderConfigSyncInterval = prevValue }()
|
||||||
|
|
||||||
|
// Reduce the sync interval to increase the write frequencey.
|
||||||
|
minimumProviderConfigSyncInterval = 5 * time.Millisecond
|
||||||
|
|
||||||
|
cliCfg := ClientConfig{
|
||||||
|
HTTPClient: http.DefaultClient,
|
||||||
|
}
|
||||||
|
cli, err := NewClient(cliCfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cli.providerConfig.Get().Empty() {
|
||||||
|
t.Errorf("want c.ProviderConfig == nil, got c.ProviderConfig=%#v")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncProviderConfig beings a goroutine which writes to the client's provider config.
|
||||||
|
c := cli.SyncProviderConfig(s.URL)
|
||||||
|
if cli.providerConfig.Get().Empty() {
|
||||||
|
t.Errorf("want c.ProviderConfig != nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// stop the background process
|
||||||
|
c <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
// Creating an OAuth client reads from the provider config.
|
||||||
|
cli.OAuthClient()
|
||||||
|
}
|
||||||
|
}
|
654
vendor/github.com/coreos/go-oidc/oidc/client_test.go
generated
vendored
Normal file
654
vendor/github.com/coreos/go-oidc/oidc/client_test.go
generated
vendored
Normal file
|
@ -0,0 +1,654 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/mail"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
"github.com/coreos/go-oidc/key"
|
||||||
|
"github.com/coreos/go-oidc/oauth2"
|
||||||
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewClientScopeDefault(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
c ClientConfig
|
||||||
|
e []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// No scope
|
||||||
|
c: ClientConfig{RedirectURL: "http://example.com/redirect"},
|
||||||
|
e: DefaultScope,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nil scope
|
||||||
|
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: nil},
|
||||||
|
e: DefaultScope,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Empty scope
|
||||||
|
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{}},
|
||||||
|
e: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Custom scope equal to default
|
||||||
|
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{"openid", "email", "profile"}},
|
||||||
|
e: DefaultScope,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Custom scope not including defaults
|
||||||
|
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{"foo", "bar"}},
|
||||||
|
e: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Custom scopes overlapping with defaults
|
||||||
|
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{"openid", "foo"}},
|
||||||
|
e: []string{"openid", "foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
c, err := NewClient(tt.c)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error from NewClient: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.e, c.scope) {
|
||||||
|
t.Errorf("case %d: want: %v, got: %v", i, tt.e, c.scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthy(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
p ProviderConfig
|
||||||
|
h bool
|
||||||
|
}{
|
||||||
|
// all ok
|
||||||
|
{
|
||||||
|
p: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
|
||||||
|
ExpiresAt: now.Add(time.Hour),
|
||||||
|
},
|
||||||
|
h: true,
|
||||||
|
},
|
||||||
|
// zero-value ProviderConfig.ExpiresAt
|
||||||
|
{
|
||||||
|
p: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
|
||||||
|
},
|
||||||
|
h: true,
|
||||||
|
},
|
||||||
|
// expired ProviderConfig
|
||||||
|
{
|
||||||
|
p: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
|
||||||
|
ExpiresAt: now.Add(time.Hour * -1),
|
||||||
|
},
|
||||||
|
h: false,
|
||||||
|
},
|
||||||
|
// empty ProviderConfig
|
||||||
|
{
|
||||||
|
p: ProviderConfig{},
|
||||||
|
h: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
c := &Client{providerConfig: newProviderConfigRepo(tt.p)}
|
||||||
|
err := c.Healthy()
|
||||||
|
want := tt.h
|
||||||
|
got := (err == nil)
|
||||||
|
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("case %d: want: healthy=%v, got: healhty=%v, err: %v", i, want, got, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientKeysFuncAll(t *testing.T) {
|
||||||
|
priv1, err := key.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate private key, error=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv2, err := key.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate private key, error=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
future := now.Add(time.Hour)
|
||||||
|
past := now.Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
keySet *key.PublicKeySet
|
||||||
|
want []key.PublicKey
|
||||||
|
}{
|
||||||
|
// two keys, non-expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, future),
|
||||||
|
want: []key.PublicKey{*key.NewPublicKey(priv2.JWK()), *key.NewPublicKey(priv1.JWK())},
|
||||||
|
},
|
||||||
|
|
||||||
|
// no keys, non-expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{}, future),
|
||||||
|
want: []key.PublicKey{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// two keys, expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, past),
|
||||||
|
want: []key.PublicKey{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// no keys, expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{}, past),
|
||||||
|
want: []key.PublicKey{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
var c Client
|
||||||
|
c.keySet = *tt.keySet
|
||||||
|
keysFunc := c.keysFuncAll()
|
||||||
|
got := keysFunc()
|
||||||
|
if !reflect.DeepEqual(tt.want, got) {
|
||||||
|
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientKeysFuncWithID(t *testing.T) {
|
||||||
|
priv1, err := key.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate private key, error=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv2, err := key.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate private key, error=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
future := now.Add(time.Hour)
|
||||||
|
past := now.Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
keySet *key.PublicKeySet
|
||||||
|
argID string
|
||||||
|
want []key.PublicKey
|
||||||
|
}{
|
||||||
|
// two keys, match, non-expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, future),
|
||||||
|
argID: priv2.ID(),
|
||||||
|
want: []key.PublicKey{*key.NewPublicKey(priv2.JWK())},
|
||||||
|
},
|
||||||
|
|
||||||
|
// two keys, no match, non-expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, future),
|
||||||
|
argID: "XXX",
|
||||||
|
want: []key.PublicKey{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// no keys, no match, non-expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{}, future),
|
||||||
|
argID: priv2.ID(),
|
||||||
|
want: []key.PublicKey{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// two keys, match, expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, past),
|
||||||
|
argID: priv2.ID(),
|
||||||
|
want: []key.PublicKey{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// no keys, no match, expired set
|
||||||
|
{
|
||||||
|
keySet: key.NewPublicKeySet([]jose.JWK{}, past),
|
||||||
|
argID: priv2.ID(),
|
||||||
|
want: []key.PublicKey{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
var c Client
|
||||||
|
c.keySet = *tt.keySet
|
||||||
|
keysFunc := c.keysFuncWithID(tt.argID)
|
||||||
|
got := keysFunc()
|
||||||
|
if !reflect.DeepEqual(tt.want, got) {
|
||||||
|
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientMetadataValid(t *testing.T) {
|
||||||
|
tests := []ClientMetadata{
|
||||||
|
// one RedirectURL
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// one RedirectURL w/ nonempty path
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com", Path: "/foo"}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// two RedirectURIs
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
url.URL{Scheme: "http", Host: "foo.example.com"},
|
||||||
|
url.URL{Scheme: "http", Host: "bar.example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
if err := tt.Valid(); err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientMetadataInvalid(t *testing.T) {
|
||||||
|
tests := []ClientMetadata{
|
||||||
|
// nil RedirectURls slice
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// empty RedirectURIs slice
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// empty url.URL
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// empty url.URL following OK item
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}, url.URL{}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// url.URL with empty Host
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: ""}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// url.URL with empty Scheme
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "", Host: "example.com"}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// url.URL with non-HTTP(S) Scheme
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "tcp", Host: "127.0.0.1"}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// EncryptionEnc without EncryptionAlg
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}},
|
||||||
|
IDTokenResponseOptions: JWAOptions{
|
||||||
|
EncryptionEnc: "A128CBC-HS256",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// List of URIs with one empty element
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}},
|
||||||
|
RequestURIs: []url.URL{
|
||||||
|
url.URL{Scheme: "http", Host: "example.com"},
|
||||||
|
url.URL{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
if err := tt.Valid(); err == nil {
|
||||||
|
t.Errorf("case %d: expected non-nil error", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChooseAuthMethod(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
supported []string
|
||||||
|
chosen string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
supported: []string{},
|
||||||
|
chosen: oauth2.AuthMethodClientSecretBasic,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
supported: []string{oauth2.AuthMethodClientSecretBasic},
|
||||||
|
chosen: oauth2.AuthMethodClientSecretBasic,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
supported: []string{oauth2.AuthMethodClientSecretPost},
|
||||||
|
chosen: oauth2.AuthMethodClientSecretPost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
supported: []string{oauth2.AuthMethodClientSecretPost, oauth2.AuthMethodClientSecretBasic},
|
||||||
|
chosen: oauth2.AuthMethodClientSecretPost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
supported: []string{oauth2.AuthMethodClientSecretBasic, oauth2.AuthMethodClientSecretPost},
|
||||||
|
chosen: oauth2.AuthMethodClientSecretBasic,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
supported: []string{oauth2.AuthMethodClientSecretJWT, oauth2.AuthMethodClientSecretPost},
|
||||||
|
chosen: oauth2.AuthMethodClientSecretPost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
supported: []string{oauth2.AuthMethodClientSecretJWT},
|
||||||
|
chosen: "",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
cfg := ProviderConfig{
|
||||||
|
TokenEndpointAuthMethodsSupported: tt.supported,
|
||||||
|
}
|
||||||
|
got, err := chooseAuthMethod(cfg)
|
||||||
|
if tt.err {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: expected non-nil err", i)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != tt.chosen {
|
||||||
|
t.Errorf("case %d: want=%q, got=%q", i, tt.chosen, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientMetadataUnmarshal(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
data string
|
||||||
|
want ClientMetadata
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`{"redirect_uris":["https://example.com"]}`,
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// redirect_uris required
|
||||||
|
`{}`,
|
||||||
|
ClientMetadata{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// must have at least one redirect_uris
|
||||||
|
`{"redirect_uris":[]}`,
|
||||||
|
ClientMetadata{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`{"redirect_uris":["https://example.com"],"contacts":["Ms. Foo <foo@example.com>"]}`,
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com"},
|
||||||
|
},
|
||||||
|
Contacts: []mail.Address{
|
||||||
|
{Name: "Ms. Foo", Address: "foo@example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// invalid URI provided for field
|
||||||
|
`{"redirect_uris":["https://example.com"],"logo_uri":"not a valid uri"}`,
|
||||||
|
ClientMetadata{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// logo_uri can't be a list
|
||||||
|
`{"redirect_uris":["https://example.com"],"logo_uri":["https://example.com/logo"]}`,
|
||||||
|
ClientMetadata{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`{
|
||||||
|
"redirect_uris":["https://example.com"],
|
||||||
|
"userinfo_encrypted_response_alg":"RSA1_5",
|
||||||
|
"userinfo_encrypted_response_enc":"A128CBC-HS256",
|
||||||
|
"contacts": [
|
||||||
|
"jane doe <jane.doe@example.com>", "john doe <john.doe@example.com>"
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com"},
|
||||||
|
},
|
||||||
|
UserInfoResponseOptions: JWAOptions{
|
||||||
|
EncryptionAlg: "RSA1_5",
|
||||||
|
EncryptionEnc: "A128CBC-HS256",
|
||||||
|
},
|
||||||
|
Contacts: []mail.Address{
|
||||||
|
{Name: "jane doe", Address: "jane.doe@example.com"},
|
||||||
|
{Name: "john doe", Address: "john.doe@example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// If encrypted_response_enc is provided encrypted_response_alg must also be.
|
||||||
|
`{
|
||||||
|
"redirect_uris":["https://example.com"],
|
||||||
|
"userinfo_encrypted_response_enc":"A128CBC-HS256"
|
||||||
|
}`,
|
||||||
|
ClientMetadata{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
var got ClientMetadata
|
||||||
|
if err := got.UnmarshalJSON([]byte(tt.data)); err != nil {
|
||||||
|
if !tt.wantErr {
|
||||||
|
t.Errorf("case %d: unmarshal failed: %v", i, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
t.Errorf("case %d: expected unmarshal to produce error", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := pretty.Compare(tt.want, got); diff != "" {
|
||||||
|
t.Errorf("case %d: results not equal: %s", i, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientMetadataMarshal(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
metadata ClientMetadata
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com", Path: "/callback"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`{"redirect_uris":["https://example.com/callback"]}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com", Path: "/callback"},
|
||||||
|
},
|
||||||
|
RequestObjectOptions: JWAOptions{
|
||||||
|
EncryptionAlg: "RSA1_5",
|
||||||
|
EncryptionEnc: "A128CBC-HS256",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`{"redirect_uris":["https://example.com/callback"],"request_object_encryption_alg":"RSA1_5","request_object_encryption_enc":"A128CBC-HS256"}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got, err := json.Marshal(&tt.metadata)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: failed to marshal metadata: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(got) != tt.want {
|
||||||
|
t.Errorf("case %d: marshaled string did not match expected string", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientMetadataMarshalRoundTrip(t *testing.T) {
|
||||||
|
tests := []ClientMetadata{
|
||||||
|
{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com", Path: "/callback"},
|
||||||
|
},
|
||||||
|
LogoURI: &url.URL{Scheme: "https", Host: "example.com", Path: "/logo"},
|
||||||
|
RequestObjectOptions: JWAOptions{
|
||||||
|
EncryptionAlg: "RSA1_5",
|
||||||
|
EncryptionEnc: "A128CBC-HS256",
|
||||||
|
},
|
||||||
|
ApplicationType: "native",
|
||||||
|
TokenEndpointAuthMethod: "client_secret_basic",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, want := range tests {
|
||||||
|
data, err := json.Marshal(&want)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: failed to marshal metadata: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var got ClientMetadata
|
||||||
|
if err := json.Unmarshal(data, &got); err != nil {
|
||||||
|
t.Errorf("case %d: failed to unmarshal metadata: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if diff := pretty.Compare(want, got); diff != "" {
|
||||||
|
t.Errorf("case %d: struct did not survive a marshaling round trip: %s", i, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientRegistrationResponseUnmarshal(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
data string
|
||||||
|
want ClientRegistrationResponse
|
||||||
|
wantErr bool
|
||||||
|
secretExpires bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`{
|
||||||
|
"client_id":"foo",
|
||||||
|
"client_secret":"bar",
|
||||||
|
"client_secret_expires_at": 1577858400,
|
||||||
|
"redirect_uris":[
|
||||||
|
"https://client.example.org/callback",
|
||||||
|
"https://client.example.org/callback2"
|
||||||
|
],
|
||||||
|
"client_name":"my_example"
|
||||||
|
}`,
|
||||||
|
ClientRegistrationResponse{
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
ClientSecretExpiresAt: time.Unix(1577858400, 0),
|
||||||
|
ClientMetadata: ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "client.example.org", Path: "/callback"},
|
||||||
|
{Scheme: "https", Host: "client.example.org", Path: "/callback2"},
|
||||||
|
},
|
||||||
|
ClientName: "my_example",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`{
|
||||||
|
"client_id":"foo",
|
||||||
|
"client_secret_expires_at": 0,
|
||||||
|
"redirect_uris":[
|
||||||
|
"https://client.example.org/callback",
|
||||||
|
"https://client.example.org/callback2"
|
||||||
|
],
|
||||||
|
"client_name":"my_example"
|
||||||
|
}`,
|
||||||
|
ClientRegistrationResponse{
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientMetadata: ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "client.example.org", Path: "/callback"},
|
||||||
|
{Scheme: "https", Host: "client.example.org", Path: "/callback2"},
|
||||||
|
},
|
||||||
|
ClientName: "my_example",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// no client id
|
||||||
|
`{
|
||||||
|
"client_secret_expires_at": 0,
|
||||||
|
"redirect_uris":[
|
||||||
|
"https://client.example.org/callback",
|
||||||
|
"https://client.example.org/callback2"
|
||||||
|
],
|
||||||
|
"client_name":"my_example"
|
||||||
|
}`,
|
||||||
|
ClientRegistrationResponse{},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
var got ClientRegistrationResponse
|
||||||
|
if err := json.Unmarshal([]byte(tt.data), &got); err != nil {
|
||||||
|
if !tt.wantErr {
|
||||||
|
t.Errorf("case %d: unmarshal failed: %v", i, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
t.Errorf("case %d: expected unmarshal to produce error", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := pretty.Compare(tt.want, got); diff != "" {
|
||||||
|
t.Errorf("case %d: results not equal: %s", i, diff)
|
||||||
|
}
|
||||||
|
if tt.secretExpires && got.ClientSecretExpiresAt.IsZero() {
|
||||||
|
t.Errorf("case %d: expected client_secret to expire, but it doesn't", i)
|
||||||
|
} else if !tt.secretExpires && !got.ClientSecretExpiresAt.IsZero() {
|
||||||
|
t.Errorf("case %d: expected client_secret to not expire, but it does", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
vendor/github.com/coreos/go-oidc/oidc/doc.go
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/oidc/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package oidc is DEPRECATED. Use github.com/coreos/go-oidc instead.
|
||||||
|
package oidc
|
44
vendor/github.com/coreos/go-oidc/oidc/identity.go
generated
vendored
Normal file
44
vendor/github.com/coreos/go-oidc/oidc/identity.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Identity struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func IdentityFromClaims(claims jose.Claims) (*Identity, error) {
|
||||||
|
if claims == nil {
|
||||||
|
return nil, errors.New("nil claim set")
|
||||||
|
}
|
||||||
|
|
||||||
|
var ident Identity
|
||||||
|
var err error
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if ident.ID, ok, err = claims.StringClaim("sub"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !ok {
|
||||||
|
return nil, errors.New("missing required claim: sub")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ident.Email, _, err = claims.StringClaim("email"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exp, ok, err := claims.TimeClaim("exp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if ok {
|
||||||
|
ident.ExpiresAt = exp
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ident, nil
|
||||||
|
}
|
113
vendor/github.com/coreos/go-oidc/oidc/identity_test.go
generated
vendored
Normal file
113
vendor/github.com/coreos/go-oidc/oidc/identity_test.go
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIdentityFromClaims(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
claims jose.Claims
|
||||||
|
want Identity
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"sub": "123850281",
|
||||||
|
"name": "Elroy",
|
||||||
|
"email": "elroy@example.com",
|
||||||
|
"exp": float64(1.416935146e+09),
|
||||||
|
},
|
||||||
|
want: Identity{
|
||||||
|
ID: "123850281",
|
||||||
|
Name: "",
|
||||||
|
Email: "elroy@example.com",
|
||||||
|
ExpiresAt: time.Date(2014, time.November, 25, 17, 05, 46, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"sub": "123850281",
|
||||||
|
"name": "Elroy",
|
||||||
|
"exp": float64(1.416935146e+09),
|
||||||
|
},
|
||||||
|
want: Identity{
|
||||||
|
ID: "123850281",
|
||||||
|
Name: "",
|
||||||
|
Email: "",
|
||||||
|
ExpiresAt: time.Date(2014, time.November, 25, 17, 05, 46, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"sub": "123850281",
|
||||||
|
"name": "Elroy",
|
||||||
|
"email": "elroy@example.com",
|
||||||
|
"exp": int64(1416935146),
|
||||||
|
},
|
||||||
|
want: Identity{
|
||||||
|
ID: "123850281",
|
||||||
|
Name: "",
|
||||||
|
Email: "elroy@example.com",
|
||||||
|
ExpiresAt: time.Date(2014, time.November, 25, 17, 05, 46, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"sub": "123850281",
|
||||||
|
"name": "Elroy",
|
||||||
|
"email": "elroy@example.com",
|
||||||
|
},
|
||||||
|
want: Identity{
|
||||||
|
ID: "123850281",
|
||||||
|
Name: "",
|
||||||
|
Email: "elroy@example.com",
|
||||||
|
ExpiresAt: time.Time{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got, err := IdentityFromClaims(tt.claims)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.want, *got) {
|
||||||
|
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, *got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentityFromClaimsFail(t *testing.T) {
|
||||||
|
tests := []jose.Claims{
|
||||||
|
// sub incorrect type
|
||||||
|
jose.Claims{
|
||||||
|
"sub": 123,
|
||||||
|
"name": "foo",
|
||||||
|
"email": "elroy@example.com",
|
||||||
|
},
|
||||||
|
// email incorrect type
|
||||||
|
jose.Claims{
|
||||||
|
"sub": "123850281",
|
||||||
|
"name": "Elroy",
|
||||||
|
"email": false,
|
||||||
|
},
|
||||||
|
// exp incorrect type
|
||||||
|
jose.Claims{
|
||||||
|
"sub": "123850281",
|
||||||
|
"name": "Elroy",
|
||||||
|
"email": "elroy@example.com",
|
||||||
|
"exp": "2014-11-25 18:05:46 +0000 UTC",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
_, err := IdentityFromClaims(tt)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: expected non-nil error", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
vendor/github.com/coreos/go-oidc/oidc/interface.go
generated
vendored
Normal file
3
vendor/github.com/coreos/go-oidc/oidc/interface.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
type LoginFunc func(ident Identity, sessionKey string) (redirectURL string, err error)
|
67
vendor/github.com/coreos/go-oidc/oidc/key.go
generated
vendored
Executable file
67
vendor/github.com/coreos/go-oidc/oidc/key.go
generated
vendored
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
phttp "github.com/coreos/go-oidc/http"
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
"github.com/coreos/go-oidc/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultPublicKeySetTTL is the default TTL set on the PublicKeySet if no
|
||||||
|
// Cache-Control header is provided by the JWK Set document endpoint.
|
||||||
|
const DefaultPublicKeySetTTL = 24 * time.Hour
|
||||||
|
|
||||||
|
// NewRemotePublicKeyRepo is responsible for fetching the JWK Set document.
|
||||||
|
func NewRemotePublicKeyRepo(hc phttp.Client, ep string) *remotePublicKeyRepo {
|
||||||
|
return &remotePublicKeyRepo{hc: hc, ep: ep}
|
||||||
|
}
|
||||||
|
|
||||||
|
type remotePublicKeyRepo struct {
|
||||||
|
hc phttp.Client
|
||||||
|
ep string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a PublicKeySet fetched from the JWK Set document endpoint. A TTL
|
||||||
|
// is set on the Key Set to avoid it having to be re-retrieved for every
|
||||||
|
// encryption event. This TTL is typically controlled by the endpoint returning
|
||||||
|
// a Cache-Control header, but defaults to 24 hours if no Cache-Control header
|
||||||
|
// is found.
|
||||||
|
func (r *remotePublicKeyRepo) Get() (key.KeySet, error) {
|
||||||
|
req, err := http.NewRequest("GET", r.ep, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := r.hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var d struct {
|
||||||
|
Keys []jose.JWK `json:"keys"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d.Keys) == 0 {
|
||||||
|
return nil, errors.New("zero keys in response")
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl, ok, err := phttp.Cacheable(resp.Header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ttl = DefaultPublicKeySetTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := time.Now().UTC().Add(ttl)
|
||||||
|
ks := key.NewPublicKeySet(d.Keys, exp)
|
||||||
|
return ks, nil
|
||||||
|
}
|
690
vendor/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
Normal file
690
vendor/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
Normal file
|
@ -0,0 +1,690 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/timeutil"
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
|
||||||
|
phttp "github.com/coreos/go-oidc/http"
|
||||||
|
"github.com/coreos/go-oidc/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Subject Identifier types defined by the OIDC spec. Specifies if the provider
|
||||||
|
// should provide the same sub claim value to all clients (public) or a unique
|
||||||
|
// value for each client (pairwise).
|
||||||
|
//
|
||||||
|
// See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
|
||||||
|
SubjectTypePublic = "public"
|
||||||
|
SubjectTypePairwise = "pairwise"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Default values for omitted provider config fields.
|
||||||
|
//
|
||||||
|
// Use ProviderConfig's Defaults method to fill a provider config with these values.
|
||||||
|
DefaultGrantTypesSupported = []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeImplicit}
|
||||||
|
DefaultResponseModesSupported = []string{"query", "fragment"}
|
||||||
|
DefaultTokenEndpointAuthMethodsSupported = []string{oauth2.AuthMethodClientSecretBasic}
|
||||||
|
DefaultClaimTypesSupported = []string{"normal"}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaximumProviderConfigSyncInterval = 24 * time.Hour
|
||||||
|
MinimumProviderConfigSyncInterval = time.Minute
|
||||||
|
|
||||||
|
discoveryConfigPath = "/.well-known/openid-configuration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// internally configurable for tests
|
||||||
|
var minimumProviderConfigSyncInterval = MinimumProviderConfigSyncInterval
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Ensure ProviderConfig satisfies these interfaces.
|
||||||
|
_ json.Marshaler = &ProviderConfig{}
|
||||||
|
_ json.Unmarshaler = &ProviderConfig{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderConfig represents the OpenID Provider Metadata specifying what
|
||||||
|
// configurations a provider supports.
|
||||||
|
//
|
||||||
|
// See: http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||||
|
type ProviderConfig struct {
|
||||||
|
Issuer *url.URL // Required
|
||||||
|
AuthEndpoint *url.URL // Required
|
||||||
|
TokenEndpoint *url.URL // Required if grant types other than "implicit" are supported
|
||||||
|
UserInfoEndpoint *url.URL
|
||||||
|
KeysEndpoint *url.URL // Required
|
||||||
|
RegistrationEndpoint *url.URL
|
||||||
|
EndSessionEndpoint *url.URL
|
||||||
|
CheckSessionIFrame *url.URL
|
||||||
|
|
||||||
|
// Servers MAY choose not to advertise some supported scope values even when this
|
||||||
|
// parameter is used, although those defined in OpenID Core SHOULD be listed, if supported.
|
||||||
|
ScopesSupported []string
|
||||||
|
// OAuth2.0 response types supported.
|
||||||
|
ResponseTypesSupported []string // Required
|
||||||
|
// OAuth2.0 response modes supported.
|
||||||
|
//
|
||||||
|
// If omitted, defaults to DefaultResponseModesSupported.
|
||||||
|
ResponseModesSupported []string
|
||||||
|
// OAuth2.0 grant types supported.
|
||||||
|
//
|
||||||
|
// If omitted, defaults to DefaultGrantTypesSupported.
|
||||||
|
GrantTypesSupported []string
|
||||||
|
ACRValuesSupported []string
|
||||||
|
// SubjectTypesSupported specifies strategies for providing values for the sub claim.
|
||||||
|
SubjectTypesSupported []string // Required
|
||||||
|
|
||||||
|
// JWA signing and encryption algorith values supported for ID tokens.
|
||||||
|
IDTokenSigningAlgValues []string // Required
|
||||||
|
IDTokenEncryptionAlgValues []string
|
||||||
|
IDTokenEncryptionEncValues []string
|
||||||
|
|
||||||
|
// JWA signing and encryption algorith values supported for user info responses.
|
||||||
|
UserInfoSigningAlgValues []string
|
||||||
|
UserInfoEncryptionAlgValues []string
|
||||||
|
UserInfoEncryptionEncValues []string
|
||||||
|
|
||||||
|
// JWA signing and encryption algorith values supported for request objects.
|
||||||
|
ReqObjSigningAlgValues []string
|
||||||
|
ReqObjEncryptionAlgValues []string
|
||||||
|
ReqObjEncryptionEncValues []string
|
||||||
|
|
||||||
|
TokenEndpointAuthMethodsSupported []string
|
||||||
|
TokenEndpointAuthSigningAlgValuesSupported []string
|
||||||
|
DisplayValuesSupported []string
|
||||||
|
ClaimTypesSupported []string
|
||||||
|
ClaimsSupported []string
|
||||||
|
ServiceDocs *url.URL
|
||||||
|
ClaimsLocalsSupported []string
|
||||||
|
UILocalsSupported []string
|
||||||
|
ClaimsParameterSupported bool
|
||||||
|
RequestParameterSupported bool
|
||||||
|
RequestURIParamaterSupported bool
|
||||||
|
RequireRequestURIRegistration bool
|
||||||
|
|
||||||
|
Policy *url.URL
|
||||||
|
TermsOfService *url.URL
|
||||||
|
|
||||||
|
// Not part of the OpenID Provider Metadata
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults returns a shallow copy of ProviderConfig with default
|
||||||
|
// values replacing omitted fields.
|
||||||
|
//
|
||||||
|
// var cfg oidc.ProviderConfig
|
||||||
|
// // Fill provider config with default values for omitted fields.
|
||||||
|
// cfg = cfg.Defaults()
|
||||||
|
//
|
||||||
|
func (p ProviderConfig) Defaults() ProviderConfig {
|
||||||
|
setDefault := func(val *[]string, defaultVal []string) {
|
||||||
|
if len(*val) == 0 {
|
||||||
|
*val = defaultVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setDefault(&p.GrantTypesSupported, DefaultGrantTypesSupported)
|
||||||
|
setDefault(&p.ResponseModesSupported, DefaultResponseModesSupported)
|
||||||
|
setDefault(&p.TokenEndpointAuthMethodsSupported, DefaultTokenEndpointAuthMethodsSupported)
|
||||||
|
setDefault(&p.ClaimTypesSupported, DefaultClaimTypesSupported)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProviderConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
e := p.toEncodableStruct()
|
||||||
|
return json.Marshal(&e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProviderConfig) UnmarshalJSON(data []byte) error {
|
||||||
|
var e encodableProviderConfig
|
||||||
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conf, err := e.toStruct()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := conf.Valid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = conf
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodableProviderConfig struct {
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
AuthEndpoint string `json:"authorization_endpoint"`
|
||||||
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||||
|
KeysEndpoint string `json:"jwks_uri"`
|
||||||
|
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
|
||||||
|
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
||||||
|
CheckSessionIFrame string `json:"check_session_iframe,omitempty"`
|
||||||
|
|
||||||
|
// Use 'omitempty' for all slices as per OIDC spec:
|
||||||
|
// "Claims that return multiple values are represented as JSON arrays.
|
||||||
|
// Claims with zero elements MUST be omitted from the response."
|
||||||
|
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
|
||||||
|
|
||||||
|
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||||
|
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||||
|
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
|
||||||
|
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||||
|
ACRValuesSupported []string `json:"acr_values_supported,omitempty"`
|
||||||
|
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||||
|
|
||||||
|
IDTokenSigningAlgValues []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||||
|
IDTokenEncryptionAlgValues []string `json:"id_token_encryption_alg_values_supported,omitempty"`
|
||||||
|
IDTokenEncryptionEncValues []string `json:"id_token_encryption_enc_values_supported,omitempty"`
|
||||||
|
UserInfoSigningAlgValues []string `json:"userinfo_signing_alg_values_supported,omitempty"`
|
||||||
|
UserInfoEncryptionAlgValues []string `json:"userinfo_encryption_alg_values_supported,omitempty"`
|
||||||
|
UserInfoEncryptionEncValues []string `json:"userinfo_encryption_enc_values_supported,omitempty"`
|
||||||
|
ReqObjSigningAlgValues []string `json:"request_object_signing_alg_values_supported,omitempty"`
|
||||||
|
ReqObjEncryptionAlgValues []string `json:"request_object_encryption_alg_values_supported,omitempty"`
|
||||||
|
ReqObjEncryptionEncValues []string `json:"request_object_encryption_enc_values_supported,omitempty"`
|
||||||
|
|
||||||
|
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
|
||||||
|
TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
|
||||||
|
|
||||||
|
DisplayValuesSupported []string `json:"display_values_supported,omitempty"`
|
||||||
|
ClaimTypesSupported []string `json:"claim_types_supported,omitempty"`
|
||||||
|
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||||
|
ServiceDocs string `json:"service_documentation,omitempty"`
|
||||||
|
ClaimsLocalsSupported []string `json:"claims_locales_supported,omitempty"`
|
||||||
|
UILocalsSupported []string `json:"ui_locales_supported,omitempty"`
|
||||||
|
ClaimsParameterSupported bool `json:"claims_parameter_supported,omitempty"`
|
||||||
|
RequestParameterSupported bool `json:"request_parameter_supported,omitempty"`
|
||||||
|
RequestURIParamaterSupported bool `json:"request_uri_parameter_supported,omitempty"`
|
||||||
|
RequireRequestURIRegistration bool `json:"require_request_uri_registration,omitempty"`
|
||||||
|
|
||||||
|
Policy string `json:"op_policy_uri,omitempty"`
|
||||||
|
TermsOfService string `json:"op_tos_uri,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg ProviderConfig) toEncodableStruct() encodableProviderConfig {
|
||||||
|
return encodableProviderConfig{
|
||||||
|
Issuer: uriToString(cfg.Issuer),
|
||||||
|
AuthEndpoint: uriToString(cfg.AuthEndpoint),
|
||||||
|
TokenEndpoint: uriToString(cfg.TokenEndpoint),
|
||||||
|
UserInfoEndpoint: uriToString(cfg.UserInfoEndpoint),
|
||||||
|
KeysEndpoint: uriToString(cfg.KeysEndpoint),
|
||||||
|
RegistrationEndpoint: uriToString(cfg.RegistrationEndpoint),
|
||||||
|
EndSessionEndpoint: uriToString(cfg.EndSessionEndpoint),
|
||||||
|
CheckSessionIFrame: uriToString(cfg.CheckSessionIFrame),
|
||||||
|
ScopesSupported: cfg.ScopesSupported,
|
||||||
|
ResponseTypesSupported: cfg.ResponseTypesSupported,
|
||||||
|
ResponseModesSupported: cfg.ResponseModesSupported,
|
||||||
|
GrantTypesSupported: cfg.GrantTypesSupported,
|
||||||
|
ACRValuesSupported: cfg.ACRValuesSupported,
|
||||||
|
SubjectTypesSupported: cfg.SubjectTypesSupported,
|
||||||
|
IDTokenSigningAlgValues: cfg.IDTokenSigningAlgValues,
|
||||||
|
IDTokenEncryptionAlgValues: cfg.IDTokenEncryptionAlgValues,
|
||||||
|
IDTokenEncryptionEncValues: cfg.IDTokenEncryptionEncValues,
|
||||||
|
UserInfoSigningAlgValues: cfg.UserInfoSigningAlgValues,
|
||||||
|
UserInfoEncryptionAlgValues: cfg.UserInfoEncryptionAlgValues,
|
||||||
|
UserInfoEncryptionEncValues: cfg.UserInfoEncryptionEncValues,
|
||||||
|
ReqObjSigningAlgValues: cfg.ReqObjSigningAlgValues,
|
||||||
|
ReqObjEncryptionAlgValues: cfg.ReqObjEncryptionAlgValues,
|
||||||
|
ReqObjEncryptionEncValues: cfg.ReqObjEncryptionEncValues,
|
||||||
|
TokenEndpointAuthMethodsSupported: cfg.TokenEndpointAuthMethodsSupported,
|
||||||
|
TokenEndpointAuthSigningAlgValuesSupported: cfg.TokenEndpointAuthSigningAlgValuesSupported,
|
||||||
|
DisplayValuesSupported: cfg.DisplayValuesSupported,
|
||||||
|
ClaimTypesSupported: cfg.ClaimTypesSupported,
|
||||||
|
ClaimsSupported: cfg.ClaimsSupported,
|
||||||
|
ServiceDocs: uriToString(cfg.ServiceDocs),
|
||||||
|
ClaimsLocalsSupported: cfg.ClaimsLocalsSupported,
|
||||||
|
UILocalsSupported: cfg.UILocalsSupported,
|
||||||
|
ClaimsParameterSupported: cfg.ClaimsParameterSupported,
|
||||||
|
RequestParameterSupported: cfg.RequestParameterSupported,
|
||||||
|
RequestURIParamaterSupported: cfg.RequestURIParamaterSupported,
|
||||||
|
RequireRequestURIRegistration: cfg.RequireRequestURIRegistration,
|
||||||
|
Policy: uriToString(cfg.Policy),
|
||||||
|
TermsOfService: uriToString(cfg.TermsOfService),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e encodableProviderConfig) toStruct() (ProviderConfig, error) {
|
||||||
|
p := stickyErrParser{}
|
||||||
|
conf := ProviderConfig{
|
||||||
|
Issuer: p.parseURI(e.Issuer, "issuer"),
|
||||||
|
AuthEndpoint: p.parseURI(e.AuthEndpoint, "authorization_endpoint"),
|
||||||
|
TokenEndpoint: p.parseURI(e.TokenEndpoint, "token_endpoint"),
|
||||||
|
UserInfoEndpoint: p.parseURI(e.UserInfoEndpoint, "userinfo_endpoint"),
|
||||||
|
KeysEndpoint: p.parseURI(e.KeysEndpoint, "jwks_uri"),
|
||||||
|
RegistrationEndpoint: p.parseURI(e.RegistrationEndpoint, "registration_endpoint"),
|
||||||
|
EndSessionEndpoint: p.parseURI(e.EndSessionEndpoint, "end_session_endpoint"),
|
||||||
|
CheckSessionIFrame: p.parseURI(e.CheckSessionIFrame, "check_session_iframe"),
|
||||||
|
ScopesSupported: e.ScopesSupported,
|
||||||
|
ResponseTypesSupported: e.ResponseTypesSupported,
|
||||||
|
ResponseModesSupported: e.ResponseModesSupported,
|
||||||
|
GrantTypesSupported: e.GrantTypesSupported,
|
||||||
|
ACRValuesSupported: e.ACRValuesSupported,
|
||||||
|
SubjectTypesSupported: e.SubjectTypesSupported,
|
||||||
|
IDTokenSigningAlgValues: e.IDTokenSigningAlgValues,
|
||||||
|
IDTokenEncryptionAlgValues: e.IDTokenEncryptionAlgValues,
|
||||||
|
IDTokenEncryptionEncValues: e.IDTokenEncryptionEncValues,
|
||||||
|
UserInfoSigningAlgValues: e.UserInfoSigningAlgValues,
|
||||||
|
UserInfoEncryptionAlgValues: e.UserInfoEncryptionAlgValues,
|
||||||
|
UserInfoEncryptionEncValues: e.UserInfoEncryptionEncValues,
|
||||||
|
ReqObjSigningAlgValues: e.ReqObjSigningAlgValues,
|
||||||
|
ReqObjEncryptionAlgValues: e.ReqObjEncryptionAlgValues,
|
||||||
|
ReqObjEncryptionEncValues: e.ReqObjEncryptionEncValues,
|
||||||
|
TokenEndpointAuthMethodsSupported: e.TokenEndpointAuthMethodsSupported,
|
||||||
|
TokenEndpointAuthSigningAlgValuesSupported: e.TokenEndpointAuthSigningAlgValuesSupported,
|
||||||
|
DisplayValuesSupported: e.DisplayValuesSupported,
|
||||||
|
ClaimTypesSupported: e.ClaimTypesSupported,
|
||||||
|
ClaimsSupported: e.ClaimsSupported,
|
||||||
|
ServiceDocs: p.parseURI(e.ServiceDocs, "service_documentation"),
|
||||||
|
ClaimsLocalsSupported: e.ClaimsLocalsSupported,
|
||||||
|
UILocalsSupported: e.UILocalsSupported,
|
||||||
|
ClaimsParameterSupported: e.ClaimsParameterSupported,
|
||||||
|
RequestParameterSupported: e.RequestParameterSupported,
|
||||||
|
RequestURIParamaterSupported: e.RequestURIParamaterSupported,
|
||||||
|
RequireRequestURIRegistration: e.RequireRequestURIRegistration,
|
||||||
|
Policy: p.parseURI(e.Policy, "op_policy-uri"),
|
||||||
|
TermsOfService: p.parseURI(e.TermsOfService, "op_tos_uri"),
|
||||||
|
}
|
||||||
|
if p.firstErr != nil {
|
||||||
|
return ProviderConfig{}, p.firstErr
|
||||||
|
}
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty returns if a ProviderConfig holds no information.
|
||||||
|
//
|
||||||
|
// This case generally indicates a ProviderConfigGetter has experienced an error
|
||||||
|
// and has nothing to report.
|
||||||
|
func (p ProviderConfig) Empty() bool {
|
||||||
|
return p.Issuer == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(sli []string, ele string) bool {
|
||||||
|
for _, s := range sli {
|
||||||
|
if s == ele {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid determines if a ProviderConfig conforms with the OIDC specification.
|
||||||
|
// If Valid returns successfully it guarantees required field are non-nil and
|
||||||
|
// URLs are well formed.
|
||||||
|
//
|
||||||
|
// Valid is called by UnmarshalJSON.
|
||||||
|
//
|
||||||
|
// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
|
||||||
|
// URLs fields where the OIDC spec requires it. This may change in future releases
|
||||||
|
// of this package. See: https://github.com/coreos/go-oidc/issues/34
|
||||||
|
func (p ProviderConfig) Valid() error {
|
||||||
|
grantTypes := p.GrantTypesSupported
|
||||||
|
if len(grantTypes) == 0 {
|
||||||
|
grantTypes = DefaultGrantTypesSupported
|
||||||
|
}
|
||||||
|
implicitOnly := true
|
||||||
|
for _, grantType := range grantTypes {
|
||||||
|
if grantType != oauth2.GrantTypeImplicit {
|
||||||
|
implicitOnly = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.SubjectTypesSupported) == 0 {
|
||||||
|
return errors.New("missing required field subject_types_supported")
|
||||||
|
}
|
||||||
|
if len(p.IDTokenSigningAlgValues) == 0 {
|
||||||
|
return errors.New("missing required field id_token_signing_alg_values_supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.ScopesSupported) != 0 && !contains(p.ScopesSupported, "openid") {
|
||||||
|
return errors.New("scoped_supported must be unspecified or include 'openid'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contains(p.IDTokenSigningAlgValues, "RS256") {
|
||||||
|
return errors.New("id_token_signing_alg_values_supported must include 'RS256'")
|
||||||
|
}
|
||||||
|
if contains(p.TokenEndpointAuthMethodsSupported, "none") {
|
||||||
|
return errors.New("token_endpoint_auth_signing_alg_values_supported cannot include 'none'")
|
||||||
|
}
|
||||||
|
|
||||||
|
uris := []struct {
|
||||||
|
val *url.URL
|
||||||
|
name string
|
||||||
|
required bool
|
||||||
|
}{
|
||||||
|
{p.Issuer, "issuer", true},
|
||||||
|
{p.AuthEndpoint, "authorization_endpoint", true},
|
||||||
|
{p.TokenEndpoint, "token_endpoint", !implicitOnly},
|
||||||
|
{p.UserInfoEndpoint, "userinfo_endpoint", false},
|
||||||
|
{p.KeysEndpoint, "jwks_uri", true},
|
||||||
|
{p.RegistrationEndpoint, "registration_endpoint", false},
|
||||||
|
{p.EndSessionEndpoint, "end_session_endpoint", false},
|
||||||
|
{p.CheckSessionIFrame, "check_session_iframe", false},
|
||||||
|
{p.ServiceDocs, "service_documentation", false},
|
||||||
|
{p.Policy, "op_policy_uri", false},
|
||||||
|
{p.TermsOfService, "op_tos_uri", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, uri := range uris {
|
||||||
|
if uri.val == nil {
|
||||||
|
if !uri.required {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("empty value for required uri field %s", uri.name)
|
||||||
|
}
|
||||||
|
if uri.val.Host == "" {
|
||||||
|
return fmt.Errorf("no host for uri field %s", uri.name)
|
||||||
|
}
|
||||||
|
if uri.val.Scheme != "http" && uri.val.Scheme != "https" {
|
||||||
|
return fmt.Errorf("uri field %s schemeis not http or https", uri.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supports determines if provider supports a client given their respective metadata.
|
||||||
|
func (p ProviderConfig) Supports(c ClientMetadata) error {
|
||||||
|
if err := p.Valid(); err != nil {
|
||||||
|
return fmt.Errorf("invalid provider config: %v", err)
|
||||||
|
}
|
||||||
|
if err := c.Valid(); err != nil {
|
||||||
|
return fmt.Errorf("invalid client config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill default values for omitted fields
|
||||||
|
c = c.Defaults()
|
||||||
|
p = p.Defaults()
|
||||||
|
|
||||||
|
// Do the supported values list the requested one?
|
||||||
|
supports := []struct {
|
||||||
|
supported []string
|
||||||
|
requested string
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{p.IDTokenSigningAlgValues, c.IDTokenResponseOptions.SigningAlg, "id_token_signed_response_alg"},
|
||||||
|
{p.IDTokenEncryptionAlgValues, c.IDTokenResponseOptions.EncryptionAlg, "id_token_encryption_response_alg"},
|
||||||
|
{p.IDTokenEncryptionEncValues, c.IDTokenResponseOptions.EncryptionEnc, "id_token_encryption_response_enc"},
|
||||||
|
{p.UserInfoSigningAlgValues, c.UserInfoResponseOptions.SigningAlg, "userinfo_signed_response_alg"},
|
||||||
|
{p.UserInfoEncryptionAlgValues, c.UserInfoResponseOptions.EncryptionAlg, "userinfo_encryption_response_alg"},
|
||||||
|
{p.UserInfoEncryptionEncValues, c.UserInfoResponseOptions.EncryptionEnc, "userinfo_encryption_response_enc"},
|
||||||
|
{p.ReqObjSigningAlgValues, c.RequestObjectOptions.SigningAlg, "request_object_signing_alg"},
|
||||||
|
{p.ReqObjEncryptionAlgValues, c.RequestObjectOptions.EncryptionAlg, "request_object_encryption_alg"},
|
||||||
|
{p.ReqObjEncryptionEncValues, c.RequestObjectOptions.EncryptionEnc, "request_object_encryption_enc"},
|
||||||
|
}
|
||||||
|
for _, field := range supports {
|
||||||
|
if field.requested == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !contains(field.supported, field.requested) {
|
||||||
|
return fmt.Errorf("provider does not support requested value for field %s", field.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stringsEqual := func(s1, s2 string) bool { return s1 == s2 }
|
||||||
|
|
||||||
|
// For lists, are the list of requested values a subset of the supported ones?
|
||||||
|
supportsAll := []struct {
|
||||||
|
supported []string
|
||||||
|
requested []string
|
||||||
|
name string
|
||||||
|
// OAuth2.0 response_type can be space separated lists where order doesn't matter.
|
||||||
|
// For example "id_token token" is the same as "token id_token"
|
||||||
|
// Support a custom compare method.
|
||||||
|
comp func(s1, s2 string) bool
|
||||||
|
}{
|
||||||
|
{p.GrantTypesSupported, c.GrantTypes, "grant_types", stringsEqual},
|
||||||
|
{p.ResponseTypesSupported, c.ResponseTypes, "response_type", oauth2.ResponseTypesEqual},
|
||||||
|
}
|
||||||
|
for _, field := range supportsAll {
|
||||||
|
requestLoop:
|
||||||
|
for _, req := range field.requested {
|
||||||
|
for _, sup := range field.supported {
|
||||||
|
if field.comp(req, sup) {
|
||||||
|
continue requestLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("provider does not support requested value for field %s", field.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(ericchiang): Are there more checks we feel comfortable with begin strict about?
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProviderConfig) SupportsGrantType(grantType string) bool {
|
||||||
|
var supported []string
|
||||||
|
if len(p.GrantTypesSupported) == 0 {
|
||||||
|
supported = DefaultGrantTypesSupported
|
||||||
|
} else {
|
||||||
|
supported = p.GrantTypesSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range supported {
|
||||||
|
if t == grantType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderConfigGetter interface {
|
||||||
|
Get() (ProviderConfig, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderConfigSetter interface {
|
||||||
|
Set(ProviderConfig) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderConfigSyncer struct {
|
||||||
|
from ProviderConfigGetter
|
||||||
|
to ProviderConfigSetter
|
||||||
|
clock clockwork.Clock
|
||||||
|
|
||||||
|
initialSyncDone bool
|
||||||
|
initialSyncWait sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderConfigSyncer(from ProviderConfigGetter, to ProviderConfigSetter) *ProviderConfigSyncer {
|
||||||
|
return &ProviderConfigSyncer{
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProviderConfigSyncer) Run() chan struct{} {
|
||||||
|
stop := make(chan struct{})
|
||||||
|
|
||||||
|
var next pcsStepper
|
||||||
|
next = &pcsStepNext{aft: time.Duration(0)}
|
||||||
|
|
||||||
|
s.initialSyncWait.Add(1)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.clock.After(next.after()):
|
||||||
|
next = next.step(s.sync)
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return stop
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProviderConfigSyncer) WaitUntilInitialSync() {
|
||||||
|
s.initialSyncWait.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
|
||||||
|
cfg, err := s.from.Get()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.to.Set(cfg); err != nil {
|
||||||
|
return 0, fmt.Errorf("error setting provider config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.initialSyncDone {
|
||||||
|
s.initialSyncWait.Done()
|
||||||
|
s.initialSyncDone = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextSyncAfter(cfg.ExpiresAt, s.clock), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pcsStepFunc func() (time.Duration, error)
|
||||||
|
|
||||||
|
type pcsStepper interface {
|
||||||
|
after() time.Duration
|
||||||
|
step(pcsStepFunc) pcsStepper
|
||||||
|
}
|
||||||
|
|
||||||
|
type pcsStepNext struct {
|
||||||
|
aft time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *pcsStepNext) after() time.Duration {
|
||||||
|
return n.aft
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *pcsStepNext) step(fn pcsStepFunc) (next pcsStepper) {
|
||||||
|
ttl, err := fn()
|
||||||
|
if err == nil {
|
||||||
|
next = &pcsStepNext{aft: ttl}
|
||||||
|
} else {
|
||||||
|
next = &pcsStepRetry{aft: time.Second}
|
||||||
|
log.Printf("go-oidc: provider config sync falied, retyring in %v: %v", next.after(), err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type pcsStepRetry struct {
|
||||||
|
aft time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *pcsStepRetry) after() time.Duration {
|
||||||
|
return r.aft
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *pcsStepRetry) step(fn pcsStepFunc) (next pcsStepper) {
|
||||||
|
ttl, err := fn()
|
||||||
|
if err == nil {
|
||||||
|
next = &pcsStepNext{aft: ttl}
|
||||||
|
} else {
|
||||||
|
next = &pcsStepRetry{aft: timeutil.ExpBackoff(r.aft, time.Minute)}
|
||||||
|
log.Printf("go-oidc: provider config sync falied, retyring in %v: %v", next.after(), err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextSyncAfter(exp time.Time, clock clockwork.Clock) time.Duration {
|
||||||
|
if exp.IsZero() {
|
||||||
|
return MaximumProviderConfigSyncInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
t := exp.Sub(clock.Now()) / 2
|
||||||
|
if t > MaximumProviderConfigSyncInterval {
|
||||||
|
t = MaximumProviderConfigSyncInterval
|
||||||
|
} else if t < minimumProviderConfigSyncInterval {
|
||||||
|
t = minimumProviderConfigSyncInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpProviderConfigGetter struct {
|
||||||
|
hc phttp.Client
|
||||||
|
issuerURL string
|
||||||
|
clock clockwork.Clock
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPProviderConfigGetter(hc phttp.Client, issuerURL string) *httpProviderConfigGetter {
|
||||||
|
return &httpProviderConfigGetter{
|
||||||
|
hc: hc,
|
||||||
|
issuerURL: issuerURL,
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *httpProviderConfigGetter) Get() (cfg ProviderConfig, err error) {
|
||||||
|
// If the Issuer value contains a path component, any terminating / MUST be removed before
|
||||||
|
// appending /.well-known/openid-configuration.
|
||||||
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
|
||||||
|
discoveryURL := strings.TrimSuffix(r.issuerURL, "/") + discoveryConfigPath
|
||||||
|
req, err := http.NewRequest("GET", discoveryURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := r.hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if err = json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ttl time.Duration
|
||||||
|
var ok bool
|
||||||
|
ttl, ok, err = phttp.Cacheable(resp.Header)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
} else if ok {
|
||||||
|
cfg.ExpiresAt = r.clock.Now().UTC().Add(ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The issuer value returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information.
|
||||||
|
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation
|
||||||
|
if !urlEqual(cfg.Issuer.String(), r.issuerURL) {
|
||||||
|
err = fmt.Errorf(`"issuer" in config (%v) does not match provided issuer URL (%v)`, cfg.Issuer, r.issuerURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchProviderConfig(hc phttp.Client, issuerURL string) (ProviderConfig, error) {
|
||||||
|
if hc == nil {
|
||||||
|
hc = http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
g := NewHTTPProviderConfigGetter(hc, issuerURL)
|
||||||
|
return g.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func WaitForProviderConfig(hc phttp.Client, issuerURL string) (pcfg ProviderConfig) {
|
||||||
|
return waitForProviderConfig(hc, issuerURL, clockwork.NewRealClock())
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForProviderConfig(hc phttp.Client, issuerURL string, clock clockwork.Clock) (pcfg ProviderConfig) {
|
||||||
|
var sleep time.Duration
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
pcfg, err = FetchProviderConfig(hc, issuerURL)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep = timeutil.ExpBackoff(sleep, time.Minute)
|
||||||
|
fmt.Printf("Failed fetching provider config, trying again in %v: %v\n", sleep, err)
|
||||||
|
time.Sleep(sleep)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
940
vendor/github.com/coreos/go-oidc/oidc/provider_test.go
generated
vendored
Normal file
940
vendor/github.com/coreos/go-oidc/oidc/provider_test.go
generated
vendored
Normal file
|
@ -0,0 +1,940 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
"github.com/kylelemons/godebug/diff"
|
||||||
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
"github.com/coreos/go-oidc/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProviderConfigDefaults(t *testing.T) {
|
||||||
|
var cfg ProviderConfig
|
||||||
|
cfg = cfg.Defaults()
|
||||||
|
tests := []struct {
|
||||||
|
got, want []string
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{cfg.GrantTypesSupported, DefaultGrantTypesSupported, "grant types"},
|
||||||
|
{cfg.ResponseModesSupported, DefaultResponseModesSupported, "response modes"},
|
||||||
|
{cfg.ClaimTypesSupported, DefaultClaimTypesSupported, "claim types"},
|
||||||
|
{
|
||||||
|
cfg.TokenEndpointAuthMethodsSupported,
|
||||||
|
DefaultTokenEndpointAuthMethodsSupported,
|
||||||
|
"token endpoint auth methods",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if diff := pretty.Compare(tt.want, tt.got); diff != "" {
|
||||||
|
t.Errorf("%s: did not match %s", tt.name, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigUnmarshal(t *testing.T) {
|
||||||
|
|
||||||
|
// helper for quickly creating uris
|
||||||
|
uri := func(path string) *url.URL {
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "server.example.com",
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
data string
|
||||||
|
want ProviderConfig
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
data: `{
|
||||||
|
"issuer": "https://server.example.com",
|
||||||
|
"authorization_endpoint": "https://server.example.com/connect/authorize",
|
||||||
|
"token_endpoint": "https://server.example.com/connect/token",
|
||||||
|
"token_endpoint_auth_methods_supported": ["client_secret_basic", "private_key_jwt"],
|
||||||
|
"token_endpoint_auth_signing_alg_values_supported": ["RS256", "ES256"],
|
||||||
|
"userinfo_endpoint": "https://server.example.com/connect/userinfo",
|
||||||
|
"jwks_uri": "https://server.example.com/jwks.json",
|
||||||
|
"registration_endpoint": "https://server.example.com/connect/register",
|
||||||
|
"scopes_supported": [
|
||||||
|
"openid", "profile", "email", "address", "phone", "offline_access"
|
||||||
|
],
|
||||||
|
"response_types_supported": [
|
||||||
|
"code", "code id_token", "id_token", "id_token token"
|
||||||
|
],
|
||||||
|
"acr_values_supported": [
|
||||||
|
"urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze"
|
||||||
|
],
|
||||||
|
"subject_types_supported": ["public", "pairwise"],
|
||||||
|
"userinfo_signing_alg_values_supported": ["RS256", "ES256", "HS256"],
|
||||||
|
"userinfo_encryption_alg_values_supported": ["RSA1_5", "A128KW"],
|
||||||
|
"userinfo_encryption_enc_values_supported": ["A128CBC-HS256", "A128GCM"],
|
||||||
|
"id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"],
|
||||||
|
"id_token_encryption_alg_values_supported": ["RSA1_5", "A128KW"],
|
||||||
|
"id_token_encryption_enc_values_supported": ["A128CBC-HS256", "A128GCM"],
|
||||||
|
"request_object_signing_alg_values_supported": ["none", "RS256", "ES256"],
|
||||||
|
"display_values_supported": ["page", "popup"],
|
||||||
|
"claim_types_supported": ["normal", "distributed"],
|
||||||
|
"claims_supported": [
|
||||||
|
"sub", "iss", "auth_time", "acr", "name", "given_name",
|
||||||
|
"family_name", "nickname", "profile", "picture", "website",
|
||||||
|
"email", "email_verified", "locale", "zoneinfo",
|
||||||
|
"http://example.info/claims/groups"
|
||||||
|
],
|
||||||
|
"claims_parameter_supported": true,
|
||||||
|
"service_documentation": "https://server.example.com/connect/service_documentation.html",
|
||||||
|
"ui_locales_supported": ["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
want: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "server.example.com"},
|
||||||
|
AuthEndpoint: uri("/connect/authorize"),
|
||||||
|
TokenEndpoint: uri("/connect/token"),
|
||||||
|
TokenEndpointAuthMethodsSupported: []string{
|
||||||
|
oauth2.AuthMethodClientSecretBasic, oauth2.AuthMethodPrivateKeyJWT,
|
||||||
|
},
|
||||||
|
TokenEndpointAuthSigningAlgValuesSupported: []string{
|
||||||
|
jose.AlgRS256, jose.AlgES256,
|
||||||
|
},
|
||||||
|
UserInfoEndpoint: uri("/connect/userinfo"),
|
||||||
|
KeysEndpoint: uri("/jwks.json"),
|
||||||
|
RegistrationEndpoint: uri("/connect/register"),
|
||||||
|
ScopesSupported: []string{
|
||||||
|
"openid", "profile", "email", "address", "phone", "offline_access",
|
||||||
|
},
|
||||||
|
ResponseTypesSupported: []string{
|
||||||
|
oauth2.ResponseTypeCode, oauth2.ResponseTypeCodeIDToken,
|
||||||
|
oauth2.ResponseTypeIDToken, oauth2.ResponseTypeIDTokenToken,
|
||||||
|
},
|
||||||
|
ACRValuesSupported: []string{
|
||||||
|
"urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze",
|
||||||
|
},
|
||||||
|
SubjectTypesSupported: []string{
|
||||||
|
SubjectTypePublic, SubjectTypePairwise,
|
||||||
|
},
|
||||||
|
UserInfoSigningAlgValues: []string{jose.AlgRS256, jose.AlgES256, jose.AlgHS256},
|
||||||
|
UserInfoEncryptionAlgValues: []string{"RSA1_5", "A128KW"},
|
||||||
|
UserInfoEncryptionEncValues: []string{"A128CBC-HS256", "A128GCM"},
|
||||||
|
IDTokenSigningAlgValues: []string{jose.AlgRS256, jose.AlgES256, jose.AlgHS256},
|
||||||
|
IDTokenEncryptionAlgValues: []string{"RSA1_5", "A128KW"},
|
||||||
|
IDTokenEncryptionEncValues: []string{"A128CBC-HS256", "A128GCM"},
|
||||||
|
ReqObjSigningAlgValues: []string{jose.AlgNone, jose.AlgRS256, jose.AlgES256},
|
||||||
|
DisplayValuesSupported: []string{"page", "popup"},
|
||||||
|
ClaimTypesSupported: []string{"normal", "distributed"},
|
||||||
|
ClaimsSupported: []string{
|
||||||
|
"sub", "iss", "auth_time", "acr", "name", "given_name",
|
||||||
|
"family_name", "nickname", "profile", "picture", "website",
|
||||||
|
"email", "email_verified", "locale", "zoneinfo",
|
||||||
|
"http://example.info/claims/groups",
|
||||||
|
},
|
||||||
|
ClaimsParameterSupported: true,
|
||||||
|
ServiceDocs: uri("/connect/service_documentation.html"),
|
||||||
|
UILocalsSupported: []string{"en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// missing a lot of required field
|
||||||
|
data: `{}`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: `{
|
||||||
|
"issuer": "https://server.example.com",
|
||||||
|
"authorization_endpoint": "https://server.example.com/connect/authorize",
|
||||||
|
"token_endpoint": "https://server.example.com/connect/token",
|
||||||
|
"jwks_uri": "https://server.example.com/jwks.json",
|
||||||
|
"response_types_supported": [
|
||||||
|
"code", "code id_token", "id_token", "id_token token"
|
||||||
|
],
|
||||||
|
"subject_types_supported": ["public", "pairwise"],
|
||||||
|
"id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
want: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "server.example.com"},
|
||||||
|
AuthEndpoint: uri("/connect/authorize"),
|
||||||
|
TokenEndpoint: uri("/connect/token"),
|
||||||
|
KeysEndpoint: uri("/jwks.json"),
|
||||||
|
ResponseTypesSupported: []string{
|
||||||
|
oauth2.ResponseTypeCode, oauth2.ResponseTypeCodeIDToken,
|
||||||
|
oauth2.ResponseTypeIDToken, oauth2.ResponseTypeIDTokenToken,
|
||||||
|
},
|
||||||
|
SubjectTypesSupported: []string{
|
||||||
|
SubjectTypePublic, SubjectTypePairwise,
|
||||||
|
},
|
||||||
|
IDTokenSigningAlgValues: []string{jose.AlgRS256, jose.AlgES256, jose.AlgHS256},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// invalid scheme 'ftp://'
|
||||||
|
data: `{
|
||||||
|
"issuer": "https://server.example.com",
|
||||||
|
"authorization_endpoint": "https://server.example.com/connect/authorize",
|
||||||
|
"token_endpoint": "https://server.example.com/connect/token",
|
||||||
|
"jwks_uri": "ftp://server.example.com/jwks.json",
|
||||||
|
"response_types_supported": [
|
||||||
|
"code", "code id_token", "id_token", "id_token token"
|
||||||
|
],
|
||||||
|
"subject_types_supported": ["public", "pairwise"],
|
||||||
|
"id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
var got ProviderConfig
|
||||||
|
if err := json.Unmarshal([]byte(tt.data), &got); err != nil {
|
||||||
|
if !tt.wantErr {
|
||||||
|
t.Errorf("case %d: failed to unmarshal provider config: %v", i, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
t.Errorf("case %d: expected error", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if diff := pretty.Compare(tt.want, got); diff != "" {
|
||||||
|
t.Errorf("case %d: unmarshaled struct did not match expected %s", i, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigMarshal(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
cfg ProviderConfig
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cfg: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "auth.example.com"},
|
||||||
|
AuthEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/auth",
|
||||||
|
},
|
||||||
|
TokenEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/token",
|
||||||
|
},
|
||||||
|
UserInfoEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/userinfo",
|
||||||
|
},
|
||||||
|
KeysEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/jwk",
|
||||||
|
},
|
||||||
|
ResponseTypesSupported: []string{oauth2.ResponseTypeCode},
|
||||||
|
SubjectTypesSupported: []string{SubjectTypePublic},
|
||||||
|
IDTokenSigningAlgValues: []string{jose.AlgRS256},
|
||||||
|
},
|
||||||
|
// spacing must match json.MarshalIndent(cfg, "", "\t")
|
||||||
|
want: `{
|
||||||
|
"issuer": "https://auth.example.com",
|
||||||
|
"authorization_endpoint": "https://auth.example.com/auth",
|
||||||
|
"token_endpoint": "https://auth.example.com/token",
|
||||||
|
"userinfo_endpoint": "https://auth.example.com/userinfo",
|
||||||
|
"jwks_uri": "https://auth.example.com/jwk",
|
||||||
|
"response_types_supported": [
|
||||||
|
"code"
|
||||||
|
],
|
||||||
|
"subject_types_supported": [
|
||||||
|
"public"
|
||||||
|
],
|
||||||
|
"id_token_signing_alg_values_supported": [
|
||||||
|
"RS256"
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cfg: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "auth.example.com"},
|
||||||
|
AuthEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/auth",
|
||||||
|
},
|
||||||
|
TokenEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/token",
|
||||||
|
},
|
||||||
|
UserInfoEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/userinfo",
|
||||||
|
},
|
||||||
|
KeysEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/jwk",
|
||||||
|
},
|
||||||
|
RegistrationEndpoint: &url.URL{
|
||||||
|
Scheme: "https", Host: "auth.example.com", Path: "/register",
|
||||||
|
},
|
||||||
|
ScopesSupported: DefaultScope,
|
||||||
|
ResponseTypesSupported: []string{oauth2.ResponseTypeCode},
|
||||||
|
ResponseModesSupported: DefaultResponseModesSupported,
|
||||||
|
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode},
|
||||||
|
SubjectTypesSupported: []string{SubjectTypePublic},
|
||||||
|
IDTokenSigningAlgValues: []string{jose.AlgRS256},
|
||||||
|
ServiceDocs: &url.URL{Scheme: "https", Host: "example.com", Path: "/docs"},
|
||||||
|
},
|
||||||
|
// spacing must match json.MarshalIndent(cfg, "", "\t")
|
||||||
|
want: `{
|
||||||
|
"issuer": "https://auth.example.com",
|
||||||
|
"authorization_endpoint": "https://auth.example.com/auth",
|
||||||
|
"token_endpoint": "https://auth.example.com/token",
|
||||||
|
"userinfo_endpoint": "https://auth.example.com/userinfo",
|
||||||
|
"jwks_uri": "https://auth.example.com/jwk",
|
||||||
|
"registration_endpoint": "https://auth.example.com/register",
|
||||||
|
"scopes_supported": [
|
||||||
|
"openid",
|
||||||
|
"email",
|
||||||
|
"profile"
|
||||||
|
],
|
||||||
|
"response_types_supported": [
|
||||||
|
"code"
|
||||||
|
],
|
||||||
|
"response_modes_supported": [
|
||||||
|
"query",
|
||||||
|
"fragment"
|
||||||
|
],
|
||||||
|
"grant_types_supported": [
|
||||||
|
"authorization_code"
|
||||||
|
],
|
||||||
|
"subject_types_supported": [
|
||||||
|
"public"
|
||||||
|
],
|
||||||
|
"id_token_signing_alg_values_supported": [
|
||||||
|
"RS256"
|
||||||
|
],
|
||||||
|
"service_documentation": "https://example.com/docs"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got, err := json.MarshalIndent(&tt.cfg, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: failed to marshal config: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if d := diff.Diff(string(got), string(tt.want)); d != "" {
|
||||||
|
t.Errorf("case %d: expected did not match result: %s", i, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg ProviderConfig
|
||||||
|
if err := json.Unmarshal(got, &cfg); err != nil {
|
||||||
|
t.Errorf("case %d: could not unmarshal marshal response: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if d := pretty.Compare(tt.cfg, cfg); d != "" {
|
||||||
|
t.Errorf("case %d: config did not survive JSON marshaling round trip: %s", i, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigSupports(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
provider ProviderConfig
|
||||||
|
client ClientMetadata
|
||||||
|
fillRequiredProviderFields bool
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
provider: ProviderConfig{},
|
||||||
|
client: ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com", Path: "/callback"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fillRequiredProviderFields: true,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// invalid provider config
|
||||||
|
provider: ProviderConfig{},
|
||||||
|
client: ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{
|
||||||
|
{Scheme: "https", Host: "example.com", Path: "/callback"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fillRequiredProviderFields: false,
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// invalid client config
|
||||||
|
provider: ProviderConfig{},
|
||||||
|
client: ClientMetadata{},
|
||||||
|
fillRequiredProviderFields: true,
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
if tt.fillRequiredProviderFields {
|
||||||
|
tt.provider = fillRequiredProviderFields(tt.provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tt.provider.Supports(tt.client)
|
||||||
|
if err == nil && !tt.ok {
|
||||||
|
t.Errorf("case %d: expected non-nil error", i)
|
||||||
|
}
|
||||||
|
if err != nil && tt.ok {
|
||||||
|
t.Errorf("case %d: supports failed: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newValidProviderConfig() ProviderConfig {
|
||||||
|
var cfg ProviderConfig
|
||||||
|
return fillRequiredProviderFields(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill a provider config with enough information to be valid
|
||||||
|
func fillRequiredProviderFields(cfg ProviderConfig) ProviderConfig {
|
||||||
|
if cfg.Issuer == nil {
|
||||||
|
cfg.Issuer = &url.URL{Scheme: "https", Host: "auth.example.com"}
|
||||||
|
}
|
||||||
|
urlPath := func(path string) *url.URL {
|
||||||
|
var u url.URL
|
||||||
|
u = *cfg.Issuer
|
||||||
|
u.Path = path
|
||||||
|
return &u
|
||||||
|
}
|
||||||
|
cfg.AuthEndpoint = urlPath("/auth")
|
||||||
|
cfg.TokenEndpoint = urlPath("/token")
|
||||||
|
cfg.UserInfoEndpoint = urlPath("/userinfo")
|
||||||
|
cfg.KeysEndpoint = urlPath("/jwk")
|
||||||
|
cfg.ResponseTypesSupported = []string{oauth2.ResponseTypeCode}
|
||||||
|
cfg.SubjectTypesSupported = []string{SubjectTypePublic}
|
||||||
|
cfg.IDTokenSigningAlgValues = []string{jose.AlgRS256}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeProviderConfigGetterSetter struct {
|
||||||
|
cfg *ProviderConfig
|
||||||
|
getCount int
|
||||||
|
setCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *fakeProviderConfigGetterSetter) Get() (ProviderConfig, error) {
|
||||||
|
g.getCount++
|
||||||
|
return *g.cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *fakeProviderConfigGetterSetter) Set(cfg ProviderConfig) error {
|
||||||
|
g.cfg = &cfg
|
||||||
|
g.setCount++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeProviderConfigHandler struct {
|
||||||
|
cfg ProviderConfig
|
||||||
|
maxAge time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeProviderConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
b, _ := json.Marshal(&s.cfg)
|
||||||
|
if s.maxAge.Seconds() >= 0 {
|
||||||
|
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(s.maxAge.Seconds())))
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigRequiredFields(t *testing.T) {
|
||||||
|
// Ensure provider metadata responses have all the required fields.
|
||||||
|
// taken from https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||||
|
requiredFields := []string{
|
||||||
|
"issuer",
|
||||||
|
"authorization_endpoint",
|
||||||
|
"token_endpoint", // "This is REQUIRED unless only the Implicit Flow is used."
|
||||||
|
"jwks_uri",
|
||||||
|
"response_types_supported",
|
||||||
|
"subject_types_supported",
|
||||||
|
"id_token_signing_alg_values_supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
svr := &fakeProviderConfigHandler{
|
||||||
|
cfg: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
|
||||||
|
ExpiresAt: time.Now().Add(time.Minute),
|
||||||
|
},
|
||||||
|
maxAge: time.Minute,
|
||||||
|
}
|
||||||
|
svr.cfg = fillRequiredProviderFields(svr.cfg)
|
||||||
|
s := httptest.NewServer(svr)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
resp, err := http.Get(s.URL + "/")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("get: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var data map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
t.Errorf("decode: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, field := range requiredFields {
|
||||||
|
if _, ok := data[field]; !ok {
|
||||||
|
t.Errorf("provider metadata does not have required field '%s'", field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type handlerClient struct {
|
||||||
|
Handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hc *handlerClient) Do(r *http.Request) (*http.Response, error) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
hc.Handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
resp := http.Response{
|
||||||
|
StatusCode: w.Code,
|
||||||
|
Header: w.Header(),
|
||||||
|
Body: ioutil.NopCloser(w.Body),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPProviderConfigGetter(t *testing.T) {
|
||||||
|
svr := &fakeProviderConfigHandler{}
|
||||||
|
hc := &handlerClient{Handler: svr}
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
dsc string
|
||||||
|
age time.Duration
|
||||||
|
cfg ProviderConfig
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
// everything is good
|
||||||
|
{
|
||||||
|
dsc: "https://example.com",
|
||||||
|
age: time.Minute,
|
||||||
|
cfg: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
|
||||||
|
ExpiresAt: now.Add(time.Minute),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// iss and disco url differ by scheme only (how google works)
|
||||||
|
{
|
||||||
|
dsc: "https://example.com",
|
||||||
|
age: time.Minute,
|
||||||
|
cfg: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
|
||||||
|
ExpiresAt: now.Add(time.Minute),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// issuer and discovery URL mismatch
|
||||||
|
{
|
||||||
|
dsc: "https://foo.com",
|
||||||
|
age: time.Minute,
|
||||||
|
cfg: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
|
||||||
|
ExpiresAt: now.Add(time.Minute),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// missing cache header results in zero ExpiresAt
|
||||||
|
{
|
||||||
|
dsc: "https://example.com",
|
||||||
|
age: -1,
|
||||||
|
cfg: ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
tt.cfg = fillRequiredProviderFields(tt.cfg)
|
||||||
|
svr.cfg = tt.cfg
|
||||||
|
svr.maxAge = tt.age
|
||||||
|
getter := NewHTTPProviderConfigGetter(hc, tt.dsc)
|
||||||
|
getter.clock = fc
|
||||||
|
|
||||||
|
got, err := getter.Get()
|
||||||
|
if err != nil {
|
||||||
|
if tt.ok {
|
||||||
|
t.Errorf("test %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.ok {
|
||||||
|
t.Errorf("test %d: expected error", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tt.cfg, got) {
|
||||||
|
t.Errorf("test %d: want: %#v, got: %#v", i, tt.cfg, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigSyncerRun(t *testing.T) {
|
||||||
|
c1 := &ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
|
||||||
|
}
|
||||||
|
c2 := &ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
first *ProviderConfig
|
||||||
|
advance time.Duration
|
||||||
|
second *ProviderConfig
|
||||||
|
firstExp time.Duration
|
||||||
|
secondExp time.Duration
|
||||||
|
count int
|
||||||
|
}{
|
||||||
|
// exp is 10m, should have same config after 1s
|
||||||
|
{
|
||||||
|
first: c1,
|
||||||
|
firstExp: time.Duration(10 * time.Minute),
|
||||||
|
advance: time.Minute,
|
||||||
|
second: c1,
|
||||||
|
secondExp: time.Duration(10 * time.Minute),
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
// exp is 10m, should have new config after 10/2 = 5m
|
||||||
|
{
|
||||||
|
first: c1,
|
||||||
|
firstExp: time.Duration(10 * time.Minute),
|
||||||
|
advance: time.Duration(5 * time.Minute),
|
||||||
|
second: c2,
|
||||||
|
secondExp: time.Duration(10 * time.Minute),
|
||||||
|
count: 2,
|
||||||
|
},
|
||||||
|
// exp is 20m, should have new config after 20/2 = 10m
|
||||||
|
{
|
||||||
|
first: c1,
|
||||||
|
firstExp: time.Duration(20 * time.Minute),
|
||||||
|
advance: time.Duration(10 * time.Minute),
|
||||||
|
second: c2,
|
||||||
|
secondExp: time.Duration(30 * time.Minute),
|
||||||
|
count: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCfg := func(i int, to *fakeProviderConfigGetterSetter, want ProviderConfig) {
|
||||||
|
got, err := to.Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test %d: unable to get config: %v", i, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Fatalf("test %d: incorrect state:\nwant=%#v\ngot=%#v", i, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
from := &fakeProviderConfigGetterSetter{}
|
||||||
|
to := &fakeProviderConfigGetterSetter{}
|
||||||
|
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
now := fc.Now().UTC()
|
||||||
|
syncer := NewProviderConfigSyncer(from, to)
|
||||||
|
syncer.clock = fc
|
||||||
|
|
||||||
|
tt.first.ExpiresAt = now.Add(tt.firstExp)
|
||||||
|
tt.second.ExpiresAt = now.Add(tt.secondExp)
|
||||||
|
if err := from.Set(*tt.first); err != nil {
|
||||||
|
t.Fatalf("test %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stop := syncer.Run()
|
||||||
|
defer close(stop)
|
||||||
|
fc.BlockUntil(1)
|
||||||
|
|
||||||
|
// first sync
|
||||||
|
assertCfg(i, to, *tt.first)
|
||||||
|
|
||||||
|
if err := from.Set(*tt.second); err != nil {
|
||||||
|
t.Fatalf("test %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fc.Advance(tt.advance)
|
||||||
|
fc.BlockUntil(1)
|
||||||
|
|
||||||
|
// second sync
|
||||||
|
assertCfg(i, to, *tt.second)
|
||||||
|
|
||||||
|
if tt.count != from.getCount {
|
||||||
|
t.Fatalf("test %d: want: %v, got: %v", i, tt.count, from.getCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type staticProviderConfigGetter struct {
|
||||||
|
cfg ProviderConfig
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *staticProviderConfigGetter) Get() (ProviderConfig, error) {
|
||||||
|
return g.cfg, g.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type staticProviderConfigSetter struct {
|
||||||
|
cfg *ProviderConfig
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticProviderConfigSetter) Set(cfg ProviderConfig) error {
|
||||||
|
s.cfg = &cfg
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigSyncerSyncFailure(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
from *staticProviderConfigGetter
|
||||||
|
to *staticProviderConfigSetter
|
||||||
|
|
||||||
|
// want indicates what ProviderConfig should be passed to Set.
|
||||||
|
// If nil, the Set should not be called.
|
||||||
|
want *ProviderConfig
|
||||||
|
}{
|
||||||
|
// generic Get failure
|
||||||
|
{
|
||||||
|
from: &staticProviderConfigGetter{err: errors.New("fail")},
|
||||||
|
to: &staticProviderConfigSetter{},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
// generic Set failure
|
||||||
|
{
|
||||||
|
from: &staticProviderConfigGetter{cfg: ProviderConfig{ExpiresAt: fc.Now().Add(time.Minute)}},
|
||||||
|
to: &staticProviderConfigSetter{err: errors.New("fail")},
|
||||||
|
want: &ProviderConfig{ExpiresAt: fc.Now().Add(time.Minute)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
pcs := &ProviderConfigSyncer{
|
||||||
|
from: tt.from,
|
||||||
|
to: tt.to,
|
||||||
|
clock: fc,
|
||||||
|
}
|
||||||
|
_, err := pcs.sync()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: expected non-nil error", i)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.want, tt.to.cfg) {
|
||||||
|
t.Errorf("case %d: Set mismatch: want=%#v got=%#v", i, tt.want, tt.to.cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextSyncAfter(t *testing.T) {
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
exp time.Time
|
||||||
|
want time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
exp: fc.Now().Add(time.Hour),
|
||||||
|
want: 30 * time.Minute,
|
||||||
|
},
|
||||||
|
// override large values with the maximum
|
||||||
|
{
|
||||||
|
exp: fc.Now().Add(168 * time.Hour), // one week
|
||||||
|
want: 24 * time.Hour,
|
||||||
|
},
|
||||||
|
// override "now" values with the minimum
|
||||||
|
{
|
||||||
|
exp: fc.Now(),
|
||||||
|
want: time.Minute,
|
||||||
|
},
|
||||||
|
// override negative values with the minimum
|
||||||
|
{
|
||||||
|
exp: fc.Now().Add(-1 * time.Minute),
|
||||||
|
want: time.Minute,
|
||||||
|
},
|
||||||
|
// zero-value Time results in maximum sync interval
|
||||||
|
{
|
||||||
|
exp: time.Time{},
|
||||||
|
want: 24 * time.Hour,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := nextSyncAfter(tt.exp, fc)
|
||||||
|
if tt.want != got {
|
||||||
|
t.Errorf("case %d: want=%v got=%v", i, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigEmpty(t *testing.T) {
|
||||||
|
cfg := ProviderConfig{}
|
||||||
|
if !cfg.Empty() {
|
||||||
|
t.Fatalf("Empty provider config reports non-empty")
|
||||||
|
}
|
||||||
|
cfg = ProviderConfig{
|
||||||
|
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
|
||||||
|
}
|
||||||
|
if cfg.Empty() {
|
||||||
|
t.Fatalf("Non-empty provider config reports empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPCSStepAfter(t *testing.T) {
|
||||||
|
pass := func() (time.Duration, error) { return 7 * time.Second, nil }
|
||||||
|
fail := func() (time.Duration, error) { return 0, errors.New("fail") }
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
stepper pcsStepper
|
||||||
|
stepFunc pcsStepFunc
|
||||||
|
want pcsStepper
|
||||||
|
}{
|
||||||
|
// good step results in retry at TTL
|
||||||
|
{
|
||||||
|
stepper: &pcsStepNext{},
|
||||||
|
stepFunc: pass,
|
||||||
|
want: &pcsStepNext{aft: 7 * time.Second},
|
||||||
|
},
|
||||||
|
|
||||||
|
// good step after failed step results results in retry at TTL
|
||||||
|
{
|
||||||
|
stepper: &pcsStepRetry{aft: 2 * time.Second},
|
||||||
|
stepFunc: pass,
|
||||||
|
want: &pcsStepNext{aft: 7 * time.Second},
|
||||||
|
},
|
||||||
|
|
||||||
|
// failed step results in a retry in 1s
|
||||||
|
{
|
||||||
|
stepper: &pcsStepNext{},
|
||||||
|
stepFunc: fail,
|
||||||
|
want: &pcsStepRetry{aft: time.Second},
|
||||||
|
},
|
||||||
|
|
||||||
|
// failed retry backs off by a factor of 2
|
||||||
|
{
|
||||||
|
stepper: &pcsStepRetry{aft: time.Second},
|
||||||
|
stepFunc: fail,
|
||||||
|
want: &pcsStepRetry{aft: 2 * time.Second},
|
||||||
|
},
|
||||||
|
|
||||||
|
// failed retry backs off by a factor of 2, up to 1m
|
||||||
|
{
|
||||||
|
stepper: &pcsStepRetry{aft: 32 * time.Second},
|
||||||
|
stepFunc: fail,
|
||||||
|
want: &pcsStepRetry{aft: 60 * time.Second},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := tt.stepper.step(tt.stepFunc)
|
||||||
|
if !reflect.DeepEqual(tt.want, got) {
|
||||||
|
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderConfigSupportsGrantType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
types []string
|
||||||
|
typ string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// explicitly supported
|
||||||
|
{
|
||||||
|
types: []string{"foo_type"},
|
||||||
|
typ: "foo_type",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// explicitly unsupported
|
||||||
|
{
|
||||||
|
types: []string{"bar_type"},
|
||||||
|
typ: "foo_type",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// default type explicitly unsupported
|
||||||
|
{
|
||||||
|
types: []string{oauth2.GrantTypeImplicit},
|
||||||
|
typ: oauth2.GrantTypeAuthCode,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// type not found in default set
|
||||||
|
{
|
||||||
|
types: []string{},
|
||||||
|
typ: "foo_type",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// type found in default set
|
||||||
|
{
|
||||||
|
types: []string{},
|
||||||
|
typ: oauth2.GrantTypeAuthCode,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
cfg := ProviderConfig{
|
||||||
|
GrantTypesSupported: tt.types,
|
||||||
|
}
|
||||||
|
got := cfg.SupportsGrantType(tt.typ)
|
||||||
|
if tt.want != got {
|
||||||
|
t.Errorf("case %d: assert %v supports %v: want=%t got=%t", i, tt.types, tt.typ, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeClient struct {
|
||||||
|
resp *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
return f.resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWaitForProviderConfigImmediateSuccess(t *testing.T) {
|
||||||
|
cfg := newValidProviderConfig()
|
||||||
|
b, err := json.Marshal(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed marshaling provider config")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := http.Response{Body: ioutil.NopCloser(bytes.NewBuffer(b))}
|
||||||
|
hc := &fakeClient{&resp}
|
||||||
|
fc := clockwork.NewFakeClock()
|
||||||
|
|
||||||
|
reschan := make(chan ProviderConfig)
|
||||||
|
go func() {
|
||||||
|
reschan <- waitForProviderConfig(hc, cfg.Issuer.String(), fc)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var got ProviderConfig
|
||||||
|
select {
|
||||||
|
case got = <-reschan:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatalf("Did not receive result within 1s")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cfg, got) {
|
||||||
|
t.Fatalf("Received incorrect provider config: want=%#v got=%#v", cfg, got)
|
||||||
|
}
|
||||||
|
}
|
88
vendor/github.com/coreos/go-oidc/oidc/transport.go
generated
vendored
Normal file
88
vendor/github.com/coreos/go-oidc/oidc/transport.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
phttp "github.com/coreos/go-oidc/http"
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenRefresher interface {
|
||||||
|
// Verify checks if the provided token is currently valid or not.
|
||||||
|
Verify(jose.JWT) error
|
||||||
|
|
||||||
|
// Refresh attempts to authenticate and retrieve a new token.
|
||||||
|
Refresh() (jose.JWT, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientCredsTokenRefresher struct {
|
||||||
|
Issuer string
|
||||||
|
OIDCClient *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientCredsTokenRefresher) Verify(jwt jose.JWT) (err error) {
|
||||||
|
_, err = VerifyClientClaims(jwt, c.Issuer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientCredsTokenRefresher) Refresh() (jwt jose.JWT, err error) {
|
||||||
|
if err = c.OIDCClient.Healthy(); err != nil {
|
||||||
|
err = fmt.Errorf("unable to authenticate, unhealthy OIDC client: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt, err = c.OIDCClient.ClientCredsToken([]string{"openid"})
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("unable to verify auth code with issuer: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticatedTransport struct {
|
||||||
|
TokenRefresher
|
||||||
|
http.RoundTripper
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
jwt jose.JWT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AuthenticatedTransport) verifiedJWT() (jose.JWT, error) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
if t.TokenRefresher.Verify(t.jwt) == nil {
|
||||||
|
return t.jwt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt, err := t.TokenRefresher.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
return jose.JWT{}, fmt.Errorf("unable to acquire valid JWT: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.jwt = jwt
|
||||||
|
return t.jwt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetJWT sets the JWT held by the Transport.
|
||||||
|
// This is useful for cases in which you want to set an initial JWT.
|
||||||
|
func (t *AuthenticatedTransport) SetJWT(jwt jose.JWT) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
t.jwt = jwt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AuthenticatedTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
jwt, err := t.verifiedJWT()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := phttp.CopyRequest(r)
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt.Encode()))
|
||||||
|
return t.RoundTripper.RoundTrip(req)
|
||||||
|
}
|
176
vendor/github.com/coreos/go-oidc/oidc/transport_test.go
generated
vendored
Normal file
176
vendor/github.com/coreos/go-oidc/oidc/transport_test.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
type staticTokenRefresher struct {
|
||||||
|
verify func(jose.JWT) error
|
||||||
|
refresh func() (jose.JWT, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticTokenRefresher) Verify(jwt jose.JWT) error {
|
||||||
|
return s.verify(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticTokenRefresher) Refresh() (jose.JWT, error) {
|
||||||
|
return s.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticatedTransportVerifiedJWT(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
refresher TokenRefresher
|
||||||
|
startJWT jose.JWT
|
||||||
|
wantJWT jose.JWT
|
||||||
|
wantError error
|
||||||
|
}{
|
||||||
|
// verification succeeds, so refresh is not called
|
||||||
|
{
|
||||||
|
refresher: &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return nil },
|
||||||
|
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "2"}, nil },
|
||||||
|
},
|
||||||
|
startJWT: jose.JWT{RawPayload: "1"},
|
||||||
|
wantJWT: jose.JWT{RawPayload: "1"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// verification fails, refresh succeeds so cached JWT changes
|
||||||
|
{
|
||||||
|
refresher: &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return errors.New("fail!") },
|
||||||
|
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "2"}, nil },
|
||||||
|
},
|
||||||
|
startJWT: jose.JWT{RawPayload: "1"},
|
||||||
|
wantJWT: jose.JWT{RawPayload: "2"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// verification succeeds, so failing refresh isn't attempted
|
||||||
|
{
|
||||||
|
refresher: &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return nil },
|
||||||
|
refresh: func() (jose.JWT, error) { return jose.JWT{}, errors.New("fail!") },
|
||||||
|
},
|
||||||
|
startJWT: jose.JWT{RawPayload: "1"},
|
||||||
|
wantJWT: jose.JWT{RawPayload: "1"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// verification fails, but refresh fails, too
|
||||||
|
{
|
||||||
|
refresher: &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return errors.New("fail!") },
|
||||||
|
refresh: func() (jose.JWT, error) { return jose.JWT{}, errors.New("fail!") },
|
||||||
|
},
|
||||||
|
startJWT: jose.JWT{RawPayload: "1"},
|
||||||
|
wantJWT: jose.JWT{},
|
||||||
|
wantError: errors.New("unable to acquire valid JWT: fail!"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
at := &AuthenticatedTransport{
|
||||||
|
TokenRefresher: tt.refresher,
|
||||||
|
}
|
||||||
|
at.SetJWT(tt.startJWT)
|
||||||
|
|
||||||
|
gotJWT, err := at.verifiedJWT()
|
||||||
|
if !reflect.DeepEqual(tt.wantError, err) {
|
||||||
|
t.Errorf("#%d: unexpected error: want=%#v got=%#v", i, tt.wantError, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.wantJWT, gotJWT) {
|
||||||
|
t.Errorf("#%d: incorrect JWT returned from verifiedJWT: want=%#v got=%#v", i, tt.wantJWT, gotJWT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticatedTransportJWTCaching(t *testing.T) {
|
||||||
|
at := &AuthenticatedTransport{
|
||||||
|
TokenRefresher: &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return errors.New("fail!") },
|
||||||
|
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "2"}, nil },
|
||||||
|
},
|
||||||
|
jwt: jose.JWT{RawPayload: "1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
wantJWT := jose.JWT{RawPayload: "2"}
|
||||||
|
gotJWT, err := at.verifiedJWT()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got non-nil error: %#v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(wantJWT, gotJWT) {
|
||||||
|
t.Fatalf("incorrect JWT returned from verifiedJWT: want=%#v got=%#v", wantJWT, gotJWT)
|
||||||
|
}
|
||||||
|
|
||||||
|
at.TokenRefresher = &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return nil },
|
||||||
|
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "3"}, nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
// the previous JWT should still be cached on the AuthenticatedTransport since
|
||||||
|
// it is still valid, even though there's a new token ready to refresh
|
||||||
|
gotJWT, err = at.verifiedJWT()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got non-nil error: %#v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(wantJWT, gotJWT) {
|
||||||
|
t.Fatalf("incorrect JWT returned from verifiedJWT: want=%#v got=%#v", wantJWT, gotJWT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeRoundTripper struct {
|
||||||
|
Request *http.Request
|
||||||
|
resp *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
r.Request = req
|
||||||
|
return r.resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticatedTransportRoundTrip(t *testing.T) {
|
||||||
|
rr := &fakeRoundTripper{nil, &http.Response{StatusCode: http.StatusOK}}
|
||||||
|
at := &AuthenticatedTransport{
|
||||||
|
TokenRefresher: &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return nil },
|
||||||
|
},
|
||||||
|
RoundTripper: rr,
|
||||||
|
jwt: jose.JWT{RawPayload: "1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := http.Request{}
|
||||||
|
_, err := at.RoundTrip(&req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(req, http.Request{}) {
|
||||||
|
t.Errorf("http.Request object was modified")
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []string{"Bearer .1."}
|
||||||
|
got := rr.Request.Header["Authorization"]
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Errorf("incorrect Authorization header: want=%#v got=%#v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticatedTransportRoundTripRefreshFail(t *testing.T) {
|
||||||
|
rr := &fakeRoundTripper{nil, &http.Response{StatusCode: http.StatusOK}}
|
||||||
|
at := &AuthenticatedTransport{
|
||||||
|
TokenRefresher: &staticTokenRefresher{
|
||||||
|
verify: func(jose.JWT) error { return errors.New("fail!") },
|
||||||
|
refresh: func() (jose.JWT, error) { return jose.JWT{}, errors.New("fail!") },
|
||||||
|
},
|
||||||
|
RoundTripper: rr,
|
||||||
|
jwt: jose.JWT{RawPayload: "1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := at.RoundTrip(&http.Request{})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected non-nil error")
|
||||||
|
}
|
||||||
|
}
|
109
vendor/github.com/coreos/go-oidc/oidc/util.go
generated
vendored
Normal file
109
vendor/github.com/coreos/go-oidc/oidc/util.go
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestTokenExtractor funcs extract a raw encoded token from a request.
|
||||||
|
type RequestTokenExtractor func(r *http.Request) (string, error)
|
||||||
|
|
||||||
|
// ExtractBearerToken is a RequestTokenExtractor which extracts a bearer token from a request's
|
||||||
|
// Authorization header.
|
||||||
|
func ExtractBearerToken(r *http.Request) (string, error) {
|
||||||
|
ah := r.Header.Get("Authorization")
|
||||||
|
if ah == "" {
|
||||||
|
return "", errors.New("missing Authorization header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" {
|
||||||
|
return "", errors.New("should be a bearer token")
|
||||||
|
}
|
||||||
|
|
||||||
|
val := ah[7:]
|
||||||
|
if len(val) == 0 {
|
||||||
|
return "", errors.New("bearer token is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CookieTokenExtractor returns a RequestTokenExtractor which extracts a token from the named cookie in a request.
|
||||||
|
func CookieTokenExtractor(cookieName string) RequestTokenExtractor {
|
||||||
|
return func(r *http.Request) (string, error) {
|
||||||
|
ck, err := r.Cookie(cookieName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("token cookie not found in request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ck.Value == "" {
|
||||||
|
return "", errors.New("token cookie found but is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ck.Value, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClaims(iss, sub string, aud interface{}, iat, exp time.Time) jose.Claims {
|
||||||
|
return jose.Claims{
|
||||||
|
// required
|
||||||
|
"iss": iss,
|
||||||
|
"sub": sub,
|
||||||
|
"aud": aud,
|
||||||
|
"iat": iat.Unix(),
|
||||||
|
"exp": exp.Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenClientID(hostport string) (string, error) {
|
||||||
|
b, err := randBytes(32)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var host string
|
||||||
|
if strings.Contains(hostport, ":") {
|
||||||
|
host, _, err = net.SplitHostPort(hostport)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
host = hostport
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s@%s", base64.URLEncoding.EncodeToString(b), host), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randBytes(n int) ([]byte, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
got, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if n != got {
|
||||||
|
return nil, errors.New("unable to generate enough random data")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// urlEqual checks two urls for equality using only the host and path portions.
|
||||||
|
func urlEqual(url1, url2 string) bool {
|
||||||
|
u1, err := url.Parse(url1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
u2, err := url.Parse(url2)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToLower(u1.Host+u1.Path) == strings.ToLower(u2.Host+u2.Path)
|
||||||
|
}
|
110
vendor/github.com/coreos/go-oidc/oidc/util_test.go
generated
vendored
Normal file
110
vendor/github.com/coreos/go-oidc/oidc/util_test.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCookieTokenExtractorInvalid(t *testing.T) {
|
||||||
|
ckName := "tokenCookie"
|
||||||
|
tests := []*http.Cookie{
|
||||||
|
&http.Cookie{},
|
||||||
|
&http.Cookie{Name: ckName},
|
||||||
|
&http.Cookie{Name: ckName, Value: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
r, _ := http.NewRequest("", "", nil)
|
||||||
|
r.AddCookie(tt)
|
||||||
|
_, err := CookieTokenExtractor(ckName)(r)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: want: error for invalid cookie token, got: no error.", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCookieTokenExtractorValid(t *testing.T) {
|
||||||
|
validToken := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
|
||||||
|
ckName := "tokenCookie"
|
||||||
|
tests := []*http.Cookie{
|
||||||
|
&http.Cookie{Name: ckName, Value: "some non-empty value"},
|
||||||
|
&http.Cookie{Name: ckName, Value: validToken},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
r, _ := http.NewRequest("", "", nil)
|
||||||
|
r.AddCookie(tt)
|
||||||
|
_, err := CookieTokenExtractor(ckName)(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: want: valid cookie with no error, got: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractBearerTokenInvalid(t *testing.T) {
|
||||||
|
tests := []string{"", "x", "Bearer", "xxxxxxx", "Bearer "}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
r, _ := http.NewRequest("", "", nil)
|
||||||
|
r.Header.Add("Authorization", tt)
|
||||||
|
_, err := ExtractBearerToken(r)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("case %d: want: invalid Authorization header, got: valid Authorization header.", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractBearerTokenValid(t *testing.T) {
|
||||||
|
validToken := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
|
||||||
|
tests := []string{
|
||||||
|
fmt.Sprintf("Bearer %s", validToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
r, _ := http.NewRequest("", "", nil)
|
||||||
|
r.Header.Add("Authorization", tt)
|
||||||
|
_, err := ExtractBearerToken(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: want: valid Authorization header, got: invalid Authorization header: %v.", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClaims(t *testing.T) {
|
||||||
|
issAt := time.Date(2, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
expAt := time.Date(2, time.January, 1, 1, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
want := jose.Claims{
|
||||||
|
"iss": "https://example.com",
|
||||||
|
"sub": "user-123",
|
||||||
|
"aud": "client-abc",
|
||||||
|
"iat": issAt.Unix(),
|
||||||
|
"exp": expAt.Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
got := NewClaims("https://example.com", "user-123", "client-abc", issAt, expAt)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Fatalf("want=%#v got=%#v", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
want2 := jose.Claims{
|
||||||
|
"iss": "https://example.com",
|
||||||
|
"sub": "user-123",
|
||||||
|
"aud": []string{"client-abc", "client-def"},
|
||||||
|
"iat": issAt.Unix(),
|
||||||
|
"exp": expAt.Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
got2 := NewClaims("https://example.com", "user-123", []string{"client-abc", "client-def"}, issAt, expAt)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(want2, got2) {
|
||||||
|
t.Fatalf("want=%#v got=%#v", want2, got2)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
190
vendor/github.com/coreos/go-oidc/oidc/verification.go
generated
vendored
Normal file
190
vendor/github.com/coreos/go-oidc/oidc/verification.go
generated
vendored
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
"github.com/coreos/go-oidc/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
func VerifySignature(jwt jose.JWT, keys []key.PublicKey) (bool, error) {
|
||||||
|
jwtBytes := []byte(jwt.Data())
|
||||||
|
for _, k := range keys {
|
||||||
|
v, err := k.Verifier()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if v.Verify(jwt.Signature, jwtBytes) == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsString returns true if the given string(needle) is found
|
||||||
|
// in the string array(haystack).
|
||||||
|
func containsString(needle string, haystack []string) bool {
|
||||||
|
for _, v := range haystack {
|
||||||
|
if v == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify claims in accordance with OIDC spec
|
||||||
|
// http://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
|
||||||
|
func VerifyClaims(jwt jose.JWT, issuer, clientID string) error {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
claims, err := jwt.Claims()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ident, err := IdentityFromClaims(claims)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ident.ExpiresAt.Before(now) {
|
||||||
|
return errors.New("token is expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
// iss REQUIRED. Issuer Identifier for the Issuer of the response.
|
||||||
|
// The iss value is a case sensitive URL using the https scheme that contains scheme,
|
||||||
|
// host, and optionally, port number and path components and no query or fragment components.
|
||||||
|
if iss, exists := claims["iss"].(string); exists {
|
||||||
|
if !urlEqual(iss, issuer) {
|
||||||
|
return fmt.Errorf("invalid claim value: 'iss'. expected=%s, found=%s.", issuer, iss)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("missing claim: 'iss'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// iat REQUIRED. Time at which the JWT was issued.
|
||||||
|
// Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z
|
||||||
|
// as measured in UTC until the date/time.
|
||||||
|
if _, exists := claims["iat"].(float64); !exists {
|
||||||
|
return errors.New("missing claim: 'iat'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// aud REQUIRED. Audience(s) that this ID Token is intended for.
|
||||||
|
// It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.
|
||||||
|
// It MAY also contain identifiers for other audiences. In the general case, the aud
|
||||||
|
// value is an array of case sensitive strings. In the common special case when there
|
||||||
|
// is one audience, the aud value MAY be a single case sensitive string.
|
||||||
|
if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
|
||||||
|
if aud != clientID {
|
||||||
|
return fmt.Errorf("invalid claims, 'aud' claim and 'client_id' do not match, aud=%s, client_id=%s", aud, clientID)
|
||||||
|
}
|
||||||
|
} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
|
||||||
|
if !containsString(clientID, aud) {
|
||||||
|
return fmt.Errorf("invalid claims, cannot find 'client_id' in 'aud' claim, aud=%v, client_id=%s", aud, clientID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyClientClaims verifies all the required claims are valid for a "client credentials" JWT.
|
||||||
|
// Returns the client ID if valid, or an error if invalid.
|
||||||
|
func VerifyClientClaims(jwt jose.JWT, issuer string) (string, error) {
|
||||||
|
claims, err := jwt.Claims()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse JWT claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iss, ok, err := claims.StringClaim("iss")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse 'iss' claim: %v", err)
|
||||||
|
} else if !ok {
|
||||||
|
return "", errors.New("missing required 'iss' claim")
|
||||||
|
} else if !urlEqual(iss, issuer) {
|
||||||
|
return "", fmt.Errorf("'iss' claim does not match expected issuer, iss=%s", iss)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, ok, err := claims.StringClaim("sub")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse 'sub' claim: %v", err)
|
||||||
|
} else if !ok {
|
||||||
|
return "", errors.New("missing required 'sub' claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
|
||||||
|
if aud != sub {
|
||||||
|
return "", fmt.Errorf("invalid claims, 'aud' claim and 'sub' claim do not match, aud=%s, sub=%s", aud, sub)
|
||||||
|
}
|
||||||
|
} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
|
||||||
|
if !containsString(sub, aud) {
|
||||||
|
return "", fmt.Errorf("invalid claims, cannot find 'sud' in 'aud' claim, aud=%v, sub=%s", aud, sub)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
exp, ok, err := claims.TimeClaim("exp")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse 'exp' claim: %v", err)
|
||||||
|
} else if !ok {
|
||||||
|
return "", errors.New("missing required 'exp' claim")
|
||||||
|
} else if exp.Before(now) {
|
||||||
|
return "", fmt.Errorf("token already expired at: %v", exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWTVerifier struct {
|
||||||
|
issuer string
|
||||||
|
clientID string
|
||||||
|
syncFunc func() error
|
||||||
|
keysFunc func() []key.PublicKey
|
||||||
|
clock clockwork.Clock
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJWTVerifier(issuer, clientID string, syncFunc func() error, keysFunc func() []key.PublicKey) JWTVerifier {
|
||||||
|
return JWTVerifier{
|
||||||
|
issuer: issuer,
|
||||||
|
clientID: clientID,
|
||||||
|
syncFunc: syncFunc,
|
||||||
|
keysFunc: keysFunc,
|
||||||
|
clock: clockwork.NewRealClock(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *JWTVerifier) Verify(jwt jose.JWT) error {
|
||||||
|
// Verify claims before verifying the signature. This is an optimization to throw out
|
||||||
|
// tokens we know are invalid without undergoing an expensive signature check and
|
||||||
|
// possibly a re-sync event.
|
||||||
|
if err := VerifyClaims(jwt, v.issuer, v.clientID); err != nil {
|
||||||
|
return fmt.Errorf("oidc: JWT claims invalid: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := VerifySignature(jwt, v.keysFunc())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
|
||||||
|
} else if ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = v.syncFunc(); err != nil {
|
||||||
|
return fmt.Errorf("oidc: failed syncing KeySet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = VerifySignature(jwt, v.keysFunc())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
|
||||||
|
} else if !ok {
|
||||||
|
return errors.New("oidc: unable to verify JWT signature: no matching keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
380
vendor/github.com/coreos/go-oidc/oidc/verification_test.go
generated
vendored
Normal file
380
vendor/github.com/coreos/go-oidc/oidc/verification_test.go
generated
vendored
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/jose"
|
||||||
|
"github.com/coreos/go-oidc/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVerifyClientClaims(t *testing.T) {
|
||||||
|
validIss := "https://example.com"
|
||||||
|
validClientID := "valid-client"
|
||||||
|
now := time.Now()
|
||||||
|
tomorrow := now.Add(24 * time.Hour)
|
||||||
|
header := jose.JOSEHeader{
|
||||||
|
jose.HeaderKeyAlgorithm: "test-alg",
|
||||||
|
jose.HeaderKeyID: "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
claims jose.Claims
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
// valid token
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": validClientID,
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// valid token, ('aud' claim is []string)
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": []string{"foo", validClientID},
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// valid token, ('aud' claim is []interface{})
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": []interface{}{"foo", validClientID},
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
// missing 'iss' claim
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": validClientID,
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// invalid 'iss' claim
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": "INVALID",
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": validClientID,
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// missing 'sub' claim
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"aud": validClientID,
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// invalid 'sub' claim
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": "INVALID",
|
||||||
|
"aud": validClientID,
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// missing 'aud' claim
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// invalid 'aud' claim
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": "INVALID",
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// invalid 'aud' claim
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": []string{"INVALID1", "INVALID2"},
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// invalid 'aud' type
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": struct{}{},
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(tomorrow.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
// expired
|
||||||
|
{
|
||||||
|
claims: jose.Claims{
|
||||||
|
"iss": validIss,
|
||||||
|
"sub": validClientID,
|
||||||
|
"aud": validClientID,
|
||||||
|
"iat": float64(now.Unix()),
|
||||||
|
"exp": float64(now.Unix()),
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
jwt, err := jose.NewJWT(header, tt.claims)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: Failed to generate JWT, error=%v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := VerifyClientClaims(jwt, validIss)
|
||||||
|
if tt.ok {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: unexpected error, err=%v", i, err)
|
||||||
|
}
|
||||||
|
if got != validClientID {
|
||||||
|
t.Errorf("case %d: incorrect client ID, want=%s, got=%s", i, validClientID, got)
|
||||||
|
}
|
||||||
|
} else if err == nil {
|
||||||
|
t.Errorf("case %d: expected error but err is nil", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJWTVerifier(t *testing.T) {
|
||||||
|
iss := "http://example.com"
|
||||||
|
now := time.Now()
|
||||||
|
future12 := now.Add(12 * time.Hour)
|
||||||
|
past36 := now.Add(-36 * time.Hour)
|
||||||
|
past12 := now.Add(-12 * time.Hour)
|
||||||
|
|
||||||
|
priv1, err := key.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate private key, error=%v", err)
|
||||||
|
}
|
||||||
|
pk1 := *key.NewPublicKey(priv1.JWK())
|
||||||
|
|
||||||
|
priv2, err := key.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate private key, error=%v", err)
|
||||||
|
}
|
||||||
|
pk2 := *key.NewPublicKey(priv2.JWK())
|
||||||
|
|
||||||
|
newJWT := func(issuer, subject string, aud interface{}, issuedAt, exp time.Time, signer jose.Signer) jose.JWT {
|
||||||
|
jwt, err := jose.NewSignedJWT(NewClaims(issuer, subject, aud, issuedAt, exp), signer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return *jwt
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
verifier JWTVerifier
|
||||||
|
jwt jose.JWT
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "JWT signed with available key",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() []key.PublicKey {
|
||||||
|
return []key.PublicKey{pk1}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv1.Signer()),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JWT signed with available key, with bad claims",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() []key.PublicKey {
|
||||||
|
return []key.PublicKey{pk1}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", "YYY", past12, future12, priv1.Signer()),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "JWT signed with available key",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() []key.PublicKey {
|
||||||
|
return []key.PublicKey{pk1}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", []string{"YYY", "ZZZ"}, past12, future12, priv1.Signer()),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "expired JWT signed with available key",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() []key.PublicKey {
|
||||||
|
return []key.PublicKey{pk1}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", "XXX", past36, past12, priv1.Signer()),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "JWT signed with unrecognized key, verifiable after sync",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() func() []key.PublicKey {
|
||||||
|
var i int
|
||||||
|
return func() []key.PublicKey {
|
||||||
|
defer func() { i++ }()
|
||||||
|
return [][]key.PublicKey{
|
||||||
|
[]key.PublicKey{pk1},
|
||||||
|
[]key.PublicKey{pk2},
|
||||||
|
}[i]
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", "XXX", past36, future12, priv2.Signer()),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "JWT signed with unrecognized key, not verifiable after sync",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() []key.PublicKey {
|
||||||
|
return []key.PublicKey{pk1}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv2.Signer()),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "verifier gets no keys from keysFunc, still not verifiable after sync",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() []key.PublicKey {
|
||||||
|
return []key.PublicKey{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv1.Signer()),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "verifier gets no keys from keysFunc, verifiable after sync",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() func() []key.PublicKey {
|
||||||
|
var i int
|
||||||
|
return func() []key.PublicKey {
|
||||||
|
defer func() { i++ }()
|
||||||
|
return [][]key.PublicKey{
|
||||||
|
[]key.PublicKey{},
|
||||||
|
[]key.PublicKey{pk2},
|
||||||
|
}[i]
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv2.Signer()),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "JWT signed with available key, 'aud' is a string array",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error { return nil },
|
||||||
|
keysFunc: func() []key.PublicKey {
|
||||||
|
return []key.PublicKey{pk1}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jwt: newJWT(iss, "XXX", []string{"ZZZ", "XXX"}, past12, future12, priv1.Signer()),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid issuer claim shouldn't trigger sync",
|
||||||
|
verifier: JWTVerifier{
|
||||||
|
issuer: "example.com",
|
||||||
|
clientID: "XXX",
|
||||||
|
syncFunc: func() error {
|
||||||
|
t.Errorf("invalid issuer claim shouldn't trigger a sync")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
keysFunc: func() func() []key.PublicKey {
|
||||||
|
var i int
|
||||||
|
return func() []key.PublicKey {
|
||||||
|
defer func() { i++ }()
|
||||||
|
return [][]key.PublicKey{
|
||||||
|
[]key.PublicKey{},
|
||||||
|
[]key.PublicKey{pk2},
|
||||||
|
}[i]
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
jwt: newJWT("invalid-issuer", "XXX", []string{"ZZZ", "XXX"}, past12, future12, priv2.Signer()),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
err := tt.verifier.Verify(tt.jwt)
|
||||||
|
if tt.wantErr && (err == nil) {
|
||||||
|
t.Errorf("case %q: wanted non-nil error", tt.name)
|
||||||
|
} else if !tt.wantErr && (err != nil) {
|
||||||
|
t.Errorf("case %q: wanted nil error, got %v", tt.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
vendor/github.com/coreos/go-oidc/oidc_test.go
generated
vendored
Normal file
21
vendor/github.com/coreos/go-oidc/oidc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientContext(t *testing.T) {
|
||||||
|
myClient := &http.Client{}
|
||||||
|
|
||||||
|
ctx := ClientContext(context.Background(), myClient)
|
||||||
|
|
||||||
|
gotClient := clientFromContext(ctx)
|
||||||
|
|
||||||
|
// Compare pointer values.
|
||||||
|
if gotClient != myClient {
|
||||||
|
t.Fatal("clientFromContext did not return the value set by ClientContext")
|
||||||
|
}
|
||||||
|
}
|
15
vendor/github.com/coreos/go-oidc/test
generated
vendored
Executable file
15
vendor/github.com/coreos/go-oidc/test
generated
vendored
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Filter out any files with a !golint build tag.
|
||||||
|
LINTABLE=$( go list -tags=golint -f '
|
||||||
|
{{- range $i, $file := .GoFiles -}}
|
||||||
|
{{ $file }} {{ end }}
|
||||||
|
{{ range $i, $file := .TestGoFiles -}}
|
||||||
|
{{ $file }} {{ end }}' github.com/coreos/go-oidc )
|
||||||
|
|
||||||
|
go test -v -i -race github.com/coreos/go-oidc
|
||||||
|
go test -v -race github.com/coreos/go-oidc
|
||||||
|
golint $LINTABLE
|
||||||
|
go vet github.com/coreos/go-oidc
|
263
vendor/github.com/coreos/go-oidc/verify.go
generated
vendored
Normal file
263
vendor/github.com/coreos/go-oidc/verify.go
generated
vendored
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IDTokenVerifier provides verification for ID Tokens.
|
||||||
|
type IDTokenVerifier struct {
|
||||||
|
keySet *remoteKeySet
|
||||||
|
config *verificationConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// verificationConfig is the unexported configuration for an IDTokenVerifier.
|
||||||
|
//
|
||||||
|
// Users interact with this struct using a VerificationOption.
|
||||||
|
type verificationConfig struct {
|
||||||
|
issuer string
|
||||||
|
// If provided, this value must be in the ID Token audiences.
|
||||||
|
audience string
|
||||||
|
// If not nil, check the expiry of the id token.
|
||||||
|
checkExpiry func() time.Time
|
||||||
|
// If specified, only these sets of algorithms may be used to sign the JWT.
|
||||||
|
requiredAlgs []string
|
||||||
|
// If not nil, don't verify nonce.
|
||||||
|
nonceSource NonceSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerificationOption provides additional checks on ID Tokens.
|
||||||
|
type VerificationOption interface {
|
||||||
|
// Unexport this method so other packages can't implement this interface.
|
||||||
|
updateConfig(c *verificationConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
|
||||||
|
//
|
||||||
|
// The returned IDTokenVerifier is tied to the Provider's context and its behavior is
|
||||||
|
// undefined once the Provider's context is canceled.
|
||||||
|
func (p *Provider) Verifier(options ...VerificationOption) *IDTokenVerifier {
|
||||||
|
config := &verificationConfig{issuer: p.issuer}
|
||||||
|
for _, option := range options {
|
||||||
|
option.updateConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVerifier(p.remoteKeySet, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVerifier(keySet *remoteKeySet, config *verificationConfig) *IDTokenVerifier {
|
||||||
|
// As discussed in the godocs for VerifrySigningAlg, because almost all providers
|
||||||
|
// only support RS256, default to only allowing it.
|
||||||
|
if len(config.requiredAlgs) == 0 {
|
||||||
|
config.requiredAlgs = []string{RS256}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IDTokenVerifier{
|
||||||
|
keySet: keySet,
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJWT(p string) ([]byte, error) {
|
||||||
|
parts := strings.Split(p, ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
|
||||||
|
}
|
||||||
|
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(sli []string, ele string) bool {
|
||||||
|
for _, s := range sli {
|
||||||
|
if s == ele {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify parses a raw ID Token, verifies it's been signed by the provider, preforms
|
||||||
|
// any additional checks passed as VerifictionOptions, and returns the payload.
|
||||||
|
//
|
||||||
|
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||||
|
//
|
||||||
|
// oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Extract the ID Token from oauth2 token.
|
||||||
|
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
// if !ok {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// token, err := verifier.Verify(ctx, rawIDToken)
|
||||||
|
//
|
||||||
|
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
|
||||||
|
jws, err := jose.ParseSigned(rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: mallformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw out tokens with invalid claims before trying to verify the token. This lets
|
||||||
|
// us do cheap checks before possibly re-syncing keys.
|
||||||
|
payload, err := parseJWT(rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
var token idToken
|
||||||
|
if err := json.Unmarshal(payload, &token); err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &IDToken{
|
||||||
|
Issuer: token.Issuer,
|
||||||
|
Subject: token.Subject,
|
||||||
|
Audience: []string(token.Audience),
|
||||||
|
Expiry: time.Time(token.Expiry),
|
||||||
|
IssuedAt: time.Time(token.IssuedAt),
|
||||||
|
Nonce: token.Nonce,
|
||||||
|
claims: payload,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check issuer.
|
||||||
|
if t.Issuer != v.config.issuer {
|
||||||
|
return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.config.issuer, t.Issuer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a client ID has been provided, make sure it's part of the audience.
|
||||||
|
if v.config.audience != "" {
|
||||||
|
if !contains(t.Audience, v.config.audience) {
|
||||||
|
return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.audience, t.Audience)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a set of required algorithms has been provided, ensure that the signatures use those.
|
||||||
|
var keyIDs, gotAlgs []string
|
||||||
|
for _, sig := range jws.Signatures {
|
||||||
|
if len(v.config.requiredAlgs) == 0 || contains(v.config.requiredAlgs, sig.Header.Algorithm) {
|
||||||
|
keyIDs = append(keyIDs, sig.Header.KeyID)
|
||||||
|
} else {
|
||||||
|
gotAlgs = append(gotAlgs, sig.Header.Algorithm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(keyIDs) == 0 {
|
||||||
|
return nil, fmt.Errorf("oidc: no signatures use a require algorithm, expected %q got %q", v.config.requiredAlgs, gotAlgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get keys from the remote key set. This may trigger a re-sync.
|
||||||
|
keys, err := v.keySet.keysWithID(ctx, keyIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: get keys for id token: %v", err)
|
||||||
|
}
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil, fmt.Errorf("oidc: no keys match signature ID(s) %q", keyIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to use a key to validate the signature.
|
||||||
|
var gotPayload []byte
|
||||||
|
for _, key := range keys {
|
||||||
|
if p, err := jws.Verify(&key); err == nil {
|
||||||
|
gotPayload = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(gotPayload) == 0 {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to verify id token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the payload returned by the square actually matches the payload parsed earlier.
|
||||||
|
if !bytes.Equal(gotPayload, payload) {
|
||||||
|
return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the nonce after we've verified the token. We don't want to allow unverified
|
||||||
|
// payloads to trigger a nonce lookup.
|
||||||
|
if v.config.nonceSource != nil {
|
||||||
|
if err := v.config.nonceSource.ClaimNonce(t.Nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAudience ensures that an ID Token was issued for the specific client.
|
||||||
|
//
|
||||||
|
// Note that a verified token may be valid for other clients, as OpenID Connect allows a token to have
|
||||||
|
// multiple audiences.
|
||||||
|
func VerifyAudience(clientID string) VerificationOption {
|
||||||
|
return clientVerifier{clientID}
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientVerifier struct {
|
||||||
|
clientID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v clientVerifier) updateConfig(c *verificationConfig) {
|
||||||
|
c.audience = v.clientID
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyExpiry ensures that an ID Token has not expired.
|
||||||
|
func VerifyExpiry() VerificationOption {
|
||||||
|
return expiryVerifier{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type expiryVerifier struct{}
|
||||||
|
|
||||||
|
func (v expiryVerifier) updateConfig(c *verificationConfig) {
|
||||||
|
c.checkExpiry = time.Now
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySigningAlg enforces that an ID Token is signed by a specific signing algorithm.
|
||||||
|
//
|
||||||
|
// Because so many providers only support RS256, if this verifiction option isn't used,
|
||||||
|
// the IDTokenVerifier defaults to only allowing RS256.
|
||||||
|
func VerifySigningAlg(allowedAlgs ...string) VerificationOption {
|
||||||
|
return algVerifier{allowedAlgs}
|
||||||
|
}
|
||||||
|
|
||||||
|
type algVerifier struct {
|
||||||
|
algs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v algVerifier) updateConfig(c *verificationConfig) {
|
||||||
|
c.requiredAlgs = v.algs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nonce returns an auth code option which requires the ID Token created by the
|
||||||
|
// OpenID Connect provider to contain the specified nonce.
|
||||||
|
func Nonce(nonce string) oauth2.AuthCodeOption {
|
||||||
|
return oauth2.SetAuthURLParam("nonce", nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonceSource represents a source which can verify a nonce is valid and has not
|
||||||
|
// been claimed before.
|
||||||
|
type NonceSource interface {
|
||||||
|
ClaimNonce(nonce string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyNonce ensures that the ID Token contains a nonce which can be claimed by the nonce source.
|
||||||
|
func VerifyNonce(source NonceSource) VerificationOption {
|
||||||
|
return nonceVerifier{source}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nonceVerifier struct {
|
||||||
|
nonceSource NonceSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n nonceVerifier) updateConfig(c *verificationConfig) {
|
||||||
|
c.nonceSource = n.nonceSource
|
||||||
|
}
|
265
vendor/github.com/coreos/go-oidc/verify_test.go
generated
vendored
Normal file
265
vendor/github.com/coreos/go-oidc/verify_test.go
generated
vendored
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVerify(t *testing.T) {
|
||||||
|
tests := []verificationTest{
|
||||||
|
{
|
||||||
|
name: "good token",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid signature",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_1},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid issuer",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://bar",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_0},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyAudience(t *testing.T) {
|
||||||
|
tests := []verificationTest{
|
||||||
|
{
|
||||||
|
name: "good audience",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
Audience: []string{"client1"},
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
audience: "client1",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mismatched audience",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
Audience: []string{"client2"},
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
audience: "client1",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_0},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple audiences, one matches",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
Audience: []string{"client2", "client1"},
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
audience: "client1",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySigningAlg(t *testing.T) {
|
||||||
|
tests := []verificationTest{
|
||||||
|
{
|
||||||
|
name: "default signing alg",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
signAlg: RS256, // By default we only support RS256.
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad signing alg",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
},
|
||||||
|
signKey: testKeyRSA_2048_0_Priv,
|
||||||
|
signAlg: RS512,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyRSA_2048_0},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ecdsa signing",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
requiredAlgs: []string{ES384},
|
||||||
|
},
|
||||||
|
signAlg: ES384,
|
||||||
|
signKey: testKeyECDSA_384_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyECDSA_384_0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one of many supported",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
requiredAlgs: []string{RS256, ES384},
|
||||||
|
},
|
||||||
|
signAlg: ES384,
|
||||||
|
signKey: testKeyECDSA_384_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyECDSA_384_0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not in requiredAlgs",
|
||||||
|
idToken: idToken{
|
||||||
|
Issuer: "https://foo",
|
||||||
|
},
|
||||||
|
config: verificationConfig{
|
||||||
|
issuer: "https://foo",
|
||||||
|
requiredAlgs: []string{RS256, ES512},
|
||||||
|
},
|
||||||
|
signAlg: ES384,
|
||||||
|
signKey: testKeyECDSA_384_0_Priv,
|
||||||
|
pubKeys: []jose.JSONWebKey{testKeyECDSA_384_0},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test.run(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type verificationTest struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
// ID token claims and a signing key to create the JWT.
|
||||||
|
idToken idToken
|
||||||
|
signKey jose.JSONWebKey
|
||||||
|
// If supplied use this signing algorithm. If not, guess
|
||||||
|
// from the signingKey.
|
||||||
|
signAlg string
|
||||||
|
|
||||||
|
config verificationConfig
|
||||||
|
pubKeys []jose.JSONWebKey
|
||||||
|
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func algForKey(t *testing.T, k jose.JSONWebKey) string {
|
||||||
|
switch key := k.Key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return RS256
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
name := key.PublicKey.Params().Name
|
||||||
|
switch name {
|
||||||
|
case elliptic.P256().Params().Name:
|
||||||
|
return ES256
|
||||||
|
case elliptic.P384().Params().Name:
|
||||||
|
return ES384
|
||||||
|
case elliptic.P521().Params().Name:
|
||||||
|
return ES512
|
||||||
|
}
|
||||||
|
t.Fatalf("unsupported ecdsa curve: %s", name)
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported key type %T", key)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v verificationTest) run(t *testing.T) {
|
||||||
|
payload, err := json.Marshal(v.idToken)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
signingAlg := v.signAlg
|
||||||
|
if signingAlg == "" {
|
||||||
|
signingAlg = algForKey(t, v.signKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := jose.NewSigner(jose.SigningKey{
|
||||||
|
Algorithm: jose.SignatureAlgorithm(signingAlg),
|
||||||
|
Key: &v.signKey,
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jws, err := signer.Sign(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := jws.CompactSerialize()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t0 := time.Now()
|
||||||
|
now := func() time.Time { return t0 }
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
server := httptest.NewServer(newKeyServer(v.pubKeys...))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
verifier := newVerifier(newRemoteKeySet(ctx, server.URL, now), &v.config)
|
||||||
|
|
||||||
|
if _, err := verifier.Verify(ctx, token); err != nil {
|
||||||
|
if !v.wantErr {
|
||||||
|
t.Errorf("%s: verify %v", v.name, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v.wantErr {
|
||||||
|
t.Errorf("%s: expected error", v.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
vendor/github.com/ericchiang/oidc/.travis.yml
generated
vendored
13
vendor/github.com/ericchiang/oidc/.travis.yml
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.5.4
|
|
||||||
- 1.6.3
|
|
||||||
- tip
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
106
vendor/github.com/ericchiang/oidc/README.md
generated
vendored
106
vendor/github.com/ericchiang/oidc/README.md
generated
vendored
|
@ -1,106 +0,0 @@
|
||||||
# OpenID Connect client support for Go
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/ericchiang/oidc?status.svg)](https://godoc.org/github.com/ericchiang/oidc)
|
|
||||||
|
|
||||||
This package implements OpenID Connect client logic for the golang.org/x/oauth2 package.
|
|
||||||
|
|
||||||
```go
|
|
||||||
provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure an OpenID Connect aware OAuth2 client.
|
|
||||||
oauth2Config := oauth2.Config{
|
|
||||||
ClientID: clientID,
|
|
||||||
ClientSecret: clientSecret,
|
|
||||||
RedirectURL: redirectURL,
|
|
||||||
Endpoint: provider.Endpoint(),
|
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
OAuth2 redirects are unchanged.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
For callbacks the provider can be used to query for [user information](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) such as email.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Verify state...
|
|
||||||
|
|
||||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userinfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Or the provider can be used to verify and inspect the OpenID Connect
|
|
||||||
[ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) in the
|
|
||||||
[token response](https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse).
|
|
||||||
|
|
||||||
```go
|
|
||||||
verifier := provider.NewVerifier(ctx)
|
|
||||||
```
|
|
||||||
|
|
||||||
The verifier itself can be constructed with addition checks, such as verifing a
|
|
||||||
token was issued for a specific client or hasn't expired.
|
|
||||||
|
|
||||||
```go
|
|
||||||
verifier := provier.NewVerifier(ctx, oidc.VerifyAudience(clientID), oidc.VerifyExpiry())
|
|
||||||
```
|
|
||||||
|
|
||||||
The returned verifier can be used to ensure the ID Token (a JWT) is signed by the provider.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Verify state...
|
|
||||||
|
|
||||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the ID Token from oauth2 token.
|
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "No ID Token found", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the ID Token is signed by the provider.
|
|
||||||
idToken, err := verifier.Verify(rawIDToken)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal ID Token for expected custom claims.
|
|
||||||
var claims struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
EmailVerified bool `json:"email_verified"`
|
|
||||||
}
|
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
|
||||||
http.Error(w, "Failed to unmarshal ID Token claims: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
```
|
|
145
vendor/github.com/ericchiang/oidc/doc.go
generated
vendored
145
vendor/github.com/ericchiang/oidc/doc.go
generated
vendored
|
@ -1,145 +0,0 @@
|
||||||
/*
|
|
||||||
Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
|
|
||||||
|
|
||||||
provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure an OpenID Connect aware OAuth2 client.
|
|
||||||
oauth2Config := oauth2.Config{
|
|
||||||
ClientID: clientID,
|
|
||||||
ClientSecret: clientSecret,
|
|
||||||
RedirectURL: redirectURL,
|
|
||||||
Endpoint: provider.Endpoint(),
|
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
|
||||||
}
|
|
||||||
|
|
||||||
OAuth2 redirects are unchanged.
|
|
||||||
|
|
||||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
For callbacks the provider can be used to query for user information such as email.
|
|
||||||
|
|
||||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Verify state...
|
|
||||||
|
|
||||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userinfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
|
|
||||||
The provider also has the ability to verify ID Tokens.
|
|
||||||
|
|
||||||
verifier := provider.NewVerifier(ctx)
|
|
||||||
|
|
||||||
The returned verifier can be used to perform basic validation on ID Token issued by the provider,
|
|
||||||
including verifying the JWT signature. It then returns the payload.
|
|
||||||
|
|
||||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Verify state...
|
|
||||||
|
|
||||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the ID Token from oauth2 token.
|
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "No ID Token found", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the ID Token is signed by the provider.
|
|
||||||
idToken, err := verifier.Verify(rawIDToken)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal ID Token for expected custom claims.
|
|
||||||
var claims struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
EmailVerified bool `json:"email_verified"`
|
|
||||||
}
|
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
|
||||||
http.Error(w, "Failed to unmarshal ID Token custom claims: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
|
|
||||||
ID Token nonces are supported.
|
|
||||||
|
|
||||||
First, provide a nonce source for nonce validation. This will then be used to wrap the existing
|
|
||||||
provider ID Token verifier.
|
|
||||||
|
|
||||||
// A verifier which boths verifies the ID Token signature and nonce.
|
|
||||||
nonceEnabledVerifier := provider.NewVerifier(ctx, oidc.VerifyNonce(nonceSource))
|
|
||||||
|
|
||||||
For the redirect provide a nonce auth code option. This will be placed as a URL parameter during
|
|
||||||
the client redirect.
|
|
||||||
|
|
||||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
nonce, err := newNonce()
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
// Provide a nonce for the OpenID Connect ID Token.
|
|
||||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
The nonce enabled verifier can then be used to verify the nonce while unpacking the ID Token.
|
|
||||||
|
|
||||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Verify state...
|
|
||||||
|
|
||||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the ID Token from oauth2 token.
|
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "No ID Token found", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the ID Token is signed by the provider and verify the nonce.
|
|
||||||
idToken, err := nonceEnabledVerifier.Verify(rawIDToken)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue as above...
|
|
||||||
})
|
|
||||||
|
|
||||||
This package uses contexts to derive HTTP clients in the same way as the oauth2 package. To configure
|
|
||||||
a custom client, use the oauth2 packages HTTPClient context key when constructing the context.
|
|
||||||
|
|
||||||
myClient := &http.Client{}
|
|
||||||
|
|
||||||
myCtx := context.WithValue(parentCtx, oauth2.HTTPClient, myClient)
|
|
||||||
|
|
||||||
// NewProvider will use myClient to make the request.
|
|
||||||
provider, err := oidc.NewProvider(myCtx, "https://accounts.example.com")
|
|
||||||
*/
|
|
||||||
package oidc
|
|
15
vendor/github.com/ericchiang/oidc/examples/README.md
generated
vendored
15
vendor/github.com/ericchiang/oidc/examples/README.md
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
# Examples
|
|
||||||
|
|
||||||
These are example uses of the oidc package. Each requires a Google account and the
|
|
||||||
client ID and secret of a registered OAuth2 application. The client ID and secret
|
|
||||||
should be set as the following environment variables:
|
|
||||||
|
|
||||||
```
|
|
||||||
GOOGLE_OAUTH2_CLIENT_ID
|
|
||||||
GOOGLE_OAUTH2_CLIENT_SECRET
|
|
||||||
```
|
|
||||||
|
|
||||||
See Google's documentation on how to set up an OAuth2 app:
|
|
||||||
https://developers.google.com/identity/protocols/OpenIDConnect?hl=en
|
|
||||||
|
|
||||||
Note that one of the redirect URL's must be "http://127.0.0.1:5556/auth/google/callback"
|
|
7
vendor/github.com/ericchiang/oidc/internal/oidc.go
generated
vendored
7
vendor/github.com/ericchiang/oidc/internal/oidc.go
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
// Package internal contains support packages for the oidc package.
|
|
||||||
package internal
|
|
||||||
|
|
||||||
// ContextKey is just an empty struct. It exists so context keys can be an immutable
|
|
||||||
// public variable with a unique type. It's immutable because nobody else can create
|
|
||||||
// a ContextKey, being unexported.
|
|
||||||
type ContextKey struct{}
|
|
188
vendor/github.com/ericchiang/oidc/jwks.go
generated
vendored
188
vendor/github.com/ericchiang/oidc/jwks.go
generated
vendored
|
@ -1,188 +0,0 @@
|
||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pquerna/cachecontrol"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
jose "gopkg.in/square/go-jose.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// No matter what insist on caching keys. This is so our request code can be
|
|
||||||
// asynchronous from matching keys. If the request code retrieved keys that
|
|
||||||
// expired immediately, the goroutine to match a JWT to a key would always see
|
|
||||||
// expired keys.
|
|
||||||
//
|
|
||||||
// TODO(ericchiang): Review this logic.
|
|
||||||
var minCache = 2 * time.Minute
|
|
||||||
|
|
||||||
type cachedKeys struct {
|
|
||||||
keys map[string]jose.JsonWebKey // immutable
|
|
||||||
expiry time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type remoteKeySet struct {
|
|
||||||
client *http.Client
|
|
||||||
|
|
||||||
// "jwks_uri" from discovery.
|
|
||||||
keysURL string
|
|
||||||
|
|
||||||
// The value is always of type *cachedKeys.
|
|
||||||
//
|
|
||||||
// To ensure consistency always call keyCache.Store when holding cond.L.
|
|
||||||
keyCache atomic.Value
|
|
||||||
|
|
||||||
// cond.L guards all following fields. sync.Cond is used in place of a mutex
|
|
||||||
// so multiple processes can wait on a single request to update keys.
|
|
||||||
cond sync.Cond
|
|
||||||
// Is there an existing request to get the remote keys?
|
|
||||||
inflight bool
|
|
||||||
// If the last attempt to refresh keys failed, the error will be saved here.
|
|
||||||
//
|
|
||||||
// TODO(ericchiang): If a routine sets this before calling cond.Broadcast(),
|
|
||||||
// there's no guarentee that a routine calling cond.Wait() will actual see
|
|
||||||
// the error called by the previous routine. Since Broadcast() unlocks
|
|
||||||
// cond.L and Wait() must reacquire the lock, other routines waiting on the
|
|
||||||
// lock might acquire it first. Maybe just log the error?
|
|
||||||
lastErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRemoteKeySet(ctx context.Context, jwksURL string) *remoteKeySet {
|
|
||||||
r := &remoteKeySet{
|
|
||||||
client: contextClient(ctx),
|
|
||||||
keysURL: jwksURL,
|
|
||||||
cond: sync.Cond{L: new(sync.Mutex)},
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteKeySet) verifyJWT(jwt string) (payload []byte, err error) {
|
|
||||||
jws, err := jose.ParseSigned(jwt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing jwt: %v", err)
|
|
||||||
}
|
|
||||||
keyIDs := make([]string, len(jws.Signatures))
|
|
||||||
for i, signature := range jws.Signatures {
|
|
||||||
keyIDs[i] = signature.Header.KeyID
|
|
||||||
}
|
|
||||||
key, err := r.getKey(keyIDs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("oidc: %s", err)
|
|
||||||
}
|
|
||||||
return jws.Verify(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteKeySet) getKeyFromCache(keyIDs []string) (*jose.JsonWebKey, bool) {
|
|
||||||
cachedKeys, ok := r.keyCache.Load().(*cachedKeys)
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
if time.Now().After(cachedKeys.expiry) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
for _, keyID := range keyIDs {
|
|
||||||
if key, ok := cachedKeys.keys[keyID]; ok {
|
|
||||||
return &key, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteKeySet) getKey(keyIDs []string) (*jose.JsonWebKey, error) {
|
|
||||||
// Fast path. Just do an atomic load.
|
|
||||||
if key, ok := r.getKeyFromCache(keyIDs); ok {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Didn't find keys, use the slow path.
|
|
||||||
r.cond.L.Lock()
|
|
||||||
defer r.cond.L.Unlock()
|
|
||||||
|
|
||||||
// Check again within the mutex.
|
|
||||||
if key, ok := r.getKeyFromCache(keyIDs); ok {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys have expired or we're trying to verify a JWT we don't have a key for.
|
|
||||||
|
|
||||||
if !r.inflight {
|
|
||||||
// There isn't currently an inflight request to update keys, start a
|
|
||||||
// goroutine to do so.
|
|
||||||
r.inflight = true
|
|
||||||
go func() {
|
|
||||||
newKeys, newExpiry, err := requestKeys(r.client, r.keysURL)
|
|
||||||
|
|
||||||
r.cond.L.Lock()
|
|
||||||
defer r.cond.L.Unlock()
|
|
||||||
|
|
||||||
r.inflight = false
|
|
||||||
if err != nil {
|
|
||||||
r.lastErr = err
|
|
||||||
} else {
|
|
||||||
r.keyCache.Store(&cachedKeys{newKeys, newExpiry})
|
|
||||||
r.lastErr = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.cond.Broadcast() // Wake all r.cond.Wait() calls.
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for r.cond.Broadcast() to be called. This unlocks r.cond.L and
|
|
||||||
// reacquires it after its done waiting.
|
|
||||||
r.cond.Wait()
|
|
||||||
|
|
||||||
if key, ok := r.getKeyFromCache(keyIDs); ok {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
if r.lastErr != nil {
|
|
||||||
return nil, r.lastErr
|
|
||||||
}
|
|
||||||
return nil, errors.New("no signing keys can validate the signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestKeys(client *http.Client, keysURL string) (map[string]jose.JsonWebKey, time.Time, error) {
|
|
||||||
req, err := http.NewRequest("GET", keysURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, time.Time{}, fmt.Errorf("can't create request: %v", err)
|
|
||||||
}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, time.Time{}, fmt.Errorf("can't GET new keys %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
|
||||||
if err != nil {
|
|
||||||
return nil, time.Time{}, fmt.Errorf("can't fetch new keys: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, time.Time{}, fmt.Errorf("can't fetch new keys: %s %s", resp.Status, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
var keySet jose.JsonWebKeySet
|
|
||||||
if err := json.Unmarshal(body, &keySet); err != nil {
|
|
||||||
return nil, time.Time{}, fmt.Errorf("can't decode keys: %v %s", err, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := make(map[string]jose.JsonWebKey, len(keySet.Keys))
|
|
||||||
for _, key := range keySet.Keys {
|
|
||||||
keys[key.KeyID] = key
|
|
||||||
}
|
|
||||||
|
|
||||||
minExpiry := time.Now().Add(minCache)
|
|
||||||
|
|
||||||
if _, expiry, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{}); err == nil {
|
|
||||||
if minExpiry.Before(expiry) {
|
|
||||||
return keys, expiry, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keys, minExpiry, nil
|
|
||||||
}
|
|
285
vendor/github.com/ericchiang/oidc/jwks_test.go
generated
vendored
285
vendor/github.com/ericchiang/oidc/jwks_test.go
generated
vendored
File diff suppressed because one or more lines are too long
35
vendor/github.com/ericchiang/oidc/nonce.go
generated
vendored
35
vendor/github.com/ericchiang/oidc/nonce.go
generated
vendored
|
@ -1,35 +0,0 @@
|
||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Nonce returns an auth code option which requires the ID Token created by the
|
|
||||||
// OpenID Connect provider to contain the specified nonce.
|
|
||||||
func Nonce(nonce string) oauth2.AuthCodeOption {
|
|
||||||
return oauth2.SetAuthURLParam("nonce", nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonceSource represents a source which can verify a nonce is valid and has not
|
|
||||||
// been claimed before.
|
|
||||||
type NonceSource interface {
|
|
||||||
ClaimNonce(nonce string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyNonce ensures that the ID Token contains a nonce which can be claimed by the nonce source.
|
|
||||||
func VerifyNonce(source NonceSource) VerificationOption {
|
|
||||||
return nonceVerifier{source}
|
|
||||||
}
|
|
||||||
|
|
||||||
type nonceVerifier struct {
|
|
||||||
nonceSource NonceSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n nonceVerifier) verifyIDToken(token *IDToken) error {
|
|
||||||
if token.Nonce == "" {
|
|
||||||
return errors.New("oidc: no nonce present in ID Token")
|
|
||||||
}
|
|
||||||
return n.nonceSource.ClaimNonce(token.Nonce)
|
|
||||||
}
|
|
76
vendor/github.com/ericchiang/oidc/oidc_test.go
generated
vendored
76
vendor/github.com/ericchiang/oidc/oidc_test.go
generated
vendored
|
@ -1,76 +0,0 @@
|
||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClientVerifier(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
clientID string
|
|
||||||
aud []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
clientID: "1",
|
|
||||||
aud: []string{"1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
clientID: "1",
|
|
||||||
aud: []string{"2"},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
clientID: "1",
|
|
||||||
aud: []string{"2", "1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
clientID: "3",
|
|
||||||
aud: []string{"1", "2"},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range tests {
|
|
||||||
token := IDToken{Audience: tc.aud}
|
|
||||||
err := (clientVerifier{tc.clientID}).verifyIDToken(&token)
|
|
||||||
if err != nil && !tc.wantErr {
|
|
||||||
t.Errorf("case %d: %v", i)
|
|
||||||
}
|
|
||||||
if err == nil && tc.wantErr {
|
|
||||||
t.Errorf("case %d: expected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalAudience(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
data string
|
|
||||||
want audience
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{`"foo"`, audience{"foo"}, false},
|
|
||||||
{`["foo","bar"]`, audience{"foo", "bar"}, false},
|
|
||||||
{"foo", nil, true}, // invalid JSON
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
var a audience
|
|
||||||
if err := json.Unmarshal([]byte(tc.data), &a); err != nil {
|
|
||||||
if !tc.wantErr {
|
|
||||||
t.Errorf("failed to unmarshal %q: %v", tc.data, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
t.Errorf("did not expected to be able to unmarshal %q", tc.data)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tc.want, a) {
|
|
||||||
t.Errorf("from %q expected %q got %q", tc.data, tc.want, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
283
vendor/github.com/ericchiang/oidc/oidcproxy/main.go
generated
vendored
283
vendor/github.com/ericchiang/oidc/oidcproxy/main.go
generated
vendored
|
@ -1,283 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/gob"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ericchiang/oidc"
|
|
||||||
"github.com/gorilla/securecookie"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cookieName = "oidc-proxy"
|
|
||||||
// This header will be set by oidcproxy during authentication and
|
|
||||||
// passed to the backend.
|
|
||||||
emailHeaderName = "X-User-Email"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Session represents a logged in user's active session.
|
|
||||||
type Session struct {
|
|
||||||
Email string
|
|
||||||
Expires time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
gob.Register(&Session{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Flags.
|
|
||||||
issuer string
|
|
||||||
backend string
|
|
||||||
scopes string
|
|
||||||
allow string
|
|
||||||
httpAddr string
|
|
||||||
httpsAddr string
|
|
||||||
cookieExp time.Duration
|
|
||||||
|
|
||||||
// Set up during initial configuration.
|
|
||||||
oauth2Config = new(oauth2.Config)
|
|
||||||
oidcProvider *oidc.Provider
|
|
||||||
backendHandler *httputil.ReverseProxy
|
|
||||||
verifier *oidc.IDTokenVerifier
|
|
||||||
|
|
||||||
// Regexps of emails to allow.
|
|
||||||
allowEmail []*regexp.Regexp
|
|
||||||
|
|
||||||
nonceSource *memNonceSource
|
|
||||||
|
|
||||||
cookieEncrypter *securecookie.SecureCookie
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.StringVar(&issuer, "issuer", "https://accounts.google.com", "The issuer URL of the OpenID Connect provider.")
|
|
||||||
flag.StringVar(&backend, "backend", "", "The URL of the backened to proxy to.")
|
|
||||||
flag.StringVar(&oauth2Config.RedirectURL, "redirect-url", "", "A full OAuth2 redirect URL.")
|
|
||||||
flag.StringVar(&oauth2Config.ClientID, "client-id", "", "The client ID of the OAuth2 client.")
|
|
||||||
flag.StringVar(&oauth2Config.ClientSecret, "client-secret", "", "The client secret of the OAuth2 client.")
|
|
||||||
flag.StringVar(&scopes, "scopes", "openid,email,profile", `A comma seprated list of OAuth2 scopes to request ("openid" required).`)
|
|
||||||
flag.StringVar(&allow, "allow-email", ".*", "Comma seperated list of email regexp's to match for access to the backend.")
|
|
||||||
flag.StringVar(&httpAddr, "http", "127.0.0.1:5556", "Default address to listen on.")
|
|
||||||
flag.DurationVar(&cookieExp, "cookie-exp", time.Hour*24, "Duration for which a login cookie is valid for.")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// Set flags from environment variables.
|
|
||||||
flag.VisitAll(func(f *flag.Flag) {
|
|
||||||
if f.Value.String() != f.DefValue {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert flag name, e.g. "redirect-url" becomes "OIDC_PROXY_REDIRECT_URL"
|
|
||||||
envVar := "OIDC_PROXY_" + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1))
|
|
||||||
|
|
||||||
if envVal := os.Getenv(envVar); envVal != "" {
|
|
||||||
if err := flag.Set(f.Name, envVal); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// All flags are manditory.
|
|
||||||
if f.Value.String() == "" {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// compile email regexps
|
|
||||||
for _, expr := range strings.Split(allow, ",") {
|
|
||||||
allowEmailRegexp, err := regexp.Compile(expr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("invalid regexp: %q %v", expr, err)
|
|
||||||
}
|
|
||||||
allowEmail = append(allowEmail, allowEmailRegexp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure reverse proxy
|
|
||||||
backendURL, err := url.Parse(backend)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to parse backend: %v", err)
|
|
||||||
}
|
|
||||||
backendHandler = httputil.NewSingleHostReverseProxy(backendURL)
|
|
||||||
|
|
||||||
redirectURL, err := url.Parse(oauth2Config.RedirectURL)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to parse redirect URL: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query for the provider.
|
|
||||||
oidcProvider, err = oidc.NewProvider(context.TODO(), issuer)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to get provider: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonceSource = newNonceSource(context.TODO())
|
|
||||||
verifier = oidcProvider.NewVerifier(context.TODO(), oidc.VerifyNonce(nonceSource))
|
|
||||||
|
|
||||||
oauth2Config.Endpoint = oidcProvider.Endpoint()
|
|
||||||
oauth2Config.Scopes = strings.Split(scopes, ",")
|
|
||||||
|
|
||||||
// Initialize secure cookies.
|
|
||||||
// TODO(ericchiang): make these configurable
|
|
||||||
hashKey := make([]byte, 64)
|
|
||||||
blockKey := make([]byte, 32)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, hashKey); err != nil {
|
|
||||||
log.Fatalf("failed to initialize hash key: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := io.ReadFull(rand.Reader, blockKey); err != nil {
|
|
||||||
log.Fatalf("failed to initialize block key: %v", err)
|
|
||||||
}
|
|
||||||
cookieEncrypter = securecookie.New(hashKey, blockKey)
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", handleProxy)
|
|
||||||
mux.HandleFunc("/login", handleRedirect)
|
|
||||||
mux.HandleFunc("/logout", handleLogout)
|
|
||||||
mux.HandleFunc(redirectURL.Path, handleCallback)
|
|
||||||
|
|
||||||
log.Printf("Listening on: %s", httpAddr)
|
|
||||||
http.ListenAndServe(httpAddr, mux)
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpRedirect returns a handler which redirects to the provided path.
|
|
||||||
func httpRedirect(path string) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Redirect(w, r, path, http.StatusFound)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpError returns a handler which presents an error to the end user.
|
|
||||||
func httpError(status int, format string, a ...interface{}) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Error(w, fmt.Sprintf(format, a...), http.StatusInternalServerError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleCallback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
func() http.Handler {
|
|
||||||
state := r.URL.Query().Get("state")
|
|
||||||
if state == "" {
|
|
||||||
log.Printf("State not set")
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
if err := nonceSource.ClaimNonce(state); err != nil {
|
|
||||||
log.Printf("Failed to claim nonce: %v", err)
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
oauth2Token, err := oauth2Config.Exchange(context.TODO(), r.URL.Query().Get("code"))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to exchange token: %v", err)
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the ID Token from oauth2 token.
|
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
|
||||||
if !ok {
|
|
||||||
log.Println("No ID Token found")
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
idToken, err := verifier.Verify(rawIDToken)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to verify token: %v", err)
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
var claims struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
EmailVerified bool `json:"email_verified"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
|
||||||
log.Printf("Failed to decode claims: %v", err)
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
if !claims.EmailVerified || claims.Email == "" {
|
|
||||||
log.Println("Failed to verify email")
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
s := Session{Email: claims.Email, Expires: time.Now().Add(cookieExp)}
|
|
||||||
encoded, err := cookieEncrypter.Encode(cookieName, s)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to encrypt session: %v", err)
|
|
||||||
return httpError(http.StatusInternalServerError, "Authentication failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the encoded cookie
|
|
||||||
cookie := &http.Cookie{Name: cookieName, Value: encoded, HttpOnly: true, Path: "/"}
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
return httpRedirect("/")
|
|
||||||
|
|
||||||
}().ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// TODO(ericchiang): since arbitrary requests can create nonces, rate limit this endpoint.
|
|
||||||
func() http.Handler {
|
|
||||||
nonce, err := nonceSource.Nonce()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to create nonce: %v", err)
|
|
||||||
return httpError(http.StatusInternalServerError, "Failed to generate redirect")
|
|
||||||
}
|
|
||||||
state, err := nonceSource.Nonce()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to create state: %v", err)
|
|
||||||
return httpError(http.StatusInternalServerError, "Failed to generate redirect")
|
|
||||||
}
|
|
||||||
return httpRedirect(oauth2Config.AuthCodeURL(state, oauth2.ApprovalForce, oidc.Nonce(nonce)))
|
|
||||||
}().ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
|
||||||
cookie := &http.Cookie{Name: cookieName, Value: "", HttpOnly: true, Path: "/"}
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
httpRedirect("/login").ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleProxy(w http.ResponseWriter, r *http.Request) {
|
|
||||||
func() http.Handler {
|
|
||||||
cookie, err := r.Cookie(cookieName)
|
|
||||||
if err != nil {
|
|
||||||
// Only error can be ErrNoCookie https://goo.gl/o5fZ49
|
|
||||||
return httpRedirect("/login")
|
|
||||||
}
|
|
||||||
var s Session
|
|
||||||
if err := cookieEncrypter.Decode(cookieName, cookie.Value, &s); err != nil {
|
|
||||||
log.Printf("Failed to decode cookie: %v", err)
|
|
||||||
return http.HandlerFunc(handleLogout) // clear the cookie
|
|
||||||
}
|
|
||||||
if time.Now().After(s.Expires) {
|
|
||||||
log.Printf("Cookie for %q expired", s.Email)
|
|
||||||
return http.HandlerFunc(handleLogout) // clear the cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, allow := range allowEmail {
|
|
||||||
if allow.MatchString(s.Email) {
|
|
||||||
r.Header.Set(emailHeaderName, s.Email)
|
|
||||||
return backendHandler
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("Denying %q", s.Email)
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp := []byte(`<html><head></head><body>Provided email does not have permission to login. <a href="/logout">Try a different account.</a></body></html>`)
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(resp)))
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
w.Write(resp)
|
|
||||||
})
|
|
||||||
}().ServeHTTP(w, r)
|
|
||||||
}
|
|
72
vendor/github.com/ericchiang/oidc/oidcproxy/nonce.go
generated
vendored
72
vendor/github.com/ericchiang/oidc/oidcproxy/nonce.go
generated
vendored
|
@ -1,72 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
gcInterval = time.Minute
|
|
||||||
expiresIn = time.Minute * 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type memNonceSource struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
nonces map[string]time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNonceSource(ctx context.Context) *memNonceSource {
|
|
||||||
s := &memNonceSource{nonces: make(map[string]time.Time)}
|
|
||||||
go s.garbageCollect(ctx)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *memNonceSource) Nonce() (string, error) {
|
|
||||||
buff := make([]byte, 32)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, buff); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
nonce := base64.RawURLEncoding.EncodeToString(buff)
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
s.nonces[nonce] = time.Now().Add(expiresIn)
|
|
||||||
|
|
||||||
return nonce, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *memNonceSource) ClaimNonce(nonce string) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
if _, ok := s.nonces[nonce]; ok {
|
|
||||||
delete(s.nonces, nonce)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("invalid nonce")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *memNonceSource) garbageCollect(ctx context.Context) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-time.After(gcInterval):
|
|
||||||
s.mu.Lock()
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
for nonce, exp := range s.nonces {
|
|
||||||
if now.After(exp) {
|
|
||||||
delete(s.nonces, nonce)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_1.pem
generated
vendored
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_1.pem
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MIHcAgEBBEIA3zFNhDsB70vMBOzeK48Zn6oic5wfx9xto4ErduEVKFST0aJEfjLO
|
|
||||||
/kzNrKDgXArCEl2KYJQfb8J9lslA7cLvpVSgBwYFK4EEACOhgYkDgYYABABxyN4Y
|
|
||||||
6VxH/86lgejSlHGrjKVSzn6YeOukabBSiU8PS/o/wfGXKX4eKCkJYqVq18zGAfcL
|
|
||||||
q+UM09ZQv/De7mGkXwC67qQv7fS7tJ/t0uFcxriQNtVGPsL4/+YmWrFJBTlK0OgD
|
|
||||||
mkSBG7ERdb5x/JzNFbajSbX0wKzs4VOZU0VVj/2DJw==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_2.pem
generated
vendored
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_2.pem
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MIHcAgEBBEIAtlsqZlQW4bWtSmDgLjbhcCgmmWEVwtzHMTZRoQnUv4rdTxCeKLY3
|
|
||||||
hjxzd5hPCmtfP68GyJhKQgKofKYD/DgQLc2gBwYFK4EEACOhgYkDgYYABAABrcbo
|
|
||||||
t8KEfgzslg4Bb7t0khCgFrT2hX5htSWnwwHiScs1yO9egRcftZg/WAoIo/QDID+i
|
|
||||||
OB4f5Flg5PygZpm/SwE4B1E8dGpyLRmBg3cC0/PfuRkGZ2E5POKZqsiRU5TkvC2D
|
|
||||||
AkwVr6UPpXPheStrp2qh6ptBUtZRzn8Q4lVFKoKe9g==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_3.pem
generated
vendored
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_3.pem
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MIHcAgEBBEIAzUq62J5hiC8B5xw9M/e9KlSeO66uq9PHRSGcFY4d9MFgFKILKU0u
|
|
||||||
cfUBCwbhOnDWkdUTp1DkLWeNhE0UUvN2FgegBwYFK4EEACOhgYkDgYYABAHH7ZyR
|
|
||||||
YXJ9oDJ/KohiQFFXqkvspk3ljvBAFUFRyfL4Q40TtgKGt5YBNmU3SHNHn3fJdjQy
|
|
||||||
xe2OZcmKYxzwCjx6mgACI0IoiIdgZN0RBy8UMhgn810C/iDg+nOZScl7P0t3DFcv
|
|
||||||
H3K5+tkVWe8lLBIUOkqEyHmmfHGYcn6Kc5jHEnAebA==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_4.pem
generated
vendored
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_4.pem
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MIHcAgEBBEIBt+T9wRbTfN3T5kSqfT5nqCt65w+SGAQ5DXQgcf7gCXId+Ux/57MA
|
|
||||||
/Dld+PvG+T8mobr1/jaFiGOLLRsjtnc5Ml+gBwYFK4EEACOhgYkDgYYABAE/ka2T
|
|
||||||
p7MsBezSgeATljES2xBY4wDOcjMmI6MzHdiO9hU/xcIQnhc2tjML2QZSMTuLy1ZQ
|
|
||||||
Yjhu0ZRg5Dxj4m7mgAFp2f/FqtOSAR5vuikaYPzHwosvNFIIpRDJCZ23j6qbtemF
|
|
||||||
5qXUlSXf2+W491rfb2njNwTWx8BLn1M3fFhobK+O9Q==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
15
vendor/github.com/ericchiang/oidc/testdata/gen.sh
generated
vendored
15
vendor/github.com/ericchiang/oidc/testdata/gen.sh
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
for i in $(seq 1 4); do
|
|
||||||
openssl ecparam -out ecdsa_521_${i}.pem -name secp521r1 -genkey -noout
|
|
||||||
done
|
|
||||||
|
|
||||||
for i in $(seq 1 4); do
|
|
||||||
openssl genrsa -out rsa_2048_${i}.pem 2048
|
|
||||||
done
|
|
||||||
|
|
||||||
for i in $(seq 1 4); do
|
|
||||||
openssl genrsa -out rsa_4096_${i}.pem 4096
|
|
||||||
done
|
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_1.pem
generated
vendored
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_1.pem
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEA05kMh0SJfLTbcAhpq1w8jpTuo+Shy95WgvYc1KzxcH6ol/nq
|
|
||||||
vFSn1VCT/OHyg1t8BGeIfeuckIppT+cRtTjrsqS6FEmJA3lAiRqJZLWbewyXgoaZ
|
|
||||||
eCsmMS0s3w8KUon4Z9t8rFVqv2fkki6p7FtLPjPD0PsTRNCPwtOGM8Ci+0qFuCrt
|
|
||||||
4flUr6DpiALkqN1PSJCAwL22y8C86S6PPBU2seR3TWdD+iMAmr3Rezh+J/JqYXK+
|
|
||||||
4qORzE6mA3hjn+goULQif5fUbYG0vRSyEp8UrlzevS88+ZzxyS9iTZ6H1ympLcsV
|
|
||||||
PeqvCIXPd1OJXn6ZGSuIgOlgZuaieKeuYLHDCQIDAQABAoIBAHrxC+R0H+YDNxRq
|
|
||||||
7uqPlufJBLbZGmDXeDBzSuEO8uFH1jEnFgoCrdk1Dib6KOvFddMhTJ7NDJS2tuWj
|
|
||||||
/hfrUJblOvCaoS8Rfjuq3XVUR1hBQq6mAfleKLyd4NphZL/8RgYh8tg2cOVxOc7t
|
|
||||||
qfEYQimL7hQ4LUPoYf7y46CiJpAV+BIwrR74k9Y3vcpumLwWrkwlfLTMWmcaiJE+
|
|
||||||
gQBVl5+CQZmVKohTPDfCnQ9+ISzxvh4nesiQORMljG/ssQaZi5h7VEJDqGOaDgVB
|
|
||||||
CsFp9fxLrQzx1Gjxv/uEhG/k45uAU1qgNZcL3/XQhyCgadUsuMM3yOndIaiQNbNN
|
|
||||||
7bm1b9kCgYEA6XICJjg6vRbIO7nS5VYW1efFhLlqYjFimjv5f5vaTWA3FM8S1crL
|
|
||||||
HG4Q8yh+CBOwY0mtcwr8RjlnX5Dsi+wGJgFjaL7OG0MDojv9/YKZse4dIHELUPKC
|
|
||||||
Wj1zxiE23JsMAWKXvkhgGJgkC4mksZHVVszcLOEvmn2uZghDtTxhpw8CgYEA6Aqr
|
|
||||||
NipU2MPPOe0Hu2Ar6Kb/EaJyJA2G8lABWILwyI6Wv13dpZz1FfjcpnsZVRNBuYla
|
|
||||||
O3OKZun537kQOfHMWJfZyj2fOrJ0z3rsWZ+nbbUy1um8Z6jHTPqQyNoelE5/QTvs
|
|
||||||
CJ0jDzotbUYsvE4TdOH2EzSneecAgBN7Brz/tGcCgYEAuSpcOBKbzMZYVr+DX7NU
|
|
||||||
c6Dek/M6Rd6kNnBh620k0AEET7YcW4X6a3eGbEjvBtsPKwIS2VCaX91CeJQMfMPe
|
|
||||||
8KBjSH8oHomeRT3Orhm8bVzQr53a+v8QlCFwRnSr/nnhIOwiLqVby8ZJuPkZsFtb
|
|
||||||
W/ksn1CSoLkV7wqZIhVd49MCgYEArkFU0hh4H1DtDlMyu0Q9tTmz00pq7Sg7bz0l
|
|
||||||
xZKPwA1Up+GV0glNBHMfQOaw33LWqL69RGhAR4juXVRdGya6js16gKZGLY5Wqnll
|
|
||||||
hOigk4K/6yUcl7vn76c7k5o53KYWaqbVWqKm8Yh/FNDeR4takSwf38xq+ODBP21h
|
|
||||||
tm24mYECgYBUS4Ox2dzDBPIKVfxyuV+FviM9wYKBd9G2xUYavF5Id/0byYOkOy11
|
|
||||||
K9L4NJI4Xztzw6KZw7ngUsBmK9AN60mLO1SkHyMr7dLanyt03X46jhtEQem+wDqf
|
|
||||||
HgKcqIz0gxaU6+widaEM33/cTi+kafH2uLr13aHU23ZfMV7oeRk3XQ==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_2.pem
generated
vendored
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_2.pem
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpQIBAAKCAQEAsB/GrXd+Ygz/Gq0nxKX06cLh4ECe+AfINVz+LdBCY1R8ossV
|
|
||||||
1v2Rk6M59EMZwsR3XuzCoioEGzI+QyEUUQO+FpsDMpZ3iL4+dTCZIo9XXfHyQEnq
|
|
||||||
JCy2w6Al2Xr6qTvfHBCMn61IGwAgDhDDb0djclJhqLhgxAVwXyxt3+pVSnUqdKtt
|
|
||||||
h0tgLqyEgqX19QjDEh0xhFT+zB/IHmolaJO1DelvDLYmyaxoOCizGb2WyMcjkZ6m
|
|
||||||
sdV/rYpzBW1BjA9N1+gnTsPF1cQ6wJJTtDRjovKWtqCQSmmPnQSVNwIz8+zI9FUc
|
|
||||||
qPM6gP6v56bcpmqPtPylY6T3GbGxBQtW+1Z2/QIDAQABAoIBAFNvikiNTlMXAxdZ
|
|
||||||
JnjTgfXn++en1Wd9EEyvdD6x5XF3CeB5QyxpTbjaX88mpqKNPlu63+3A59cWc0aL
|
|
||||||
+jrzAe9lmhsyCwi9z4rm7fTgYSxBPVlVatWeVSrRyHyB9RONKIH8GRJgHcOkyIrB
|
|
||||||
SESEVklHW7p5NmZGiVidDKRCOAugMpnYbOB2Nf7wI7cxHT8QcxcKFaQTdCSnYv0D
|
|
||||||
eMWpEmP8vsEmnme8Q6Uax470yBHQQvI7JfWUIbh3wZVdDLllbr+E17ej8EHatCyD
|
|
||||||
A6J1lLZ72DJy37G/n3kLLtHy0oWjVk9ZT2m+HEQHSEnAqgHz0UzGKGVoeSnbAxUw
|
|
||||||
FATBNYUCgYEA3KowvUgRyc1ydi2L6YqQQg8Mhq6SdSPkC91p3pI8pMJGwco0tI2y
|
|
||||||
95EFMsFUg3m+v2URDKxjt3itx+vqWxSqeQBgUzKk+0TRuKt0+8dav5BUA3njtLEk
|
|
||||||
VdlzwofbvagUnE+slg0lQKsK/IzODPCckcOjBcUyJI1PNYdDjfQjv7cCgYEAzFO1
|
|
||||||
vtexZEUw1QGsrxlxrwBu7ui3NvpfC7rGqJIiUxm7cLrfDbOSHJxutCNOZH90zDZl
|
|
||||||
xiVBTc3tvpdXdj+zuUkf9Q4dKNTIq3+Hwm3iGXS+yw6rT2C2I3h+IaSCF1YpKakw
|
|
||||||
MSjOwlmnYIckeYGHe2kfEgNiL7qRD+SxJCjSVusCgYEA2/3Mc5h7K35oQ8tqtl1P
|
|
||||||
LpyEN22pU6GBhBasqpmOXg/VrPPjkbHHH6tzzEMT97OTaIrg8YqYK1zjm/HmBgHX
|
|
||||||
ZqTqY2eVNXBJyVseWLlKDrtcFs8ZJZaJDBGrp9/8QdtlGOURwdK/NfaQEHJsJlhn
|
|
||||||
L6ckSudq8yfyNQJyZf5k+YcCgYEArSPqCBNiICN5Y6YNnDqlWLO3TP8p8Y5rZ9cX
|
|
||||||
a9SY/W36pWXUiRm3IEN2k3KvhP10DW+zAhqjobh0U2KPHIaSVtmeGNui3eyhNqHU
|
|
||||||
em7+fq+s1QhTJeo/rQL3bq6mBfxe2Qyi56U6vvmVmXgq8kNOeMb1KyBu3R7suVkC
|
|
||||||
ui9VPY0CgYEArXpn5sZz8ERHXjeRHTVxaTk2djvgYtzVKjEGR1LzX6Bi7drbw/lF
|
|
||||||
M1Fjqog/k0tsDM7pC30EoxpRR10hSFdC7dQ+STTeTr1gmB6xJmA8boe1jpAfhFXm
|
|
||||||
sjjrsFsw3mUpsJD3Ck482T3BA0iZP3NvC/+ge0IkRUC1/j8KP0zQKZI=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_3.pem
generated
vendored
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_3.pem
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEowIBAAKCAQEAv9Dpk+nfW9lOSHdt8UO5f/ELRA+JM4vX9GkbJy9/M/NTv7gg
|
|
||||||
HBNpxYA/RhIMnifeXFAomLYMov0Gxh4+85Dv4YT3lhxlUiwi2yaOIXlgy15R8kGR
|
|
||||||
OCFVKgfqfQw3WnWhjGyH3Rb4Znk4IGh2qj+w2lBlnbeKCxXq+SMB/Kc3TMmJg4Cx
|
|
||||||
P73vVmT3VifW9goOO4DDNAv0uTl/KgWnaVoPc8eRmYxBc9chTxjlmoLHsCy8z9+p
|
|
||||||
pn33eTyfcNF5DS310sMHLcvpP25fPMpkbzDDjnCzBouI0YkJJ1F2m1GPEqnsN0is
|
|
||||||
1F9TGJppcAOpb9aaPmtU2wlzMs5Rwm5vQ6/9wQIDAQABAoIBAQCPczl7+QeltRoq
|
|
||||||
b8a1DCUKXcZDHCtLdWYHzyMTZx4GSA917cl1tb8AiSzIxm7RSJevCfOSYXOJ4RjT
|
|
||||||
yYLivJ3pVnuis5HCpmda5badqhyNevhl6EsmYydBy7G92wj6icZLMk9ZNPiIClfD
|
|
||||||
RNyZ7g/g9QdJsB14tOeJcnjl7lgY/8RQOv52mWC2AfvCRGf6fHoopMP4ZlSTDP3q
|
|
||||||
2LMGNUW+cEwjZg+AIheJCoCOs51pvm/B2DXeb9T/GMzway3qFBKU/RYi+ceOm6UF
|
|
||||||
+BZwStxpXMRbnXrXv9wS3S6260+NSdgIVF7ErzYjinLhZL9Wc6khoRVOCg2ESewr
|
|
||||||
+2sJqoABAoGBAOST6/PgAfvODs0m3tzb8dDYMcCtD7p3QoC3HiG7mEu6vnb/GdZZ
|
|
||||||
6B1XHiK36xLL0/8tf2BtJIqmrDqOJ8pdiT1sbQUtv8lmbdr2PoV2P86v11F8ZBkq
|
|
||||||
szjpQ6gGaItT3dAjJxSGCiwdvMw4za5GJAUPTQZk+t1XasiaAQznMimBAoGBANbT
|
|
||||||
9tv+mqwvzK4/Pw/gyIJkIQrbgXTFdMjhZJkNVxCm2R7YnNDRLSxmng0nJCB2Tgkp
|
|
||||||
EouWNYi9rsWnmR+AOinSSoAb6znQwOZhQzQb2KzkfBnlZ4XFUxvW4OZmJDZILmyr
|
|
||||||
/8uHnEcT7xwT7L90j9cSxq/+WCbB7GGpFmmVpXRBAoGAd7l/ElsXzuOsXwpoGzjd
|
|
||||||
HS3QSYKcRWfoHnFLyBFxgOEMmFmgF+U5rfyOnVLGPy8iGHulR0WDqVgJyBXjg5yg
|
|
||||||
oNqk89x1ozESg2kNcGxymXkDB/xmlcQG4d1UgbLxmWDRQw7Wjmpy846T8Egke47j
|
|
||||||
mP7dsma7+6mpFe+Mc0y5uoECgYBp1D+u/oz5uA5v5G5Phx+fxG3WqG3stX0jnI1v
|
|
||||||
LHgwltEs9e7Cm9lSHzdLKXYNm9ozfw1IwHWc6DyZ2EeBkiyU/6h91cMaVzFADLgL
|
|
||||||
ipBCE8jjBPTrnFqlw0RFnBnIt+RO2qiHfkXJahOH1HTzmBtoCzLf7j9E0JF/Rsno
|
|
||||||
t7SrQQKBgDxAKKHMLT2zm0kz6eTSLSB7WDNxZ14+u32fZj2ln4JMERTk3byGojPX
|
|
||||||
zf9poWwCWJOLd7uVtLl2dQZFUZG9GJuZXO15qmKAaXMI5yVwH1ygzzZvDLOlH8Nn
|
|
||||||
19ZjFhRLreVeAFLMTyOapEH+5QZxaszMR8Xzna9BJnschht/kPM7
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_4.pem
generated
vendored
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_4.pem
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEogIBAAKCAQEArmoiX5G36MKPiVGS1sicruEaGRrbhPbIKOf97aGGQRjXVngo
|
|
||||||
Knwd2L4T9CRyABgQm3tLHHcT5crODoy46wX2g9onTZWViWWuhJ5wxXNmUbCAPWHb
|
|
||||||
j9SunW53WuLYZ/IJLNZt5XYCAFPjAakWp8uMuuDwWo5EyFaw85X3FSMhVmmaYDd0
|
|
||||||
cn+1H4+NS/52wX7tWmyvGUNJ8lzjFAnnOtBJByvkyIC7HDphkLQV4j//sMNY1mPX
|
|
||||||
HbsYgFv2J/LIJtkjdYO2UoDhZG3Gvj16fMy2JE2owA8IX4/s+XAmA2PiTfd0J5b4
|
|
||||||
drAKEcdDl83G6L3depEkTkfvp0ZLsh9xupAvIwIDAQABAoIBABKGgWonPyKA7+AF
|
|
||||||
AxS/MC0/CZebC6/+ylnV8lm4K1tkuRKdJp8EmeL4pYPsDxPFepYZLWwzlbB1rxdK
|
|
||||||
iSWld36fwEb0WXLDkxrQ/Wdrj3Wjyqs6ZqjLTVS5dAH6UEQSKDlT+U5DD4lbX6RA
|
|
||||||
goCGFUeQNtdXfyTMWHU2+4yKM7NKzUpczFky+0d10Mg0ANj3/4IILdr3hqkmMSI9
|
|
||||||
1TB9ksWBXJxt3nGxAjzSFihQFUlc231cey/HhYbvAX5fN0xhLxOk88adDcdXE7br
|
|
||||||
3Ser1q6XaaFQSMj4oi1+h3RAT9MUjJ6johEqjw0PbEZtOqXvA1x5vfFdei6SqgKn
|
|
||||||
Am3BspkCgYEA2lIiKEkT/Je6ZH4Omhv9atbGoBdETAstL3FnNQjkyVau9f6bxQkl
|
|
||||||
4/sz985JpaiasORQBiTGY8JDT/hXjROkut91agi2Vafhr29L/mto7KZglfDsT4b2
|
|
||||||
9z/EZH8wHw7eYhvdoBbMbqNDSI8RrGa4mpLpuN+E0wsFTzSZEL+QMQUCgYEAzIQh
|
|
||||||
xnreQvDAhNradMqLmxRpayn1ORaPReD4/off+mi7hZRLKtP0iNgEVEWHJ6HEqqi1
|
|
||||||
r38XAc8ap/lfOVMar2MLyCFOhYspdHZ+TGLZfr8gg/Fzeq9IRGKYadmIKVwjMeyH
|
|
||||||
REPqg1tyrvMOE0HI5oqkko8JTDJ0OyVC0Vc6+AcCgYAqCzkywugLc/jcU35iZVOH
|
|
||||||
WLdFq1Vmw5w/D7rNdtoAgCYPj6nV5y4Z2o2mgl6ifXbU7BMRK9Hc8lNeOjg6HfdS
|
|
||||||
WahV9DmRA1SuIWPkKjE5qczd81i+9AHpmakrpWbSBF4FTNKAewOBpwVVGuBPcDTK
|
|
||||||
59IE3V7J+cxa9YkotYuCNQKBgCwGla7AbHBEm2z+H+DcaUktD7R+B8gOTzFfyLoi
|
|
||||||
Tdj+CsAquDO0BQQgXG43uWySql+CifoJhc5h4v8d853HggsXa0XdxaWB256yk2Wm
|
|
||||||
MePTCRDePVm/ufLetqiyp1kf+IOaw1Oyux0j5oA62mDS3Iikd+EE4Z+BjPvefY/L
|
|
||||||
E2qpAoGAZo5Wwwk7q8b1n9n/ACh4LpE+QgbFdlJxlfFLJCKstl37atzS8UewOSZj
|
|
||||||
FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ
|
|
||||||
Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_1.pem
generated
vendored
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_1.pem
generated
vendored
|
@ -1,51 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJKAIBAAKCAgEAsj0DJZqC86gp5P0B/KXk4CoWLa06uEpMu4K+0QFNR6XwbB7A
|
|
||||||
FFXtcjemUnuqnwqHqouG7pcc/KJAiakWPYPbAtwb0JQo3lRPRf6EewjHmspk9vqd
|
|
||||||
YBqump9VZedCs7acHCT/xoHhsMI4PyTX5VzhTJoZSMSm1aTX91ZTG2R/KkFhchPd
|
|
||||||
b4kMjc2LK9O1CN8vQoX/IolCOlUOVcVrF20jEiOIFpc+5t4V+jRvx4A52dG0DqBm
|
|
||||||
Isx3+OioRHD1qRKbi8L851a4UAeX16rW0xsuyCy5htfh3Kbts6AFCtzmvR12QYBl
|
|
||||||
+aL1Ksj4lraUmQOkwuj6p+w6x2AU3z5Z48qOmUdcmBnSJE9k37wQEqT4Y09O4+Zb
|
|
||||||
UcaAdcexT1YLEKaL6ghGw9pE7gPTmohk+gTYpiPnQ4H/M7HamN8dHN/Ch1hvaI0/
|
|
||||||
ZdxSEU2jWlkEeg7c5n7Bmdbc+yMAXeWvS/BeH4yGNh6TtwvTYpq7aDcP4cYw7L7j
|
|
||||||
ODIWEmCVgOzdDxn5REHmrOx3qSjimCA/Dia2PHSqkO/pJ0Lu/YNHWdEhxJXYtX8x
|
|
||||||
Ldk7i3syJl+s3Qbi1GrNzPRr+enyAWVk5wM4XVwiNlRvvHh8c+gPs7a5qTS2FulW
|
|
||||||
psAUB+pCC9Znv8ixgVR9ZMxLALZRIzgZ3s16OZP7pl9t8u87LnbyeihY03sCAwEA
|
|
||||||
AQKCAgEAm4gqCtI9mykPBcbRyQlqI0IWgF09dDtBog6BPBiKuw7OMUrUCerBfH2b
|
|
||||||
ITbQuF+T6vo+EEzE+p8K+hUWVy+MGX7Ats3Sq8+eLVHfgQ00QJqEaBBg68/ctQh8
|
|
||||||
mKOozPF4YAbZOvtzWa7hLhiUXI0j/JgroBgaDSv/WNF3S9vyK4lJ4yX6gK1yyvql
|
|
||||||
iuT+gHNg5gfPju9/Xy+Bhs7ymEqf4+AljLEGLqd1PhQrxkbaNHyNRoYpGgyaVBWR
|
|
||||||
X8fCVnrqSJcp4SUHSK6XjZaCR0zdEcgVTNltOgJgQfJM9CG3JydiXd4RHjlY/rDI
|
|
||||||
W5uPJ8bKK1rp/0ZgNEJfdD8QaXoD28AzoSiL2U8w2wipuq0zAExX9H7ZhgdCMv8n
|
|
||||||
1yZ3UWhva1LKzjP/fs9ER4CGK7S5U4tmzF8rYSw0A/RuP5bBhGRAJoCs9ZHmM0TU
|
|
||||||
Z9JDAPLy1/P2ICRuHH6mIVOZkHp0IedSJIPqSJ8LywpPNCyh2lIxksnyL0WnI0ai
|
|
||||||
8sIu+1t8NGOv/41yUKwJ9LUG+oaWmnUNqduECvYmuX0ByoU/E9YY3RsadiCd5P8l
|
|
||||||
xviITyRQG+M5BqOTzd7Xa7K9VBy2V7Vnk/gf6bX8fbv/2TYVD4nnjxNd/H6Uzlj5
|
|
||||||
i1R7/gip4rEA6n/ZbioM8TATaJLG2ZAlnxFAGDw10S3cj1W8gMECggEBANxNwBSx
|
|
||||||
zbm2vFWCyYY73D+el5cgvSpoXhhu6WotN1aQMBMg4DVQxUSD92erSWvYlb1HOBJL
|
|
||||||
wjNLMz5NOanRy42mEXso+oN0PMpykGOb1WmoMx186xj7o7p9NK9AFF7ll2DQMYHd
|
|
||||||
9Y/F8ggmj+4orvMMBL//pYDmdguZ2O+KAr/VZAVj6YTnO/tdmk6n4EhAvB5Hy5wZ
|
|
||||||
kqi+YD3W01l6vmtWUQBKquaO5XktsAJtv5NapOmy1Fu08gEYwAgADI5Y8CLyMtzX
|
|
||||||
NmCSoTZ+GNYPIbW4DocSqyH+N6YpyqLDifb3wE7uvyFwYK6y68TNKhO1F7JLQQNM
|
|
||||||
7pM7F1YkUagzE5kCggEBAM8eYUkXqdno5TTubYcH0KOt7kX2ZB4ABFcYnQsMLAC1
|
|
||||||
29yElr562jMqgYhpIV+pPMSxKec91g42p6Uiqo1VeciCkFkIcVUyZO1yBFsHnWcS
|
|
||||||
z1GFDb0ePDAddLIrOmpKQveRpubPThfioqnrrVjm4YObWVwaO/BiAvFF9ZIPgB7N
|
|
||||||
VmXINKJ75Zof6NsCq4Z27dnnXlh1N2kRDEVV2P/x1HRCq1BB84IePGmSjcHBXRqU
|
|
||||||
PD0F8PdIYlcW1xDlQuj/x9iWso1MWBWzeuGROTp0LYGA0A6NChU5ldTK3T3+evNo
|
|
||||||
KO8xzrjbUH6I6XvWitWG1hvHf31UhoiYOuxjF/++zDMCggEAVgSRrELkdc/w516C
|
|
||||||
u0PiMoEE5YBl/An2O4oK32c6RTVVYBKlGIwqCh+Q2UybBV3y0Y3eSd6EvCxvnLLg
|
|
||||||
gfslhHBEQRd2AR/AoLdsw0fUY0XGd4wP65hNjIJYsNjPW2I/4hBIVFHLENEUOLR9
|
|
||||||
3FrMPKADtsfl4leZ3du7RYRYoHh8blJdmoQC+pnIp0+LFgsYqKYVzSR7DCIRR/P6
|
|
||||||
X+S6NwTj6b49znobBV6ea8RYWfu5inpFymzzVRRJ3pXOUUJOuQZib7IkTD7UbYd8
|
|
||||||
wQ/1dJOiMIFMiqBNMDb/JOA+nUyNLQSxYigTyAKaZiRJeppp3zbc8qH2QUyARyU1
|
|
||||||
MPyIeQKCAQA5WO4S8Oxkm6mrKEFHXBCW4XfSA1DhRZvuCbCh+HLOl4wS2NtsTlPQ
|
|
||||||
SvqmrIVDGXbr9ynlDygPs25juN+EVqBrtksFe+L1dgif/ivakJcyjPC+X5rYPGDp
|
|
||||||
6Z4AHxwDhiBYsAmIaunyjxv+9HSA4xyZ9g+eAt2Jx3mNGJPQJ16QKMa9U9vPCYMf
|
|
||||||
U6qDyY94ocFlzjw/PeVjwAanxAdbhrgOoM8SX9BuvLR5fsylU0bWLykmtFht/6rK
|
|
||||||
9lYCJZiLLxdEjyVNHlBdYd6qSi2QU86txt7UyJR8H/+udaUgny+n6bU71YypfoAh
|
|
||||||
KQOM+HBkgvsRogFY0GiXtZ7LCP0CIPAlAoIBAG49Ot1AghC9OwLXjcneGRbsML4w
|
|
||||||
vkQT9vBkStonR56RkCnN8xGDvLwQQXOJ0Fodo8C3Tup5AHrTNi+V69YVQYCtuM4+
|
|
||||||
heWNZxpYDxzZ1IU5a7ITlH5TJFP5paaaH+tIqRZRp+p2+j5y/CyAe5hoYY4K1Xog
|
|
||||||
Zuz2piLO5IWKxtznwlQGjlsZ1n6p+WS89NFHuIF2r9NrovPjkEDDqnEcLRTDUnLT
|
|
||||||
8FcpdlNkarS/X01ZTBFja5EfW49UWW8sQNRLzVfnP+MECvGFy+oA+4vGprSBuxmt
|
|
||||||
WBY7y+z5VD3o+JVHLqkdETwIP9YKLMVhqUb5DQb2EYwggU+1HgZNqTdbfDk=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_2.pem
generated
vendored
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_2.pem
generated
vendored
|
@ -1,51 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJKgIBAAKCAgEAobIQNPQNtGZlPSpbtZC/c/gb5OxoJfoq07h6X8kyeR3xq9qv
|
|
||||||
js8u8YQMt/AtgYw1tSdgNeaYzsvMgaSVgexf8ysqys5obWjcB/sjRR6UJY4hgSm5
|
|
||||||
oLoGy8fi8lbSEtpthHStA7UVUGU95Ypga8r++Kkizk84wDq+0jZAGaEPvhO7RJne
|
|
||||||
nbEKhhZVIFb13LlkaX1eNOAxAcTszFgX/DaZWFcfzKop6gS7agU5NLCtyAjKdSRz
|
|
||||||
CXYMVyVnXhZaxZtLawk9Ld8qIqwmytiPwnlRugoFhTrCPIPfAb1UaESKj4EuupUi
|
|
||||||
brlByun/ENiWvbzTZyGLSuYA6rruyfUll4oDT/SwsDMJ0jZiu5C1bTgA+gY6kB+I
|
|
||||||
A3EvcP2Eh1uiUvwy5adAr29wlP7/8YwWvyEAAaO9SKbh36xcbsJH9OIQbFati99L
|
|
||||||
kn7jfyktUGKxgmwYilF2ehtB1tPOMKRor8HJGoMEcrjiu4Qvc3ygaZtomfY4zxL1
|
|
||||||
uNeZ8c51pm5lmo30kJ5q5aWX6iS4Sp+ncFiPjxj7bV7nXOGqwmxK23tk4qdUOK4s
|
|
||||||
z26uc+CChP6VdOwLU1uu2FdRvBCPSnvBFIk5qaFZkhbk/gA+iS8R82TUf1DWfvZ4
|
|
||||||
why6XJDPQRnscySiIckN3X30TD68dpJ5VtzpRzCvNN9GxoRFY6apcJXoV0sCAwEA
|
|
||||||
AQKCAgAKR01scE8mtpOc7cJiqk7hSlZLmRONxndOeh2dVSbWOCcSq5YZV+Y+CAze
|
|
||||||
7G+YGpeXamddRclU6/OWEiZG2gXHaWkQ90oAGnhSMY6uaCE2ufA7S7G3G9wuvAgb
|
|
||||||
K5WzCRuJHfmZkLtIHwduPfufHopSuD20K6kJ3zIeHsC4YFql1I9E7xsNnyFyIJ1M
|
|
||||||
rvp2C3rskcGZTt8Oo7wByV/M8pOQ4Ajvc6mybJaVSLu4M7r4SkbEZ4rAgTaLm58U
|
|
||||||
hgtDIHoM1cuDzPnatmLI5jdNP3UIhHaRX4jVW/SjIavp7OF5+dZEmhJUQ4aBJZrH
|
|
||||||
MV1ztjsiBSnbmv9X7IYdZG39UhKfuqph20l3NQQ9lGhJH68/V5YEt/301O7Jz2jo
|
|
||||||
hPP56TvvP8sNJZqkiD3JQ9Do4V4s0pUR6RQiim34gdivhRNntPjL1hfBZVoIIxOY
|
|
||||||
Ek//7OmsfrPwHEatZP6UT+IJTVZRbGp6a5Qu0YmQUh1/MVK+h2PU+FVpkgYALm1u
|
|
||||||
6e2dXqRK1NI0v0icFMUTBZAw8h7mob7IlkoA/uLWjgkVZf48/2wCiCtxjDiU31Hu
|
|
||||||
QGV3AZFccjKPwjc+lcWjjH3+nM/q4Z9b2r9x5XaPOlq4mXF4QnheSgQ7URhiEG2I
|
|
||||||
RI1pmV20HCU/qmAiiGGBWOBYlOSBuRbun5/eSOY8c3FXIMOckQKCAQEAy7F3eqYR
|
|
||||||
0tXCJRzR9EQld13QVDeP4PpADbMVzuGXbG6+DDjXgRQiDh0gnS2hSKATd61ZRg+m
|
|
||||||
RHVEPAf9lfEyDutr6qZRZJs14auI0Z4eBfdq+pL23e5tcmj5GuqBZ6qR5ZYHblz+
|
|
||||||
OT0pEa0VfCW63e0XU/1iG0y6PGbSQiHmqas6E3s7ccDjZZE7lRCBn4+RouBBxtFo
|
|
||||||
leugrTK+ahObFeSYSQqjG5vGP13gatui5hib8cGG6QUV6GQvezvLvo/rBmCNFrjm
|
|
||||||
wtQ4vfICifadKYYxyTNNB/EKDrXB1E0NyDM8pmXcfFlajQU0fucvSCNmXIDF8nnL
|
|
||||||
TCB6mGQBX5VUPwKCAQEAyze5K7YQXe5lp7PLz9IHYyY5dtc4wzwHI4PGe2TAKEtQ
|
|
||||||
jEOjSi4bgUYGyalJ8vqmOrGkCBW4NCF8Ywb6wNHgK/O6BGbaSl8N2cYqA9wSnVYB
|
|
||||||
c57ieUCg1A/3mdEwO+jHng1G6Me2Tf7VzkO/XPXyA2Fy6XhIg4xf+mxlW010dbKS
|
|
||||||
kn4CWljY8XhuX1DZMjA+UAsMzaI6DFcxbIcgcVMP6lp9mnk0Fnm5T5x/EpNjuAHV
|
|
||||||
D0m9x9TlQ86akFdf+FljEsj/drDIZ8mkU4JTKiH88wrwfKHrOqhv1NX7hLCXs7wb
|
|
||||||
tkxVF71M2qOcYl5TAvJz9uv2U4O9G5l1jL9wT9eJ9QKCAQEAniUAwGajO+/eNfY0
|
|
||||||
Q9OMyyo5Dsm8mU1x4bEC44ZejD9GqjKPjpXVAuQ2aBH/QGWX97jMsQqBanEpMvp5
|
|
||||||
Nar31IGPXbUXSGcA5F7LcQO0B6nakwT7Sb9NliBOF0mugo/5iih7SIJGlqYXdrPN
|
|
||||||
FIAunxLuo7T8MHnXtgGWiOXNMjnQc0OgGWdKpZamjcss+Hb8+Vnnd7cp3gv8ybu1
|
|
||||||
/qGOLOc4HK13iX3d42C9VfmEdeTxXjeEyPG72pu+CY2ZWDBgpqjboaKY9vbRvxdg
|
|
||||||
RUEFMDISAUYlLl9EEbun626Pnrm5Au/eyWSOWyKJaWWQXg+t72/DP8izwD0PMbWj
|
|
||||||
I1TK/QKCAQEAiYSK5R6OUtIprmPILzlE0H6kclxQSCXN+uWIoiXatynINzLqRB+R
|
|
||||||
c1is7TiHF0swxBVEGEiCX5ytbOHjPCqKVZPYNHRZkexjFhS4h+YcHqZ90v0Y6s6m
|
|
||||||
RvsLJebeihwLQVRgwNOs9XjWvH8x9zlj7Y+7UGyaPZL3vCIwMKnofmE6OLHW68am
|
|
||||||
ADnsDspKQGFPOaFQp7L5LzKt+nAyrx1zbraPusH8Up1Knqobf7mHyJRM1syjBaB3
|
|
||||||
CPy9saG/CvOKTMMBxRL6eumELxLJLoDTiLDFbsGvygEDtHadfvx1nCZWZnWfO7JZ
|
|
||||||
WLdQ82w7JoplmRmylm9WwF+HoZhG63DDJQKCAQEAjkYiyHgh/drr0tQLlpELy3+8
|
|
||||||
3CfKgijIs4Q+UGBsygEkPUzS/oFYl9oJ/yFSSOCsyGTYF9ojXYbdaLyHFS1YcpWN
|
|
||||||
pcPcZRmd68RdVk2gOMAHBAvT2YN56OQJiDwghMA0bIoY2BI7+h5mFLAgudSZXHY8
|
|
||||||
RYt9IjEaFai9cvR88p7p9bQJNuIXWXia9PcdSldHJ01S+GhWHNbd6J0BfPhfAO+R
|
|
||||||
VJl98+U6jLyXyY1Ma1v9AfI6dC43SUKG4Z8b2B0ynxMANiOWXCj823owTk542yFi
|
|
||||||
ylWbhUGqQ4uMn8ojPArNY+Ndpmlgc8MpgXjaEzMil7BfnhlTHz5sQkMM4ByhaA==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_3.pem
generated
vendored
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_3.pem
generated
vendored
|
@ -1,51 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJKAIBAAKCAgEAtZndOamd6RPyspdd28050is48R/ozNBdi5xa2023cQ/lWl7j
|
|
||||||
TFTvYyrp/pJ0GvIIUwG3BKQyQBTKcDhLrQzrTO8fxqKfNSQ9/9J2AVChFulVcaJR
|
|
||||||
dAJ+k+jjvR+1LMjOxpxjv+RJyTSB4Z6W9xOyqfwAyNsc3nZ2jukHlOCvkpwUJQW2
|
|
||||||
AMQFLlZoIZ4FyZ5qkSPP8kw6E6bA76/qSY8/bVMleX7HySIAfA7car3lZuxGMkP1
|
|
||||||
aV7Mak4/5kHZwB75SjuiQVwj3urMA1vEoCuwXgdisMS4lmB8iLyW/XJSWHmLrEnQ
|
|
||||||
JWbRpie7MYTz2tOJ5GlQpD0U93eV51sn3nQ7If+08IpfZwnsqf+Eew3+cb1scbrI
|
|
||||||
rwoS2JI9MW97I4iUiLnXl8ZDYy2pIIt3bs5gFIdNVEr29uDnmtvmZfFaj/RuPLO+
|
|
||||||
4YxNILYo42j8grJyuNikhvCMOWvB/6QMWGUc0BEz4QiZJLzqGWpTP0Nli3AedAxT
|
|
||||||
6CIbOhfl8RKyZUl4gDD9ZIsXUSZ0dL5CnIYU8yhq9yw45wh8gwPKTo/fiDog44WK
|
|
||||||
cFiQOlTNg1G6x0fUcbfrsfGwNfc9skXwDz+hcdD7dePZfN7jKeQxtX5dj5DIJAhX
|
|
||||||
9wanwaoqEj+G5KyXbsn4tuYu2SipSad3uA53UVAHzLapDkD9iiaJ+HrNn3ECAwEA
|
|
||||||
AQKCAgAW+spgspL13H1YlgjdeIG5k5iYAoat7Cv6L6XbnGD7IJzQK7OthA3qyZJk
|
|
||||||
kVm50yi0gEINh02IiFj5jFYfJsRbruKhexCUY+qohZRDJFXOFWang3e1K1+jDdRL
|
|
||||||
qUh+y0ZHIaEJtjSUDl3lE/FcgJSaJ/ZddESZ7fmgqeI4t5nf/noaGTfnruZM78gr
|
|
||||||
gNiQo8guZ463xWeP9wjxC5ylBEhtaBkU37MeQ3w2NpcztqXhuUJEuA7E76cESLST
|
|
||||||
SX/pbMH038jvZl5vpdx9DE68Ser+awbVAX+uH7WChALDPYUoBvFistBw+yrKULrC
|
|
||||||
UGWfKieHzL/UmJofmnVQmltYLfMRarjnGrit2uinJb+xxjt4yKMA6ZCHr6jF7ZkK
|
|
||||||
F+vKEbLMGZGZcOQvrhMwTkI6asoj3AoDl8pv1AtFlYJi43hlF8fqGHyaEE8KY90M
|
|
||||||
z5SC0wxhUfUGV4FVHRdq/UvCGQf/bJhRfGftnC1BNc9NkGQHZAbjeu4YSkIjFPEO
|
|
||||||
k2LwKt8XVFX2bhFNMApP9ktTFlUtOV1Ljjn7+R2L7P/hjRmajnMcSgDZYbQ5Ndw9
|
|
||||||
fsZ0gTr1NZERgud4CPLgtc6uwp0rYiIwWn2VoMGNM9WUVDhFmOlznKnPqrkw3zH8
|
|
||||||
uor7/+AI1AGltarvTcth2kriS34AUUoaGysZwIBcYgJSjNPvMQKCAQEA3UVUzjyn
|
|
||||||
J8UCl+KS5vkXJjIp3mvpT4ZlomSJwuWFChXRHiy033rb2Pz6gCKj8Vh/xHfRSn20
|
|
||||||
ZlR/hodqhgyZAEkqvvzcBXm6coEwFywZbhN+cJPpqFkrehLBOC+ukOejUo+0Fdm6
|
|
||||||
pF42hGZngq5GbAi5A60FJO1xOIjPj0chBOk5SUTPz7C8TZnE5ae3iSHEcqB2f7qY
|
|
||||||
OLLrAAs0YQmOOAB17S6UYcRXTAik6yzONboYFUiyTSid6fuMr9akWP7BGOMNTgdB
|
|
||||||
RbZB4PYcvMDqJdCOwJV1eqhNz87i3GPAq/D+K+YItxBi4flh8ijrPjpZ4hH22aeL
|
|
||||||
tmfxZVK5TilW9QKCAQEA0hqZbmnLCj0TV71VZsodb2G+6BRAnS15/ppW1BHuNvmF
|
|
||||||
noRMnTPAVqbzZ5VZshK0dVsQa66lB6z0uPzOG3euxozrK/Y0a4i0bbdsTO1C+MQ4
|
|
||||||
ssIasCWTiKaBWuQ+v9yglEvi9nhaQbMUGhwH029xd1e2t7m10O4HYXF1N640j6gu
|
|
||||||
sLvMc0xNvom/Sv+MP8YoXX0NwwLqqxFVQhPqFsim0EhpfGrxRxmWeby1UB0yxn1N
|
|
||||||
y87UrF8Ap8MxBImz9avL/5I8LI729xMZywa+RNG3YM9AYsnP2JbZPz3kc2oP0XsL
|
|
||||||
83lUd8QD+Z2sFB2rL15XnBD1GETOxSvHqjJCcSBBDQKCAQBDp10kqbraGAyQ7//G
|
|
||||||
i0aesRvIG+p8HDWbD25nntGsobsMpNKwudnaYI8e+nhx5IM8SP4+7mxoFVHgiirx
|
|
||||||
zYxCYBynxJxpOCzfscxIaX1lAKTaOv9oL8txSaa2TS3stEZlifaf77B3bS7yEHV5
|
|
||||||
qVty0L/w9cfq4IaLqJj9z9uyqrSPSHDZqcoJWAixxzQAw8hS2+kfaKf+PgZIPyTG
|
|
||||||
vqszSEDGQkWwFt4yKzpxhYOPPdT7PPz3RoHx9q2vXctmQo4708BPqTw12mIOLHHg
|
|
||||||
7IMrCLd8/rWqySbxcOpARGe2qrqsJWtovaPeP+fIqOY0Ypb03lVBe07meKWAO2jZ
|
|
||||||
Ex65AoIBAFDfIDPZ0OeN/sYFALxiC9Z1n1Ahi4V0ncKckdNrW3AZt47+iabw5pX0
|
|
||||||
CTjTygS7Im8RsE5imO9NaZ1S4dq8xK90SolPaXoC0sBwm+U4ZlDu5owYHsGylQlC
|
|
||||||
XgQoWubq+3xZgXExfjxPu+sY4wJFoT04rAIoH43eMUUWsPHPwjeRmvc4MkgnFL3E
|
|
||||||
s7cgilF56suheQyZMM7MCy82DyLZ9Suy07eqSlj9xmfxdTDzLDouvSU35bC7mLr6
|
|
||||||
bQG8J2Lmz8z98t+L4A/WcFUvsUk4GAfRfo0H9VL/LXwkTK0IJDKT1FPRXewDrSwF
|
|
||||||
vti3Ws8O11YhSNYglh5a7a3bTqvQqHkCggEBAM/oLCz33rLaNQ7Ogcqd3i08vHrT
|
|
||||||
CYL+Yxb1ZEIegGgfpIMlYYByhneCcqhXTp8jleJJjRDoaJLfG2rzm4yQ/xIXh2nA
|
|
||||||
c31BSicJffk+DV1N7BoZG/OC7CSo0GTlJZ8sAKhAfUApYjVHuDp1GZdHBpLmeYs/
|
|
||||||
zSilstxPBgaKUZ92QNt32gjT/nrq/xYmPDR8AEaurS7cZtkAIsmWEJajmqP9PBNZ
|
|
||||||
eeCpKaa8m8cSjnSDbTXrA3l7ga2gdEcy++fh+5+VPmpPfnegzImJidC3eBfbDqjn
|
|
||||||
/zmUYntVTJ6FepsULoaXz1mQGNzgqCx8y3s0TZ4KmwakiRxwS1OUOWSRvtg=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_4.pem
generated
vendored
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_4.pem
generated
vendored
|
@ -1,51 +0,0 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJKgIBAAKCAgEAoDHNjtZEnbu8o4nWZR8gV4ugYlysC1QP2mxVN66RSXbeuxHi
|
|
||||||
aYHjMTYxMSaMLBuj240kP5brwmZ/Tpwy6xpDKmfNFebd1HmE38KuWn9fnZQdgylo
|
|
||||||
w9glcsTLxNOsI/wSV4htF/nRzsvAx95nM7HHbdbHNtPwRhExoNJQ6AOuCVtIAe9B
|
|
||||||
yAvzdV5voOhWJIahblPbVwU5NX4elm+XGsvlDP66lKcss9lBTjDnWAP4ZcMABBvb
|
|
||||||
h5Kc5T9aRBz1WAmUP0qs6+b/F/XYPIKzrOfRpWt+I/l60h1a/RkmlMR3xujLBh96
|
|
||||||
r6ATrYdIHDrg33snCkMUyEe6WfPbD02UTZCIttrfZqWy3ddpmV2FKRiGqPvSHIQa
|
|
||||||
GBHYZtjV33bdSfmZE25c3LHLCCCP6xJfzK4JMGPEhsDhk3XPWeY3qpKiNAcKSsjg
|
|
||||||
B8ibgpwCtP8Hb+h1GG3ZLfoMLYmoladL4crnff/RXhab7vrM67Ggz4uyqB+lXbDx
|
|
||||||
NK3UnmkY6xkX86REMSvJw/tCWBpFxn/2X8UlhgDd7fjgxD7HecowWlXSk+DOdKeD
|
|
||||||
pDsEGfi2J6/kfRn02QUKnJO6x7gOB5PQPoOjWwQkCaFx4zRngHFXYGuBIv9qNk8W
|
|
||||||
EtkuSnCbuLnjZrUmSCjgMsxQQqaXo7Cy/UKH97PSaNsD7hXaUmZLd2+kdpcCAwEA
|
|
||||||
AQKCAgEAi1Ckpxsa015owIToKks2kkxAsCpOCRATNW7PcbxkZ9J0A5abJAysq6io
|
|
||||||
gUk30Eg9aXvG0XKMGCWRg6j9806EqQVa6zg7JUSFVR/3B4cMfXtJaz8A+IkqkDQr
|
|
||||||
zkITy7u1q+Bel+JQH5s9TdTSRbfPa2vFFp6csCLV2Tnu2MgSe9qhteUAfVw/X4xA
|
|
||||||
YlyMRfm7vLo63+QQC8BiE4x6ifhWe8WwOAVnMAW58KlBGF9jkARVKD2d3rqXrhs5
|
|
||||||
glD44ZZ7Ecv8tK/Qm2LXqlA0uCNnRIhGTDz0HnUfI0vTLL/sNtVPc0S/Kqt5UYl8
|
|
||||||
IejmlhSBMECEe2U94Grd0OI0HnybF6KtZ1/YDxDS0AQyO6z4CLs8hUKbaLm6V/i4
|
|
||||||
+9fkWvn4DcS7stTeUe4+I9ua/uaDrrvTNBRH0gaLsl3m3ptPiqN8yigApsCyGrgm
|
|
||||||
hnxvE1DQCvRw+yeNjgEgtHEJe9z6pcX/0jrw0zH9hIMFM7nSXWONXGQ5AvWuaD1b
|
|
||||||
cF3JhAP8IYXPqwNDj6dr61vszMqh7iJgQklJm0YbiUiaf00vGpcVKdHYzInIXdoD
|
|
||||||
rGgPtpoDWx2rAOAdw56IltWV35lSz+zYaPGqQglc+pcMfTqE+7FoFFJmOz2cXxIl
|
|
||||||
jZehd7dXmBx/yYYedcamLH1A4vmsazJJIxXSRtt2pQ52hdvkU7kCggEBANHr3Ldc
|
|
||||||
ZYH7KYcowfjfmreAJaRP9SVo+1FtqiwU+I9pzYtT4aXqXLzlJUbHwkWyf3ameghb
|
|
||||||
EWRKuutC39zz71UsongjVpIPCFZjXXPlo5qxMLAZ0I5KBq4xYEoAplNSJye02HvT
|
|
||||||
F9KU/J84YwZuJFhWcFXPp5JiFxpW8t1t8iEOuFdvtNkZ1eOByFCC83a/l4sjKCaZ
|
|
||||||
xemvQCAX9rmZptqAsWAgUmnhJaTcpjv5jfbwqmJDfGgpPxBap8HlpRlF8V0tG/jZ
|
|
||||||
O0EYoPiHloOD/QoCWaqttQGxxxJW+FfDFo4rlo9xst//6nIVXPU59GbHYBXhio9f
|
|
||||||
s2UJXOC8sSh9lUsCggEBAMNbouqyltGiX8w3vicEAJDzWubrVkVsRdNyKLh7ARuM
|
|
||||||
uO1faQ/HuCyoagFx0tt+AnlOQRPU4YprmBZj1f8PSHoCtwfm3FXo7mpJVDu8T5zD
|
|
||||||
Ja7YOAjmLA42m33ZeAxMdZPTvRYSAP4uxMozrg/tl4md8/lYvStAcoMy5jYvUXF1
|
|
||||||
E1iWum5EJV4gqkBI5c18QYIzd/hbluxOUhI/fA9AQLK+I56fGMw0hdT48M5Iu/OU
|
|
||||||
nBVF8tuw7CXNUhxbfIP4Q8ahBcTu3FDBl/qIqsd7Zv7ckaS5Xr7J57189x8zZqNf
|
|
||||||
k/oHZ0Tdwl5cr29B28YC1vtJ7TLotcDZXXPvOeY/sGUCggEABSkCHPPFfwN4it0C
|
|
||||||
n6aHfBlHU5mvkgLZoq/Kbhj53zSfm9wtANIZA3+ygeHpMaNopLcE6u2qKMf5fkz/
|
|
||||||
icPpTzOwrrlXqHF8J/t7UZ0Ef4n5g2qvCMBjF6cZEdigPg4X7k7wv2J6BHArIZLW
|
|
||||||
RFMyy4Ucb8+R8/Q7UyduAulv+UYOW//f9zI+YsBO90Owzmt5Qy9TDlfbWJo5PlC4
|
|
||||||
fOl9A4QEWDOTMw0Yysutvm2tArP5zD6ScVEKPtGrrAWEIHHqs/qm5GAap8f+NP3I
|
|
||||||
QmVdNADIyXxJpcgD97xxkF64UDhcFBycZAs7bSB/T3vkOR6PixonOM0GcOZhBRk+
|
|
||||||
VZt4rwKCAQEAoal+SwvYpMfi0KM8VxsHwOuxSLBs5uwvaEfrDKa1hu/PxJcU4Psc
|
|
||||||
HNCNUH65x+sh7vJkBh4/OgXJiJW7a+NgzZ7bic1wfiNQ0GG4M+qkUwxmbab9z9dx
|
|
||||||
k5161Q0WO8816UvqCI6DhdR8Avv7SbEKmtY8JBZcDKO7X3jKawKDOglxJfktc7wu
|
|
||||||
1BLh8GqiyIXPzAf9emeIoCo73l/ssM4x+/g+j7AGnE3GhjQvSfWEm5BaDXyh+U0S
|
|
||||||
TkH3dgH7K1ZR99geZxZm+OkLdEaOVJ943uT2HUNM9UMt42+7LHWjtQSN9vUTbzi3
|
|
||||||
9NBsWPw9+0E0WCSYBm3uohT+McdAuZnwxQKCAQEAvQhO9GqMMtOnN/QUdU4FHVKl
|
|
||||||
R8vuJpT3w0ywBcHj5aYwPgkLxCYmfnZtD0kNPkP1FjlYz6C+cGUgNPNifoj6CwCA
|
|
||||||
oRrw7mgyhHuoum+7qlFwJzhuyx94Z4B7RbINfEsqk4mXGRssUUUBGd4Sh29w54SU
|
|
||||||
dQMn7s0LiwN6AOwOaAnjD9RlBE+021N9Dax71DuBu1RzA/Z1sC5EFYkL1C5B4kSJ
|
|
||||||
BYd0Nvru5DRidDrJuxr8tnGqOkNqT2kaujkVmFy6ra6nshkbErbd1LXZksM2PP96
|
|
||||||
bM5Pta4jX4ylcS8e5F16zmZZrtuBDla1kizM7tGdPQQztXeBHZzvdQDlWTHqkw==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
1
vendor/gopkg.in/square/go-jose.v1/.gitcookies.sh.enc
generated
vendored
1
vendor/gopkg.in/square/go-jose.v1/.gitcookies.sh.enc
generated
vendored
|
@ -1 +0,0 @@
|
||||||
'|Ę&{tÄU|gGę(ěŹCy=+¨śňcű:u:/pś#~žü["±4¤!nŮAŞDK<Šuf˙hĹażÂ:şü¸ˇ´B/ŁŘ¤ą¤ň_<C588>hÎŰSăT*wĚxĽŻťą-ç|ťŕŔÓ<C594>ŃÄäóĚ㣗A$$â6ŁÁâG)8nĎpűĆˡ3ĚšśoďĎvŽB–3ż]xÝ“Ó2l§G•|qRŢŻ
ö2
5R–Ó×Ç$´ń˝YčˇŢÝ™l‘Ë«yAI"ŰŚ<C5B0>®íĂ»ąĽkÄ|Kĺţ[9ĆâŇĺ=°ú˙źń|@S•3ó#ćťx?ľV„,ľ‚SĆÝőśwPíogŇ6&V6 ©D.dBŠ7
|
|
7
vendor/gopkg.in/square/go-jose.v1/.gitignore
generated
vendored
7
vendor/gopkg.in/square/go-jose.v1/.gitignore
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
*~
|
|
||||||
.*.swp
|
|
||||||
*.out
|
|
||||||
*.test
|
|
||||||
*.pem
|
|
||||||
*.cov
|
|
||||||
jose-util/jose-util
|
|
45
vendor/gopkg.in/square/go-jose.v1/.travis.yml
generated
vendored
45
vendor/gopkg.in/square/go-jose.v1/.travis.yml
generated
vendored
|
@ -1,45 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
||||||
- tip
|
|
||||||
|
|
||||||
go_import_path: gopkg.in/square/go-jose.v1
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- export PATH=$HOME/.local/bin:$PATH
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
# Install encrypted gitcookies to get around bandwidth-limits
|
|
||||||
# that is causing Travis-CI builds to fail. For more info, see
|
|
||||||
# https://github.com/golang/go/issues/12933
|
|
||||||
- openssl aes-256-cbc -K $encrypted_1528c3c2cafd_key -iv $encrypted_1528c3c2cafd_iv -in .gitcookies.sh.enc -out .gitcookies.sh -d || true
|
|
||||||
- bash .gitcookies.sh || true
|
|
||||||
- go get github.com/wadey/gocovmerge
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
- go get golang.org/x/tools/cmd/cover || true
|
|
||||||
- go get code.google.com/p/go.tools/cmd/cover || true
|
|
||||||
- pip install cram --user `whoami`
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test . -v -covermode=count -coverprofile=profile.cov
|
|
||||||
- go test . -tags std_json -v -covermode=count -coverprofile=profile-std-json.cov
|
|
||||||
- go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov
|
|
||||||
- go test ./json -v # no coverage for forked encoding/json package
|
|
||||||
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t
|
|
||||||
- cd ..
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- gocovmerge *.cov */*.cov > merged.coverprofile
|
|
||||||
- $HOME/gopath/bin/goveralls -coverprofile merged.coverprofile -service=travis-ci
|
|
||||||
|
|
10
vendor/gopkg.in/square/go-jose.v1/BUG-BOUNTY.md
generated
vendored
10
vendor/gopkg.in/square/go-jose.v1/BUG-BOUNTY.md
generated
vendored
|
@ -1,10 +0,0 @@
|
||||||
Serious about security
|
|
||||||
======================
|
|
||||||
|
|
||||||
Square recognizes the important contributions the security research community
|
|
||||||
can make. We therefore encourage reporting security issues with the code
|
|
||||||
contained in this repository.
|
|
||||||
|
|
||||||
If you believe you have discovered a security vulnerability, please follow the
|
|
||||||
guidelines at <https://hackerone.com/square-open-source>.
|
|
||||||
|
|
14
vendor/gopkg.in/square/go-jose.v1/CONTRIBUTING.md
generated
vendored
14
vendor/gopkg.in/square/go-jose.v1/CONTRIBUTING.md
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
# Contributing
|
|
||||||
|
|
||||||
If you would like to contribute code to go-jose you can do so through GitHub by
|
|
||||||
forking the repository and sending a pull request.
|
|
||||||
|
|
||||||
When submitting code, please make every effort to follow existing conventions
|
|
||||||
and style in order to keep the code as readable as possible. Please also make
|
|
||||||
sure all tests pass by running `go test`, and format your code with `go fmt`.
|
|
||||||
We also recommend using `golint` and `errcheck`.
|
|
||||||
|
|
||||||
Before your code can be accepted into the project you must also sign the
|
|
||||||
[Individual Contributor License Agreement][1].
|
|
||||||
|
|
||||||
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue