mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Godeps: update coreos/go-oidc to remove net/http/httptest import
Updates #21114
This commit is contained in:
parent
fec00b535f
commit
3df0ca5bf9
10
Godeps/Godeps.json
generated
10
Godeps/Godeps.json
generated
@ -271,23 +271,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/http",
|
"ImportPath": "github.com/coreos/go-oidc/http",
|
||||||
"Rev": "024cdeee09d02fb439eb55bc422e582ac115615b"
|
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/jose",
|
"ImportPath": "github.com/coreos/go-oidc/jose",
|
||||||
"Rev": "024cdeee09d02fb439eb55bc422e582ac115615b"
|
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/key",
|
"ImportPath": "github.com/coreos/go-oidc/key",
|
||||||
"Rev": "024cdeee09d02fb439eb55bc422e582ac115615b"
|
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oauth2",
|
"ImportPath": "github.com/coreos/go-oidc/oauth2",
|
||||||
"Rev": "024cdeee09d02fb439eb55bc422e582ac115615b"
|
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-oidc/oidc",
|
"ImportPath": "github.com/coreos/go-oidc/oidc",
|
||||||
"Rev": "024cdeee09d02fb439eb55bc422e582ac115615b"
|
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-semver/semver",
|
"ImportPath": "github.com/coreos/go-semver/semver",
|
||||||
|
46
Godeps/_workspace/src/github.com/coreos/go-oidc/http/client.go
generated
vendored
46
Godeps/_workspace/src/github.com/coreos/go-oidc/http/client.go
generated
vendored
@ -1,51 +1,7 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
Do(*http.Request) (*http.Response, error)
|
Do(*http.Request) (*http.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestRecorder struct {
|
|
||||||
Response *http.Response
|
|
||||||
Error error
|
|
||||||
|
|
||||||
Request *http.Request
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *RequestRecorder) Do(req *http.Request) (*http.Response, error) {
|
|
||||||
rr.Request = req
|
|
||||||
|
|
||||||
if rr.Response == nil && rr.Error == nil {
|
|
||||||
panic("RequestRecorder Response and Error cannot both be nil")
|
|
||||||
} else if rr.Response != nil && rr.Error != nil {
|
|
||||||
panic("RequestRecorder Response and Error cannot both be non-nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
return rr.Response, rr.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *RequestRecorder) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
return rr.Do(req)
|
|
||||||
}
|
|
||||||
|
25
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/claims.go
generated
vendored
25
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/claims.go
generated
vendored
@ -3,6 +3,7 @@ package jose
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,13 +71,33 @@ func (c Claims) Int64Claim(name string) (int64, bool, error) {
|
|||||||
return v, true, nil
|
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) {
|
func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
|
||||||
v, ok, err := c.Int64Claim(name)
|
v, ok, err := c.Float64Claim(name)
|
||||||
if !ok || err != nil {
|
if !ok || err != nil {
|
||||||
return time.Time{}, ok, err
|
return time.Time{}, ok, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Unix(v, 0).UTC(), true, nil
|
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) {
|
func decodeClaims(payload []byte) (Claims, error) {
|
||||||
|
51
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jose.go
generated
vendored
51
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jose.go
generated
vendored
@ -13,6 +13,57 @@ const (
|
|||||||
HeaderKeyID = "kid"
|
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
|
type JOSEHeader map[string]string
|
||||||
|
|
||||||
func (j JOSEHeader) Validate() error {
|
func (j JOSEHeader) Validate() error {
|
||||||
|
4
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jwk.go
generated
vendored
4
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jwk.go
generated
vendored
@ -70,6 +70,10 @@ func (j *JWK) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JWKSet struct {
|
||||||
|
Keys []JWK `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
func decodeExponent(e string) (int, error) {
|
func decodeExponent(e string) (int, error) {
|
||||||
decE, err := decodeBase64URLPaddingOptional(e)
|
decE, err := decodeBase64URLPaddingOptional(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
2
Godeps/_workspace/src/github.com/coreos/go-oidc/key/key.go
generated
vendored
2
Godeps/_workspace/src/github.com/coreos/go-oidc/key/key.go
generated
vendored
@ -135,7 +135,7 @@ func (s *PrivateKeySet) Active() *PrivateKey {
|
|||||||
type GeneratePrivateKeyFunc func() (*PrivateKey, error)
|
type GeneratePrivateKeyFunc func() (*PrivateKey, error)
|
||||||
|
|
||||||
func GeneratePrivateKey() (*PrivateKey, error) {
|
func GeneratePrivateKey() (*PrivateKey, error) {
|
||||||
pk, err := rsa.GenerateKey(rand.Reader, 1024)
|
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
12
Godeps/_workspace/src/github.com/coreos/go-oidc/key/repo.go
generated
vendored
12
Godeps/_workspace/src/github.com/coreos/go-oidc/key/repo.go
generated
vendored
@ -1,6 +1,9 @@
|
|||||||
package key
|
package key
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
var ErrorNoKeys = errors.New("no keys found")
|
var ErrorNoKeys = errors.New("no keys found")
|
||||||
|
|
||||||
@ -22,6 +25,7 @@ func NewPrivateKeySetRepo() PrivateKeySetRepo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type memPrivateKeySetRepo struct {
|
type memPrivateKeySetRepo struct {
|
||||||
|
mu sync.RWMutex
|
||||||
pks PrivateKeySet
|
pks PrivateKeySet
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,11 +37,17 @@ func (r *memPrivateKeySetRepo) Set(ks KeySet) error {
|
|||||||
return errors.New("nil KeySet")
|
return errors.New("nil KeySet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
r.pks = *pks
|
r.pks = *pks
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *memPrivateKeySetRepo) Get() (KeySet, error) {
|
func (r *memPrivateKeySetRepo) Get() (KeySet, error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
if r.pks.keys == nil {
|
if r.pks.keys == nil {
|
||||||
return nil, ErrorNoKeys
|
return nil, ErrorNoKeys
|
||||||
}
|
}
|
||||||
|
8
Godeps/_workspace/src/github.com/coreos/go-oidc/key/sync.go
generated
vendored
8
Godeps/_workspace/src/github.com/coreos/go-oidc/key/sync.go
generated
vendored
@ -29,7 +29,7 @@ func (s *KeySetSyncer) Run() chan struct{} {
|
|||||||
var failing bool
|
var failing bool
|
||||||
var next time.Duration
|
var next time.Duration
|
||||||
for {
|
for {
|
||||||
exp, err := sync(s.readable, s.writable, s.clock)
|
exp, err := syncKeySet(s.readable, s.writable, s.clock)
|
||||||
if err != nil || exp == 0 {
|
if err != nil || exp == 0 {
|
||||||
if !failing {
|
if !failing {
|
||||||
failing = true
|
failing = true
|
||||||
@ -62,12 +62,12 @@ func (s *KeySetSyncer) Run() chan struct{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Sync(r ReadableKeySetRepo, w WritableKeySetRepo) (time.Duration, error) {
|
func Sync(r ReadableKeySetRepo, w WritableKeySetRepo) (time.Duration, error) {
|
||||||
return sync(r, w, clockwork.NewRealClock())
|
return syncKeySet(r, w, clockwork.NewRealClock())
|
||||||
}
|
}
|
||||||
|
|
||||||
// sync copies the keyset from r to the KeySet at w and returns the duration in which the KeySet will expire.
|
// 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.
|
// If keyset has already expired, returns a zero duration.
|
||||||
func sync(r ReadableKeySetRepo, w WritableKeySetRepo, clock clockwork.Clock) (exp time.Duration, err error) {
|
func syncKeySet(r ReadableKeySetRepo, w WritableKeySetRepo, clock clockwork.Clock) (exp time.Duration, err error) {
|
||||||
var ks KeySet
|
var ks KeySet
|
||||||
ks, err = r.Get()
|
ks, err = r.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
45
Godeps/_workspace/src/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
45
Godeps/_workspace/src/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
@ -8,14 +8,49 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
phttp "github.com/coreos/go-oidc/http"
|
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 (
|
const (
|
||||||
|
// OAuth2.0 response types registered by OIDC.
|
||||||
|
//
|
||||||
|
// See: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#RegistryContents
|
||||||
ResponseTypeCode = "code"
|
ResponseTypeCode = "code"
|
||||||
|
ResponseTypeCodeIDToken = "code id_token"
|
||||||
|
ResponseTypeCodeIDTokenToken = "code id_token token"
|
||||||
|
ResponseTypeIDToken = "id_token"
|
||||||
|
ResponseTypeIDTokenToken = "id_token token"
|
||||||
|
ResponseTypeToken = "token"
|
||||||
|
ResponseTypeNone = "none"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -136,22 +171,24 @@ func (c *Client) commonURLValues() url.Values {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) newAuthenticatedRequest(url string, values url.Values) (*http.Request, error) {
|
func (c *Client) newAuthenticatedRequest(urlToken string, values url.Values) (*http.Request, error) {
|
||||||
var req *http.Request
|
var req *http.Request
|
||||||
var err error
|
var err error
|
||||||
switch c.authMethod {
|
switch c.authMethod {
|
||||||
case AuthMethodClientSecretPost:
|
case AuthMethodClientSecretPost:
|
||||||
values.Set("client_secret", c.creds.Secret)
|
values.Set("client_secret", c.creds.Secret)
|
||||||
req, err = http.NewRequest("POST", url, strings.NewReader(values.Encode()))
|
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case AuthMethodClientSecretBasic:
|
case AuthMethodClientSecretBasic:
|
||||||
req, err = http.NewRequest("POST", url, strings.NewReader(values.Encode()))
|
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.SetBasicAuth(c.creds.ID, c.creds.Secret)
|
encodedID := url.QueryEscape(c.creds.ID)
|
||||||
|
encodedSecret := url.QueryEscape(c.creds.Secret)
|
||||||
|
req.SetBasicAuth(encodedID, encodedSecret)
|
||||||
default:
|
default:
|
||||||
panic("misconfigured client: auth method not supported")
|
panic("misconfigured client: auth method not supported")
|
||||||
}
|
}
|
||||||
|
574
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
574
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
@ -1,9 +1,11 @@
|
|||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -36,23 +38,520 @@ type ClientIdentity struct {
|
|||||||
Metadata ClientMetadata
|
Metadata ClientMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientMetadata struct {
|
type JWAOptions struct {
|
||||||
RedirectURLs []url.URL
|
// 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 {
|
func (m *ClientMetadata) Valid() error {
|
||||||
if len(m.RedirectURLs) == 0 {
|
if len(m.RedirectURIs) == 0 {
|
||||||
return errors.New("zero redirect URLs")
|
return errors.New("zero redirect URLs")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range m.RedirectURLs {
|
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" {
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
return errors.New("invalid redirect URL: scheme not http/https")
|
return fmt.Errorf("uri field %s scheme is not http or https", fieldName)
|
||||||
} else if u.Host == "" {
|
}
|
||||||
return errors.New("invalid redirect URL: host empty")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,34 +600,12 @@ type Client struct {
|
|||||||
redirectURL string
|
redirectURL string
|
||||||
scope []string
|
scope []string
|
||||||
keySet key.PublicKeySet
|
keySet key.PublicKeySet
|
||||||
|
providerSyncer *ProviderConfigSyncer
|
||||||
|
|
||||||
keySetSyncMutex sync.RWMutex
|
keySetSyncMutex sync.RWMutex
|
||||||
lastKeySetSync time.Time
|
lastKeySetSync time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Healthy() error {
|
func (c *Client) Healthy() error {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
@ -155,8 +632,8 @@ func (c *Client) OAuthClient() (*oauth2.Client, error) {
|
|||||||
ocfg := oauth2.Config{
|
ocfg := oauth2.Config{
|
||||||
Credentials: oauth2.ClientCredentials(c.credentials),
|
Credentials: oauth2.ClientCredentials(c.credentials),
|
||||||
RedirectURL: c.redirectURL,
|
RedirectURL: c.redirectURL,
|
||||||
AuthURL: cfg.AuthEndpoint,
|
AuthURL: cfg.AuthEndpoint.String(),
|
||||||
TokenURL: cfg.TokenEndpoint,
|
TokenURL: cfg.TokenEndpoint.String(),
|
||||||
Scope: c.scope,
|
Scope: c.scope,
|
||||||
AuthMethod: authMethod,
|
AuthMethod: authMethod,
|
||||||
}
|
}
|
||||||
@ -178,9 +655,13 @@ func chooseAuthMethod(cfg ProviderConfig) (string, error) {
|
|||||||
return "", errors.New("no supported auth methods")
|
return "", errors.New("no supported auth methods")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncProviderConfig starts the provider config syncer
|
||||||
func (c *Client) SyncProviderConfig(discoveryURL string) chan struct{} {
|
func (c *Client) SyncProviderConfig(discoveryURL string) chan struct{} {
|
||||||
r := NewHTTPProviderConfigGetter(c.httpClient, discoveryURL)
|
r := NewHTTPProviderConfigGetter(c.httpClient, discoveryURL)
|
||||||
return NewProviderConfigSyncer(r, c.providerConfig).Run()
|
s := NewProviderConfigSyncer(r, c.providerConfig)
|
||||||
|
stop := s.Run()
|
||||||
|
s.WaitUntilInitialSync()
|
||||||
|
return stop
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) maybeSyncKeys() error {
|
func (c *Client) maybeSyncKeys() error {
|
||||||
@ -204,7 +685,7 @@ func (c *Client) maybeSyncKeys() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := c.providerConfig.Get()
|
cfg := c.providerConfig.Get()
|
||||||
r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint)
|
r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint.String())
|
||||||
w := &clientKeyRepo{client: c}
|
w := &clientKeyRepo{client: c}
|
||||||
_, err := key.Sync(r, w)
|
_, err := key.Sync(r, w)
|
||||||
c.lastKeySetSync = time.Now().UTC()
|
c.lastKeySetSync = time.Now().UTC()
|
||||||
@ -299,7 +780,7 @@ func (c *Client) VerifyJWT(jwt jose.JWT) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v := NewJWTVerifier(
|
v := NewJWTVerifier(
|
||||||
c.providerConfig.Get().Issuer,
|
c.providerConfig.Get().Issuer.String(),
|
||||||
c.credentials.ID,
|
c.credentials.ID,
|
||||||
c.maybeSyncKeys, keysFunc)
|
c.maybeSyncKeys, keysFunc)
|
||||||
|
|
||||||
@ -340,3 +821,26 @@ func (c *Client) keysFuncAll() func() []key.PublicKey {
|
|||||||
return c.keySet.Keys()
|
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
|
||||||
|
}
|
||||||
|
443
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
443
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
@ -2,8 +2,11 @@ package oidc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
@ -18,6 +21,26 @@ var (
|
|||||||
log = capnslog.NewPackageLogger("github.com/coreos/go-oidc", "http")
|
log = capnslog.NewPackageLogger("github.com/coreos/go-oidc", "http")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 (
|
const (
|
||||||
MaximumProviderConfigSyncInterval = 24 * time.Hour
|
MaximumProviderConfigSyncInterval = 24 * time.Hour
|
||||||
MinimumProviderConfigSyncInterval = time.Minute
|
MinimumProviderConfigSyncInterval = time.Minute
|
||||||
@ -28,29 +51,414 @@ const (
|
|||||||
// internally configurable for tests
|
// internally configurable for tests
|
||||||
var minimumProviderConfigSyncInterval = MinimumProviderConfigSyncInterval
|
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 {
|
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
|
||||||
|
|
||||||
|
// 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"`
|
Issuer string `json:"issuer"`
|
||||||
AuthEndpoint string `json:"authorization_endpoint"`
|
AuthEndpoint string `json:"authorization_endpoint"`
|
||||||
TokenEndpoint string `json:"token_endpoint"`
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||||
KeysEndpoint string `json:"jwks_uri"`
|
KeysEndpoint string `json:"jwks_uri"`
|
||||||
ResponseTypesSupported []string `json:"response_types_supported"`
|
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
|
||||||
GrantTypesSupported []string `json:"grant_types_supported"`
|
|
||||||
SubjectTypesSupported []string `json:"subject_types_supported"`
|
// Use 'omitempty' for all slices as per OIDC spec:
|
||||||
IDTokenAlgValuesSupported []string `json:"id_token_alg_values_supported"`
|
// "Claims that return multiple values are represented as JSON arrays.
|
||||||
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
|
// Claims with zero elements MUST be omitted from the response."
|
||||||
ExpiresAt time.Time `json:"-"`
|
// 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),
|
||||||
|
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"),
|
||||||
|
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 {
|
func (p ProviderConfig) Empty() bool {
|
||||||
return p.Issuer == ""
|
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.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 {
|
func (p ProviderConfig) SupportsGrantType(grantType string) bool {
|
||||||
var supported []string
|
var supported []string
|
||||||
if len(p.GrantTypesSupported) == 0 {
|
if len(p.GrantTypesSupported) == 0 {
|
||||||
// If omitted, the default value is ["authorization_code", "implicit"].
|
supported = DefaultGrantTypesSupported
|
||||||
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
|
||||||
supported = []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeImplicit}
|
|
||||||
} else {
|
} else {
|
||||||
supported = p.GrantTypesSupported
|
supported = p.GrantTypesSupported
|
||||||
}
|
}
|
||||||
@ -75,6 +483,9 @@ type ProviderConfigSyncer struct {
|
|||||||
from ProviderConfigGetter
|
from ProviderConfigGetter
|
||||||
to ProviderConfigSetter
|
to ProviderConfigSetter
|
||||||
clock clockwork.Clock
|
clock clockwork.Clock
|
||||||
|
|
||||||
|
initialSyncDone bool
|
||||||
|
initialSyncWait sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProviderConfigSyncer(from ProviderConfigGetter, to ProviderConfigSetter) *ProviderConfigSyncer {
|
func NewProviderConfigSyncer(from ProviderConfigGetter, to ProviderConfigSetter) *ProviderConfigSyncer {
|
||||||
@ -91,6 +502,7 @@ func (s *ProviderConfigSyncer) Run() chan struct{} {
|
|||||||
var next pcsStepper
|
var next pcsStepper
|
||||||
next = &pcsStepNext{aft: time.Duration(0)}
|
next = &pcsStepNext{aft: time.Duration(0)}
|
||||||
|
|
||||||
|
s.initialSyncWait.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -105,6 +517,10 @@ func (s *ProviderConfigSyncer) Run() chan struct{} {
|
|||||||
return stop
|
return stop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ProviderConfigSyncer) WaitUntilInitialSync() {
|
||||||
|
s.initialSyncWait.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
|
func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
|
||||||
cfg, err := s.from.Get()
|
cfg, err := s.from.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,6 +531,11 @@ func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
|
|||||||
return 0, fmt.Errorf("error setting provider config: %v", err)
|
return 0, fmt.Errorf("error setting provider config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !s.initialSyncDone {
|
||||||
|
s.initialSyncWait.Done()
|
||||||
|
s.initialSyncDone = true
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("Updating provider config: config=%#v", cfg)
|
log.Infof("Updating provider config: config=%#v", cfg)
|
||||||
|
|
||||||
return nextSyncAfter(cfg.ExpiresAt, s.clock), nil
|
return nextSyncAfter(cfg.ExpiresAt, s.clock), nil
|
||||||
@ -223,7 +644,7 @@ func (r *httpProviderConfigGetter) Get() (cfg ProviderConfig, err error) {
|
|||||||
|
|
||||||
// The issuer value returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information.
|
// 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
|
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation
|
||||||
if !urlEqual(cfg.Issuer, r.issuerURL) {
|
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)
|
err = fmt.Errorf(`"issuer" in config (%v) does not match provided issuer URL (%v)`, cfg.Issuer, r.issuerURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
4
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/util.go
generated
vendored
4
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/util.go
generated
vendored
@ -59,8 +59,8 @@ func NewClaims(iss, sub string, aud interface{}, iat, exp time.Time) jose.Claims
|
|||||||
"iss": iss,
|
"iss": iss,
|
||||||
"sub": sub,
|
"sub": sub,
|
||||||
"aud": aud,
|
"aud": aud,
|
||||||
"iat": float64(iat.Unix()),
|
"iat": iat.Unix(),
|
||||||
"exp": float64(exp.Unix()),
|
"exp": exp.Unix(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user