forked from mystiq/dex
Merge pull request #267 from ericchiang/metadata
add dynamic client registration
This commit is contained in:
commit
af790e46bb
38 changed files with 1520 additions and 215 deletions
|
@ -14,7 +14,7 @@ We'll also start the example web app, so we can try registering and logging in.
|
|||
Before continuing, you must have the following installed on your system:
|
||||
|
||||
* Go 1.4 or greater
|
||||
* Postgres 9.0 or greater (this guide also assumes that Postgres is up and running)
|
||||
* Postgres 9.4 or greater (this guide also assumes that Postgres is up and running)
|
||||
|
||||
In addition, if you wish to try out authenticating against Google's OIDC backend, you must have a new client registered with Google:
|
||||
|
||||
|
|
10
Godeps/Godeps.json
generated
10
Godeps/Godeps.json
generated
|
@ -16,23 +16,23 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/http",
|
||||
"Rev": "145916abb78708694762ff359ab1e34c47c7947f"
|
||||
"Rev": "6039032c0b15517897116d333ead8edf38792437"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/jose",
|
||||
"Rev": "145916abb78708694762ff359ab1e34c47c7947f"
|
||||
"Rev": "6039032c0b15517897116d333ead8edf38792437"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/key",
|
||||
"Rev": "145916abb78708694762ff359ab1e34c47c7947f"
|
||||
"Rev": "6039032c0b15517897116d333ead8edf38792437"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/oauth2",
|
||||
"Rev": "145916abb78708694762ff359ab1e34c47c7947f"
|
||||
"Rev": "6039032c0b15517897116d333ead8edf38792437"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/oidc",
|
||||
"Rev": "145916abb78708694762ff359ab1e34c47c7947f"
|
||||
"Rev": "6039032c0b15517897116d333ead8edf38792437"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/pkg/capnslog",
|
||||
|
|
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"
|
||||
)
|
||||
|
||||
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 {
|
||||
|
|
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
|
||||
}
|
||||
|
||||
type JWKSet struct {
|
||||
Keys []JWK `json:"keys"`
|
||||
}
|
||||
|
||||
func decodeExponent(e string) (int, error) {
|
||||
decE, err := decodeBase64URLPaddingOptional(e)
|
||||
if err != nil {
|
||||
|
|
37
Godeps/_workspace/src/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
37
Godeps/_workspace/src/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
|
@ -8,14 +8,49 @@ import (
|
|||
"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 (
|
||||
ResponseTypeCode = "code"
|
||||
// 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 (
|
||||
|
|
519
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
519
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/client.go
generated
vendored
|
@ -1,9 +1,11 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -36,23 +38,518 @@ type ClientIdentity struct {
|
|||
Metadata ClientMetadata
|
||||
}
|
||||
|
||||
type ClientMetadata struct {
|
||||
RedirectURLs []url.URL
|
||||
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
|
||||
}
|
||||
uris[i] = *(p.parseURI(val, field))
|
||||
}
|
||||
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.RedirectURLs) == 0 {
|
||||
if len(m.RedirectURIs) == 0 {
|
||||
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" {
|
||||
return errors.New("invalid redirect URL: scheme not http/https")
|
||||
} else if u.Host == "" {
|
||||
return errors.New("invalid redirect URL: host empty")
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -133,8 +630,8 @@ func (c *Client) OAuthClient() (*oauth2.Client, error) {
|
|||
ocfg := oauth2.Config{
|
||||
Credentials: oauth2.ClientCredentials(c.credentials),
|
||||
RedirectURL: c.redirectURL,
|
||||
AuthURL: cfg.AuthEndpoint,
|
||||
TokenURL: cfg.TokenEndpoint,
|
||||
AuthURL: cfg.AuthEndpoint.String(),
|
||||
TokenURL: cfg.TokenEndpoint.String(),
|
||||
Scope: c.scope,
|
||||
AuthMethod: authMethod,
|
||||
}
|
||||
|
@ -186,7 +683,7 @@ func (c *Client) maybeSyncKeys() error {
|
|||
}
|
||||
|
||||
cfg := c.providerConfig.Get()
|
||||
r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint)
|
||||
r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint.String())
|
||||
w := &clientKeyRepo{client: c}
|
||||
_, err := key.Sync(r, w)
|
||||
c.lastKeySetSync = time.Now().UTC()
|
||||
|
@ -281,7 +778,7 @@ func (c *Client) VerifyJWT(jwt jose.JWT) error {
|
|||
}
|
||||
|
||||
v := NewJWTVerifier(
|
||||
c.providerConfig.Get().Issuer,
|
||||
c.providerConfig.Get().Issuer.String(),
|
||||
c.credentials.ID,
|
||||
c.maybeSyncKeys, keysFunc)
|
||||
|
||||
|
|
437
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
437
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
|
@ -2,8 +2,10 @@ package oidc
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -19,6 +21,26 @@ var (
|
|||
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 (
|
||||
MaximumProviderConfigSyncInterval = 24 * time.Hour
|
||||
MinimumProviderConfigSyncInterval = time.Minute
|
||||
|
@ -29,29 +51,414 @@ const (
|
|||
// 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 string `json:"issuer"`
|
||||
AuthEndpoint string `json:"authorization_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
KeysEndpoint string `json:"jwks_uri"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported"`
|
||||
IDTokenAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
||||
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
|
||||
ExpiresAt time.Time `json:"-"`
|
||||
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"`
|
||||
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"`
|
||||
|
||||
// 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),
|
||||
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 {
|
||||
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 {
|
||||
var supported []string
|
||||
if len(p.GrantTypesSupported) == 0 {
|
||||
// If omitted, the default value is ["authorization_code", "implicit"].
|
||||
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||
supported = []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeImplicit}
|
||||
supported = DefaultGrantTypesSupported
|
||||
} else {
|
||||
supported = p.GrantTypesSupported
|
||||
}
|
||||
|
@ -237,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.
|
||||
// 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)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ dex consists of multiple components:
|
|||
- configure identity provider connectors
|
||||
- administer OIDC client identities
|
||||
- **database**; a database is used to for persistent storage for keys, users,
|
||||
OAuth sessions and other data. Currently Postgres is the only supported
|
||||
OAuth sessions and other data. Currently Postgres (9.4+) is the only supported
|
||||
database.
|
||||
|
||||
A typical dex deployment consists of N dex-workers behind a load balanacer, and one dex-overlord.
|
||||
|
|
|
@ -172,7 +172,7 @@ func (ci *clientIdentity) UnmarshalJSON(data []byte) error {
|
|||
Secret: c.Secret,
|
||||
}
|
||||
ci.Metadata = oidc.ClientMetadata{
|
||||
RedirectURLs: make([]url.URL, len(c.RedirectURLs)),
|
||||
RedirectURIs: make([]url.URL, len(c.RedirectURLs)),
|
||||
}
|
||||
|
||||
for i, us := range c.RedirectURLs {
|
||||
|
@ -180,7 +180,7 @@ func (ci *clientIdentity) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ci.Metadata.RedirectURLs[i] = *up
|
||||
ci.Metadata.RedirectURIs[i] = *up
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
|
|||
{
|
||||
id: "foo",
|
||||
meta: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
|
@ -29,7 +29,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
|
|||
{
|
||||
id: "bar",
|
||||
meta: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "https", Host: "example.com/foo"},
|
||||
url.URL{Scheme: "https", Host: "example.com/bar"},
|
||||
},
|
||||
|
@ -60,8 +60,8 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
|
|||
t.Errorf("case %d: expected repo to contain newly created Client", i)
|
||||
}
|
||||
|
||||
wantURLs := tt.meta.RedirectURLs
|
||||
gotURLs := all[0].Metadata.RedirectURLs
|
||||
wantURLs := tt.meta.RedirectURIs
|
||||
gotURLs := all[0].Metadata.RedirectURIs
|
||||
if !reflect.DeepEqual(wantURLs, gotURLs) {
|
||||
t.Errorf("case %d: redirect url mismatch, want=%v, got=%v", i, wantURLs, gotURLs)
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func TestMemClientIdentityRepoNewDuplicate(t *testing.T) {
|
|||
cr := NewClientIdentityRepo(nil)
|
||||
|
||||
meta1 := oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "https", Host: "foo.example.com"},
|
||||
},
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func TestMemClientIdentityRepoNewDuplicate(t *testing.T) {
|
|||
}
|
||||
|
||||
meta2 := oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "https", Host: "bar.example.com"},
|
||||
},
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ func TestClientIdentityUnmarshalJSON(t *testing.T) {
|
|||
sort.Strings(expectedURLs)
|
||||
|
||||
actualURLs := make([]string, 0)
|
||||
for _, u := range actual.Metadata.RedirectURLs {
|
||||
for _, u := range actual.Metadata.RedirectURIs {
|
||||
actualURLs = append(actualURLs, u.String())
|
||||
}
|
||||
sort.Strings(actualURLs)
|
||||
|
|
|
@ -45,6 +45,7 @@ func main() {
|
|||
emailConfig := fs.String("email-cfg", "./static/fixtures/emailer.json", "configures emailer.")
|
||||
|
||||
enableRegistration := fs.Bool("enable-registration", false, "Allows users to self-register")
|
||||
enableClientRegistration := fs.Bool("enable-client-registration", false, "Allow dynamic registration of clients")
|
||||
|
||||
noDB := fs.Bool("no-db", false, "manage entities in-process w/o any encryption, used only for single-node testing")
|
||||
|
||||
|
@ -117,14 +118,15 @@ func main() {
|
|||
}
|
||||
|
||||
scfg := server.ServerConfig{
|
||||
IssuerURL: *issuer,
|
||||
TemplateDir: *templates,
|
||||
EmailTemplateDirs: emailTemplateDirs,
|
||||
EmailFromAddress: *emailFrom,
|
||||
EmailerConfigFile: *emailConfig,
|
||||
IssuerName: *issuerName,
|
||||
IssuerLogoURL: *issuerLogoURL,
|
||||
EnableRegistration: *enableRegistration,
|
||||
IssuerURL: *issuer,
|
||||
TemplateDir: *templates,
|
||||
EmailTemplateDirs: emailTemplateDirs,
|
||||
EmailFromAddress: *emailFrom,
|
||||
EmailerConfigFile: *emailConfig,
|
||||
IssuerName: *issuerName,
|
||||
IssuerLogoURL: *issuerLogoURL,
|
||||
EnableRegistration: *enableRegistration,
|
||||
EnableClientRegistration: *enableClientRegistration,
|
||||
}
|
||||
|
||||
if *noDB {
|
||||
|
|
|
@ -37,7 +37,7 @@ func runNewClient(cmd *cobra.Command, args []string) int {
|
|||
redirectURLs[i] = *u
|
||||
}
|
||||
|
||||
cc, err := getDriver().NewClient(oidc.ClientMetadata{RedirectURLs: redirectURLs})
|
||||
cc, err := getDriver().NewClient(oidc.ClientMetadata{RedirectURIs: redirectURLs})
|
||||
if err != nil {
|
||||
stderr("Failed creating new client: %v", err)
|
||||
return 1
|
||||
|
|
|
@ -21,13 +21,13 @@ func newAPIDriver(pcfg oidc.ProviderConfig, creds oidc.ClientCredentials) (drive
|
|||
|
||||
trans := &oidc.AuthenticatedTransport{
|
||||
TokenRefresher: &oidc.ClientCredsTokenRefresher{
|
||||
Issuer: pcfg.Issuer,
|
||||
Issuer: pcfg.Issuer.String(),
|
||||
OIDCClient: oc,
|
||||
},
|
||||
RoundTripper: http.DefaultTransport,
|
||||
}
|
||||
hc := &http.Client{Transport: trans}
|
||||
svc, err := schema.NewWithBasePath(hc, pcfg.Issuer)
|
||||
svc, err := schema.NewWithBasePath(hc, pcfg.Issuer.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -41,10 +41,10 @@ type apiDriver struct {
|
|||
|
||||
func (d *apiDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials, error) {
|
||||
sc := &schema.Client{
|
||||
RedirectURIs: make([]string, len(meta.RedirectURLs)),
|
||||
RedirectURIs: make([]string, len(meta.RedirectURIs)),
|
||||
}
|
||||
|
||||
for i, u := range meta.RedirectURLs {
|
||||
for i, u := range meta.RedirectURIs {
|
||||
sc.RedirectURIs[i] = u.String()
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ func (d *dbDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
clientID, err := oidc.GenClientID(meta.RedirectURLs[0].Host)
|
||||
clientID, err := oidc.GenClientID(meta.RedirectURIs[0].Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -89,8 +89,8 @@ func TestLoginURL(t *testing.T) {
|
|||
Credentials: oidc.ClientCredentials{ID: tt.cid, Secret: "fake-client-secret"},
|
||||
RedirectURL: tt.redir,
|
||||
ProviderConfig: oidc.ProviderConfig{
|
||||
AuthEndpoint: "http://example.com/authorize",
|
||||
TokenEndpoint: "http://example.com/token",
|
||||
AuthEndpoint: &url.URL{Scheme: "http", Host: "example.com", Path: "/authorize"},
|
||||
TokenEndpoint: &url.URL{Scheme: "http", Host: "example.com", Path: "/token"},
|
||||
},
|
||||
Scope: tt.scope,
|
||||
}
|
||||
|
|
45
db/client.go
45
db/client.go
|
@ -6,7 +6,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
|
@ -49,7 +48,7 @@ func newClientIdentityModel(id string, secret []byte, meta *oidc.ClientMetadata)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
bmeta, err := json.Marshal(newClientMetadataJSON(meta))
|
||||
bmeta, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -70,38 +69,6 @@ type clientIdentityModel struct {
|
|||
DexAdmin bool `db:"dex_admin"`
|
||||
}
|
||||
|
||||
func newClientMetadataJSON(cm *oidc.ClientMetadata) *clientMetadataJSON {
|
||||
cmj := clientMetadataJSON{
|
||||
RedirectURLs: make([]string, len(cm.RedirectURLs)),
|
||||
}
|
||||
|
||||
for i, u := range cm.RedirectURLs {
|
||||
cmj.RedirectURLs[i] = (&u).String()
|
||||
}
|
||||
|
||||
return &cmj
|
||||
}
|
||||
|
||||
type clientMetadataJSON struct {
|
||||
RedirectURLs []string `json:"redirectURLs"`
|
||||
}
|
||||
|
||||
func (cmj clientMetadataJSON) ClientMetadata() (*oidc.ClientMetadata, error) {
|
||||
cm := oidc.ClientMetadata{
|
||||
RedirectURLs: make([]url.URL, len(cmj.RedirectURLs)),
|
||||
}
|
||||
|
||||
for i, us := range cmj.RedirectURLs {
|
||||
up, err := url.Parse(us)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cm.RedirectURLs[i] = *up
|
||||
}
|
||||
|
||||
return &cm, nil
|
||||
}
|
||||
|
||||
func (m *clientIdentityModel) ClientIdentity() (*oidc.ClientIdentity, error) {
|
||||
ci := oidc.ClientIdentity{
|
||||
Credentials: oidc.ClientCredentials{
|
||||
|
@ -110,18 +77,10 @@ func (m *clientIdentityModel) ClientIdentity() (*oidc.ClientIdentity, error) {
|
|||
},
|
||||
}
|
||||
|
||||
var cmj clientMetadataJSON
|
||||
err := json.Unmarshal([]byte(m.Metadata), &cmj)
|
||||
if err != nil {
|
||||
if err := json.Unmarshal([]byte(m.Metadata), &ci.Metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cm, err := cmj.ClientMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ci.Metadata = *cm
|
||||
return &ci, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package db
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/go-gorp/gorp"
|
||||
|
@ -25,7 +26,7 @@ func initDB(dsn string) *gorp.DbMap {
|
|||
func TestGetPlannedMigrations(t *testing.T) {
|
||||
dsn := os.Getenv("DEX_TEST_DSN")
|
||||
if dsn == "" {
|
||||
t.Logf("Test will not run without DEX_TEST_DSN environment variable.")
|
||||
t.Skip("Test will not run without DEX_TEST_DSN environment variable.")
|
||||
return
|
||||
}
|
||||
dbMap := initDB(dsn)
|
||||
|
@ -40,3 +41,81 @@ func TestGetPlannedMigrations(t *testing.T) {
|
|||
t.Fatalf("expected non-empty migrations")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateClientMetadata(t *testing.T) {
|
||||
dsn := os.Getenv("DEX_TEST_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("Test will not run without DEX_TEST_DSN environment variable.")
|
||||
return
|
||||
}
|
||||
dbMap := initDB(dsn)
|
||||
|
||||
nMigrations := 9
|
||||
n, err := MigrateMaxMigrations(dbMap, nMigrations)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to perform initial migration: %v", err)
|
||||
}
|
||||
if n != nMigrations {
|
||||
t.Fatalf("expected to perform %d migrations, got %d", nMigrations, n)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
before string
|
||||
after string
|
||||
}{
|
||||
// only update rows without a "redirect_uris" key
|
||||
{
|
||||
`{"redirectURLs":["foo"]}`,
|
||||
`{"redirectURLs" : ["foo"], "redirect_uris" : ["foo"]}`,
|
||||
},
|
||||
{
|
||||
`{"redirectURLs":["foo","bar"]}`,
|
||||
`{"redirectURLs" : ["foo","bar"], "redirect_uris" : ["foo","bar"]}`,
|
||||
},
|
||||
{
|
||||
`{"redirect_uris":["foo"],"another_field":8}`,
|
||||
`{"redirect_uris":["foo"],"another_field":8}`,
|
||||
},
|
||||
{
|
||||
`{"redirectURLs" : ["foo"], "redirect_uris" : ["foo"]}`,
|
||||
`{"redirectURLs" : ["foo"], "redirect_uris" : ["foo"]}`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
model := &clientIdentityModel{
|
||||
ID: strconv.Itoa(i),
|
||||
Secret: []byte("verysecret"),
|
||||
Metadata: tt.before,
|
||||
}
|
||||
if err := dbMap.Insert(model); err != nil {
|
||||
t.Fatalf("could not insert model: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
n, err = MigrateMaxMigrations(dbMap, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to perform initial migration: %v", err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("expected to perform 1 migration, got %d", n)
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
id := strconv.Itoa(i)
|
||||
m, err := dbMap.Get(clientIdentityModel{}, id)
|
||||
if err != nil {
|
||||
t.Errorf("case %d: failed to get model: %err", i, err)
|
||||
continue
|
||||
}
|
||||
cim, ok := m.(*clientIdentityModel)
|
||||
if !ok {
|
||||
t.Errorf("case %d: unrecognized model type: %T", i, m)
|
||||
continue
|
||||
}
|
||||
|
||||
if cim.Metadata != tt.after {
|
||||
t.Errorf("case %d: want=%q, got=%q", i, tt.after, cim.Metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
9
db/migrations/0010_client_metadata_field_changed.sql
Normal file
9
db/migrations/0010_client_metadata_field_changed.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
-- +migrate Up
|
||||
UPDATE client_identity
|
||||
SET metadata = text(
|
||||
json_build_object(
|
||||
'redirectURLs', json(json(metadata)->>'redirectURLs'),
|
||||
'redirect_uris', json(json(metadata)->>'redirectURLs')
|
||||
)
|
||||
)
|
||||
WHERE (json(metadata)->>'redirect_uris') IS NULL;
|
|
@ -9,6 +9,7 @@
|
|||
// 0007_session_scope.sql
|
||||
// 0008_users_active_or_inactive.sql
|
||||
// 0009_key_not_primary_key.sql
|
||||
// 0010_client_metadata_field_changed.sql
|
||||
// DO NOT EDIT!
|
||||
|
||||
package migrations
|
||||
|
@ -91,7 +92,7 @@ func dbMigrations0001_initial_migrationSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0001_initial_migration.sql", size: 1388, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0001_initial_migration.sql", size: 1388, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -111,7 +112,7 @@ func dbMigrations0002_dex_adminSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0002_dex_admin.sql", size: 126, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0002_dex_admin.sql", size: 126, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -131,7 +132,7 @@ func dbMigrations0003_user_created_atSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0003_user_created_at.sql", size: 111, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0003_user_created_at.sql", size: 111, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -151,7 +152,7 @@ func dbMigrations0004_session_nonceSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0004_session_nonce.sql", size: 60, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0004_session_nonce.sql", size: 60, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -171,7 +172,7 @@ func dbMigrations0005_refresh_token_createSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0005_refresh_token_create.sql", size: 506, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0005_refresh_token_create.sql", size: 506, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -191,7 +192,7 @@ func dbMigrations0006_user_email_uniqueSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0006_user_email_unique.sql", size: 99, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0006_user_email_unique.sql", size: 99, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -211,7 +212,7 @@ func dbMigrations0007_session_scopeSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0007_session_scope.sql", size: 60, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0007_session_scope.sql", size: 60, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -231,7 +232,7 @@ func dbMigrations0008_users_active_or_inactiveSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0008_users_active_or_inactive.sql", size: 110, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0008_users_active_or_inactive.sql", size: 110, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -251,7 +252,27 @@ func dbMigrations0009_key_not_primary_keySql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0009_key_not_primary_key.sql", size: 182, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
|
||||
info := bindataFileInfo{name: "db/migrations/0009_key_not_primary_key.sql", size: 182, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _dbMigrations0010_client_metadata_field_changedSql = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd2\xd5\x55\xd0\xce\xcd\x4c\x2f\x4a\x2c\x49\x55\x08\x2d\xe0\x0a\x0d\x70\x71\x0c\x71\x55\x48\xce\xc9\x4c\xcd\x2b\x89\xcf\x4c\x01\x92\x99\x25\x95\x5c\xc1\xae\x21\x0a\xb9\xa9\x25\x89\x29\x89\x25\x89\x0a\xb6\x0a\x25\xa9\x15\x25\x1a\x5c\x0a\x40\x90\x55\x9c\x9f\x17\x9f\x54\x9a\x99\x93\x12\x9f\x9f\x94\x95\x9a\x0c\x15\x06\x01\xf5\xa2\xd4\x94\xcc\x22\xa0\x50\x68\x90\x4f\xb1\xba\x0e\x58\xa9\x06\x98\x80\x99\xa4\xa9\x6b\x67\x87\xaa\x4a\x53\x07\x53\x7b\x7c\x69\x51\x26\xd1\xfa\xc1\xda\x81\xa4\x26\x57\xb8\x87\x6b\x90\xab\x02\x1e\x0d\x10\x73\x35\x15\x3c\x83\x15\xfc\x42\x7d\x7c\xac\xb9\x00\x01\x00\x00\xff\xff\xeb\xe6\x9a\x19\x0b\x01\x00\x00")
|
||||
|
||||
func dbMigrations0010_client_metadata_field_changedSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_dbMigrations0010_client_metadata_field_changedSql,
|
||||
"db/migrations/0010_client_metadata_field_changed.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func dbMigrations0010_client_metadata_field_changedSql() (*asset, error) {
|
||||
bytes, err := dbMigrations0010_client_metadata_field_changedSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "db/migrations/0010_client_metadata_field_changed.sql", size: 267, mode: os.FileMode(436), modTime: time.Unix(1, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -308,15 +329,16 @@ func AssetNames() []string {
|
|||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"db/migrations/0001_initial_migration.sql": dbMigrations0001_initial_migrationSql,
|
||||
"db/migrations/0002_dex_admin.sql": dbMigrations0002_dex_adminSql,
|
||||
"db/migrations/0003_user_created_at.sql": dbMigrations0003_user_created_atSql,
|
||||
"db/migrations/0004_session_nonce.sql": dbMigrations0004_session_nonceSql,
|
||||
"db/migrations/0005_refresh_token_create.sql": dbMigrations0005_refresh_token_createSql,
|
||||
"db/migrations/0006_user_email_unique.sql": dbMigrations0006_user_email_uniqueSql,
|
||||
"db/migrations/0007_session_scope.sql": dbMigrations0007_session_scopeSql,
|
||||
"db/migrations/0008_users_active_or_inactive.sql": dbMigrations0008_users_active_or_inactiveSql,
|
||||
"db/migrations/0009_key_not_primary_key.sql": dbMigrations0009_key_not_primary_keySql,
|
||||
"db/migrations/0001_initial_migration.sql": dbMigrations0001_initial_migrationSql,
|
||||
"db/migrations/0002_dex_admin.sql": dbMigrations0002_dex_adminSql,
|
||||
"db/migrations/0003_user_created_at.sql": dbMigrations0003_user_created_atSql,
|
||||
"db/migrations/0004_session_nonce.sql": dbMigrations0004_session_nonceSql,
|
||||
"db/migrations/0005_refresh_token_create.sql": dbMigrations0005_refresh_token_createSql,
|
||||
"db/migrations/0006_user_email_unique.sql": dbMigrations0006_user_email_uniqueSql,
|
||||
"db/migrations/0007_session_scope.sql": dbMigrations0007_session_scopeSql,
|
||||
"db/migrations/0008_users_active_or_inactive.sql": dbMigrations0008_users_active_or_inactiveSql,
|
||||
"db/migrations/0009_key_not_primary_key.sql": dbMigrations0009_key_not_primary_keySql,
|
||||
"db/migrations/0010_client_metadata_field_changed.sql": dbMigrations0010_client_metadata_field_changedSql,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
|
@ -362,15 +384,16 @@ type bintree struct {
|
|||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"db": &bintree{nil, map[string]*bintree{
|
||||
"migrations": &bintree{nil, map[string]*bintree{
|
||||
"0001_initial_migration.sql": &bintree{dbMigrations0001_initial_migrationSql, map[string]*bintree{}},
|
||||
"0002_dex_admin.sql": &bintree{dbMigrations0002_dex_adminSql, map[string]*bintree{}},
|
||||
"0003_user_created_at.sql": &bintree{dbMigrations0003_user_created_atSql, map[string]*bintree{}},
|
||||
"0004_session_nonce.sql": &bintree{dbMigrations0004_session_nonceSql, map[string]*bintree{}},
|
||||
"0005_refresh_token_create.sql": &bintree{dbMigrations0005_refresh_token_createSql, map[string]*bintree{}},
|
||||
"0006_user_email_unique.sql": &bintree{dbMigrations0006_user_email_uniqueSql, map[string]*bintree{}},
|
||||
"0007_session_scope.sql": &bintree{dbMigrations0007_session_scopeSql, map[string]*bintree{}},
|
||||
"0008_users_active_or_inactive.sql": &bintree{dbMigrations0008_users_active_or_inactiveSql, map[string]*bintree{}},
|
||||
"0009_key_not_primary_key.sql": &bintree{dbMigrations0009_key_not_primary_keySql, map[string]*bintree{}},
|
||||
"0001_initial_migration.sql": &bintree{dbMigrations0001_initial_migrationSql, map[string]*bintree{}},
|
||||
"0002_dex_admin.sql": &bintree{dbMigrations0002_dex_adminSql, map[string]*bintree{}},
|
||||
"0003_user_created_at.sql": &bintree{dbMigrations0003_user_created_atSql, map[string]*bintree{}},
|
||||
"0004_session_nonce.sql": &bintree{dbMigrations0004_session_nonceSql, map[string]*bintree{}},
|
||||
"0005_refresh_token_create.sql": &bintree{dbMigrations0005_refresh_token_createSql, map[string]*bintree{}},
|
||||
"0006_user_email_unique.sql": &bintree{dbMigrations0006_user_email_uniqueSql, map[string]*bintree{}},
|
||||
"0007_session_scope.sql": &bintree{dbMigrations0007_session_scopeSql, map[string]*bintree{}},
|
||||
"0008_users_active_or_inactive.sql": &bintree{dbMigrations0008_users_active_or_inactiveSql, map[string]*bintree{}},
|
||||
"0009_key_not_primary_key.sql": &bintree{dbMigrations0009_key_not_primary_keySql, map[string]*bintree{}},
|
||||
"0010_client_metadata_field_changed.sql": &bintree{dbMigrations0010_client_metadata_field_changedSql, map[string]*bintree{}},
|
||||
}},
|
||||
}},
|
||||
}}
|
||||
|
|
|
@ -193,7 +193,7 @@ func TestDBClientIdentityRepoMetadata(t *testing.T) {
|
|||
r := db.NewClientIdentityRepo(connect(t))
|
||||
|
||||
cm := oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"},
|
||||
url.URL{Scheme: "https", Host: "example.com", Path: "/callback"},
|
||||
},
|
||||
|
@ -230,7 +230,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) {
|
|||
r := db.NewClientIdentityRepo(connect(t))
|
||||
|
||||
meta1 := oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "foo.example.com"},
|
||||
},
|
||||
}
|
||||
|
@ -240,7 +240,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) {
|
|||
}
|
||||
|
||||
meta2 := oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "bar.example.com"},
|
||||
},
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ func TestDBClientIdentityRepoAuthenticate(t *testing.T) {
|
|||
r := db.NewClientIdentityRepo(connect(t))
|
||||
|
||||
cm := oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"},
|
||||
},
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ func TestDBClientIdentityAll(t *testing.T) {
|
|||
r := db.NewClientIdentityRepo(connect(t))
|
||||
|
||||
cm := oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"},
|
||||
},
|
||||
}
|
||||
|
@ -326,7 +326,7 @@ func TestDBClientIdentityAll(t *testing.T) {
|
|||
}
|
||||
|
||||
cm = oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "foo.com", Path: "/cb"},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ var (
|
|||
Secret: "secret-1",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{
|
||||
Scheme: "https",
|
||||
Host: "client1.example.com/callback",
|
||||
|
@ -36,7 +36,7 @@ var (
|
|||
Secret: "secret-2",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{
|
||||
Scheme: "https",
|
||||
Host: "client2.example.com/callback",
|
||||
|
|
|
@ -72,8 +72,8 @@ func TestClientCreate(t *testing.T) {
|
|||
t.Error("Expected new client to exist in repo")
|
||||
}
|
||||
|
||||
gotURLs := make([]string, len(meta.RedirectURLs))
|
||||
for i, u := range meta.RedirectURLs {
|
||||
gotURLs := make([]string, len(meta.RedirectURIs))
|
||||
for i, u := range meta.RedirectURIs {
|
||||
gotURLs[i] = u.String()
|
||||
}
|
||||
if !reflect.DeepEqual(newClientInput.RedirectURIs, gotURLs) {
|
||||
|
|
|
@ -104,7 +104,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures {
|
|||
Secret: testClientSecret,
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
testRedirectURL,
|
||||
},
|
||||
},
|
||||
|
@ -115,7 +115,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures {
|
|||
Secret: "secret",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
testRedirectURL,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) {
|
|||
ID: sc.Id,
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: make([]url.URL, len(sc.RedirectURIs)),
|
||||
RedirectURIs: make([]url.URL, len(sc.RedirectURIs)),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) {
|
|||
return oidc.ClientIdentity{}, errors.New("redirect URL invalid")
|
||||
}
|
||||
|
||||
ci.Metadata.RedirectURLs[i] = *u
|
||||
ci.Metadata.RedirectURIs[i] = *u
|
||||
}
|
||||
|
||||
return ci, nil
|
||||
|
@ -36,9 +36,9 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) {
|
|||
func MapClientIdentityToSchemaClient(c oidc.ClientIdentity) Client {
|
||||
cl := Client{
|
||||
Id: c.Credentials.ID,
|
||||
RedirectURIs: make([]string, len(c.Metadata.RedirectURLs)),
|
||||
RedirectURIs: make([]string, len(c.Metadata.RedirectURIs)),
|
||||
}
|
||||
for i, u := range c.Metadata.RedirectURLs {
|
||||
for i, u := range c.Metadata.RedirectURIs {
|
||||
cl.RedirectURIs[i] = u.String()
|
||||
}
|
||||
return cl
|
||||
|
@ -48,9 +48,9 @@ func MapClientIdentityToSchemaClientWithSecret(c oidc.ClientIdentity) ClientWith
|
|||
cl := ClientWithSecret{
|
||||
Id: c.Credentials.ID,
|
||||
Secret: c.Credentials.Secret,
|
||||
RedirectURIs: make([]string, len(c.Metadata.RedirectURLs)),
|
||||
RedirectURIs: make([]string, len(c.Metadata.RedirectURIs)),
|
||||
}
|
||||
for i, u := range c.Metadata.RedirectURLs {
|
||||
for i, u := range c.Metadata.RedirectURIs {
|
||||
cl.RedirectURIs[i] = u.String()
|
||||
}
|
||||
return cl
|
||||
|
|
57
server/client_registration.go
Normal file
57
server/client_registration.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/dex/pkg/log"
|
||||
"github.com/coreos/go-oidc/oauth2"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
invalidRedirectURI = "invalid_redirect_uri"
|
||||
invalidClientMetadata = "invalid_client_metadata"
|
||||
)
|
||||
|
||||
func (s *Server) handleClientRegistration(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := s.handleClientRegistrationRequest(r)
|
||||
if err != nil {
|
||||
code := http.StatusBadRequest
|
||||
if err.Type == oauth2.ErrorServerError {
|
||||
code = http.StatusInternalServerError
|
||||
}
|
||||
writeResponseWithBody(w, code, err)
|
||||
} else {
|
||||
writeResponseWithBody(w, http.StatusCreated, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleClientRegistrationRequest(r *http.Request) (*oidc.ClientRegistrationResponse, *apiError) {
|
||||
var clientMetadata oidc.ClientMetadata
|
||||
if err := json.NewDecoder(r.Body).Decode(&clientMetadata); err != nil {
|
||||
return nil, newAPIError(oauth2.ErrorInvalidRequest, err.Error())
|
||||
}
|
||||
if err := s.ProviderConfig().Supports(clientMetadata); err != nil {
|
||||
return nil, newAPIError(invalidClientMetadata, err.Error())
|
||||
}
|
||||
|
||||
// metadata is guarenteed to have at least one redirect_uri by earlier validation.
|
||||
id, err := oidc.GenClientID(clientMetadata.RedirectURIs[0].Host)
|
||||
if err != nil {
|
||||
log.Errorf("Faild to create client ID: %v", err)
|
||||
return nil, newAPIError(oauth2.ErrorServerError, "unable to save client metadata")
|
||||
}
|
||||
|
||||
creds, err := s.ClientIdentityRepo.New(id, clientMetadata)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create new client identity: %v", err)
|
||||
return nil, newAPIError(oauth2.ErrorServerError, "unable to save client metadata")
|
||||
}
|
||||
|
||||
return &oidc.ClientRegistrationResponse{
|
||||
ClientID: creds.ID,
|
||||
ClientSecret: creds.Secret,
|
||||
ClientMetadata: clientMetadata,
|
||||
}, nil
|
||||
}
|
161
server/client_registration_test.go
Normal file
161
server/client_registration_test.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/go-oidc/oauth2"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
func TestClientRegistration(t *testing.T) {
|
||||
tests := []struct {
|
||||
body string
|
||||
code int
|
||||
}{
|
||||
{"", http.StatusBadRequest},
|
||||
{
|
||||
`{
|
||||
"redirect_uris": [
|
||||
"https://client.example.org/callback",
|
||||
"https://client.example.org/callback2"
|
||||
]
|
||||
}`,
|
||||
http.StatusCreated,
|
||||
},
|
||||
{
|
||||
// Requesting unsupported client metadata fields (user_info_encrypted).
|
||||
`{
|
||||
"application_type": "web",
|
||||
"redirect_uris":[
|
||||
"https://client.example.org/callback",
|
||||
"https://client.example.org/callback2"
|
||||
],
|
||||
"client_name": "My Example",
|
||||
"logo_uri": "https://client.example.org/logo.png",
|
||||
"subject_type": "pairwise",
|
||||
"sector_identifier_uri": "https://other.example.net/file_of_redirect_uris.json",
|
||||
"token_endpoint_auth_method": "client_secret_basic",
|
||||
"jwks_uri": "https://client.example.org/my_public_keys.jwks",
|
||||
"userinfo_encrypted_response_alg": "RSA1_5",
|
||||
"userinfo_encrypted_response_enc": "A128CBC-HS256",
|
||||
"contacts": ["ve7jtb@example.org", "mary@example.org"],
|
||||
"request_uris": [
|
||||
"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA" ]
|
||||
}`,
|
||||
http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
`{
|
||||
"application_type": "web",
|
||||
"redirect_uris":[
|
||||
"https://client.example.org/callback",
|
||||
"https://client.example.org/callback2"
|
||||
],
|
||||
"client_name": "My Example",
|
||||
"logo_uri": "https://client.example.org/logo.png",
|
||||
"subject_type": "pairwise",
|
||||
"sector_identifier_uri": "https://other.example.net/file_of_redirect_uris.json",
|
||||
"token_endpoint_auth_method": "client_secret_basic",
|
||||
"jwks_uri": "https://client.example.org/my_public_keys.jwks",
|
||||
"contacts": ["ve7jtb@example.org", "mary@example.org"],
|
||||
"request_uris": [
|
||||
"https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA" ]
|
||||
}`,
|
||||
http.StatusCreated,
|
||||
},
|
||||
}
|
||||
|
||||
var handler http.Handler
|
||||
f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
testServer := httptest.NewServer(f)
|
||||
|
||||
issuerURL, err := url.Parse(testServer.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testServer.Close()
|
||||
|
||||
for i, tt := range tests {
|
||||
fixtures, err := makeTestFixtures()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixtures.srv.IssuerURL = *issuerURL
|
||||
fixtures.srv.EnableClientRegistration = true
|
||||
|
||||
handler = fixtures.srv.HTTPHandler()
|
||||
|
||||
err = func() error {
|
||||
// GET provider config through discovery URL.
|
||||
resp, err := http.Get(testServer.URL + "/.well-known/openid-configuration")
|
||||
if err != nil {
|
||||
return fmt.Errorf("GET config: %v", err)
|
||||
}
|
||||
var cfg oidc.ProviderConfig
|
||||
err = json.NewDecoder(resp.Body).Decode(&cfg)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode resp: %v", err)
|
||||
}
|
||||
|
||||
if cfg.RegistrationEndpoint == nil {
|
||||
return errors.New("registration endpoint not available")
|
||||
}
|
||||
|
||||
// POST registration request to registration endpoint.
|
||||
body := strings.NewReader(tt.body)
|
||||
resp, err = http.Post(cfg.RegistrationEndpoint.String(), "application/json", body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("POSTing client metadata: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != tt.code {
|
||||
return fmt.Errorf("expected status code=%d, got=%d", tt.code, resp.StatusCode)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
var oauthErr oauth2.Error
|
||||
if err := json.NewDecoder(resp.Body).Decode(&oauthErr); err != nil {
|
||||
return fmt.Errorf("failed to decode oauth2 error: %v", err)
|
||||
}
|
||||
if oauthErr.Type == "" {
|
||||
return fmt.Errorf("got oauth2 error with no 'error' field")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read registration response.
|
||||
var r oidc.ClientRegistrationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
return fmt.Errorf("decode response: %v", err)
|
||||
}
|
||||
if r.ClientID == "" {
|
||||
return fmt.Errorf("no client id in registration response")
|
||||
}
|
||||
|
||||
metadata, err := fixtures.clientIdentityRepo.Metadata(r.ClientID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup client id after creation")
|
||||
}
|
||||
|
||||
if diff := pretty.Compare(&metadata, &r.ClientMetadata); diff != "" {
|
||||
return fmt.Errorf("metadata in response did not match metadata in db: %s", diff)
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
t.Errorf("case %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -89,7 +89,7 @@ func (c *clientResource) create(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
clientID, err := oidc.GenClientID(ci.Metadata.RedirectURLs[0].Host)
|
||||
clientID, err := oidc.GenClientID(ci.Metadata.RedirectURIs[0].Host)
|
||||
if err != nil {
|
||||
log.Errorf("Failed generating ID for new client: %v", err)
|
||||
writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "unable to generate client ID"))
|
||||
|
|
|
@ -89,13 +89,13 @@ func TestCreateInvalidRequest(t *testing.T) {
|
|||
{
|
||||
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["asdf.com"]}`)},
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantBody: `{"error":"invalid_client_metadata","error_description":"invalid redirect URL: scheme not http/https"}`,
|
||||
wantBody: `{"error":"invalid_client_metadata","error_description":"no host for uri field redirect_uris"}`,
|
||||
},
|
||||
// uri missing host
|
||||
{
|
||||
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["http://"]}`)},
|
||||
wantCode: http.StatusBadRequest,
|
||||
wantBody: `{"error":"invalid_client_metadata","error_description":"invalid redirect URL: host empty"}`,
|
||||
wantBody: `{"error":"invalid_client_metadata","error_description":"no host for uri field redirect_uris"}`,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ func TestList(t *testing.T) {
|
|||
oidc.ClientIdentity{
|
||||
Credentials: oidc.ClientCredentials{ID: "foo", Secret: "bar"},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "example.com"},
|
||||
},
|
||||
},
|
||||
|
@ -202,7 +202,7 @@ func TestList(t *testing.T) {
|
|||
oidc.ClientIdentity{
|
||||
Credentials: oidc.ClientCredentials{ID: "foo", Secret: "bar"},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "example.com"},
|
||||
},
|
||||
},
|
||||
|
@ -210,7 +210,7 @@ func TestList(t *testing.T) {
|
|||
oidc.ClientIdentity{
|
||||
Credentials: oidc.ClientCredentials{ID: "biz", Secret: "bang"},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "https", Host: "example.com", Path: "one/two/three"},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,15 +26,16 @@ import (
|
|||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
IssuerURL string
|
||||
IssuerName string
|
||||
IssuerLogoURL string
|
||||
TemplateDir string
|
||||
EmailTemplateDirs []string
|
||||
EmailFromAddress string
|
||||
EmailerConfigFile string
|
||||
StateConfig StateConfigurer
|
||||
EnableRegistration bool
|
||||
IssuerURL string
|
||||
IssuerName string
|
||||
IssuerLogoURL string
|
||||
TemplateDir string
|
||||
EmailTemplateDirs []string
|
||||
EmailFromAddress string
|
||||
EmailerConfigFile string
|
||||
StateConfig StateConfigurer
|
||||
EnableRegistration bool
|
||||
EnableClientRegistration bool
|
||||
}
|
||||
|
||||
type StateConfigurer interface {
|
||||
|
@ -73,7 +74,8 @@ func (cfg *ServerConfig) Server() (*Server, error) {
|
|||
HealthChecks: []health.Checkable{km},
|
||||
Connectors: []connector.Connector{},
|
||||
|
||||
EnableRegistration: cfg.EnableRegistration,
|
||||
EnableRegistration: cfg.EnableRegistration,
|
||||
EnableClientRegistration: cfg.EnableClientRegistration,
|
||||
}
|
||||
|
||||
err = cfg.StateConfig.Configure(&srv)
|
||||
|
|
|
@ -158,7 +158,7 @@ func handleVerifyEmailResendFunc(
|
|||
return
|
||||
}
|
||||
|
||||
*redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURLs)
|
||||
*redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURIs)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case (client.ErrorInvalidRedirectURL):
|
||||
|
|
|
@ -29,19 +29,20 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
httpPathDiscovery = "/.well-known/openid-configuration"
|
||||
httpPathToken = "/token"
|
||||
httpPathKeys = "/keys"
|
||||
httpPathAuth = "/auth"
|
||||
httpPathHealth = "/health"
|
||||
httpPathAPI = "/api"
|
||||
httpPathRegister = "/register"
|
||||
httpPathEmailVerify = "/verify-email"
|
||||
httpPathVerifyEmailResend = "/resend-verify-email"
|
||||
httpPathSendResetPassword = "/send-reset-password"
|
||||
httpPathResetPassword = "/reset-password"
|
||||
httpPathAcceptInvitation = "/accept-invitation"
|
||||
httpPathDebugVars = "/debug/vars"
|
||||
httpPathDiscovery = "/.well-known/openid-configuration"
|
||||
httpPathToken = "/token"
|
||||
httpPathKeys = "/keys"
|
||||
httpPathAuth = "/auth"
|
||||
httpPathHealth = "/health"
|
||||
httpPathAPI = "/api"
|
||||
httpPathRegister = "/register"
|
||||
httpPathEmailVerify = "/verify-email"
|
||||
httpPathVerifyEmailResend = "/resend-verify-email"
|
||||
httpPathSendResetPassword = "/send-reset-password"
|
||||
httpPathResetPassword = "/reset-password"
|
||||
httpPathAcceptInvitation = "/accept-invitation"
|
||||
httpPathDebugVars = "/debug/vars"
|
||||
httpPathClientRegistration = "/registration"
|
||||
|
||||
cookieLastSeen = "LastSeen"
|
||||
cookieShowEmailVerifiedMessage = "ShowEmailVerifiedMessage"
|
||||
|
@ -55,7 +56,7 @@ func handleDiscoveryFunc(cfg oidc.ProviderConfig) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(cfg)
|
||||
b, err := json.Marshal(&cfg)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to marshal %#v to JSON: %v", cfg, err)
|
||||
}
|
||||
|
@ -309,13 +310,13 @@ func handleAuthFunc(srv OIDCServer, idpcs []connector.Connector, tpl *template.T
|
|||
return
|
||||
}
|
||||
|
||||
if len(cm.RedirectURLs) == 0 {
|
||||
if len(cm.RedirectURIs) == 0 {
|
||||
log.Errorf("Client %q has no redirect URLs", acr.ClientID)
|
||||
writeAuthError(w, oauth2.NewError(oauth2.ErrorServerError), acr.State)
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL, err := client.ValidRedirectURL(acr.RedirectURL, cm.RedirectURLs)
|
||||
redirectURL, err := client.ValidRedirectURL(acr.RedirectURL, cm.RedirectURIs)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case (client.ErrorCantChooseRedirectURL):
|
||||
|
|
|
@ -83,7 +83,7 @@ func TestHandleAuthFuncResponsesSingleRedirectURL(t *testing.T) {
|
|||
Secret: "secrete",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "client.example.com", Path: "/callback"},
|
||||
},
|
||||
},
|
||||
|
@ -206,7 +206,7 @@ func TestHandleAuthFuncResponsesMultipleRedirectURLs(t *testing.T) {
|
|||
Secret: "secrete",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{Scheme: "http", Host: "foo.example.com", Path: "/callback"},
|
||||
url.URL{Scheme: "http", Host: "bar.example.com", Path: "/callback"},
|
||||
},
|
||||
|
@ -363,17 +363,22 @@ func TestHandleDiscoveryFuncMethodNotAllowed(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHandleDiscoveryFunc(t *testing.T) {
|
||||
u := "http://server.example.com"
|
||||
u := url.URL{Scheme: "http", Host: "server.example.com"}
|
||||
pathURL := func(path string) *url.URL {
|
||||
ucopy := u
|
||||
ucopy.Path = path
|
||||
return &ucopy
|
||||
}
|
||||
cfg := oidc.ProviderConfig{
|
||||
Issuer: u,
|
||||
AuthEndpoint: u + httpPathAuth,
|
||||
TokenEndpoint: u + httpPathToken,
|
||||
KeysEndpoint: u + httpPathKeys,
|
||||
Issuer: &u,
|
||||
AuthEndpoint: pathURL(httpPathAuth),
|
||||
TokenEndpoint: pathURL(httpPathToken),
|
||||
KeysEndpoint: pathURL(httpPathKeys),
|
||||
|
||||
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode},
|
||||
ResponseTypesSupported: []string{"code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IDTokenAlgValuesSupported: []string{"RS256"},
|
||||
IDTokenSigningAlgValues: []string{"RS256"},
|
||||
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ func (h *SendResetPasswordEmailHandler) validateRedirectURL(clientID string, red
|
|||
return url.URL{}, false
|
||||
}
|
||||
|
||||
validURL, err := client.ValidRedirectURL(parsed, cm.RedirectURLs)
|
||||
validURL, err := client.ValidRedirectURL(parsed, cm.RedirectURIs)
|
||||
if err != nil {
|
||||
log.Errorf("Invalid redirectURL for clientID: redirectURL:%q, clientID:%q", redirectURL, clientID)
|
||||
return url.URL{}, false
|
||||
|
|
|
@ -74,6 +74,7 @@ type Server struct {
|
|||
RefreshTokenRepo refresh.RefreshTokenRepo
|
||||
UserEmailer *useremail.UserEmailer
|
||||
EnableRegistration bool
|
||||
EnableClientRegistration bool
|
||||
|
||||
localConnectorID string
|
||||
}
|
||||
|
@ -111,21 +112,27 @@ func (s *Server) KillSession(sessionKey string) error {
|
|||
}
|
||||
|
||||
func (s *Server) ProviderConfig() oidc.ProviderConfig {
|
||||
iss := s.IssuerURL.String()
|
||||
authEndpoint := s.absURL(httpPathAuth)
|
||||
tokenEndpoint := s.absURL(httpPathToken)
|
||||
keysEndpoint := s.absURL(httpPathKeys)
|
||||
cfg := oidc.ProviderConfig{
|
||||
Issuer: iss,
|
||||
|
||||
AuthEndpoint: iss + httpPathAuth,
|
||||
TokenEndpoint: iss + httpPathToken,
|
||||
KeysEndpoint: iss + httpPathKeys,
|
||||
Issuer: &s.IssuerURL,
|
||||
AuthEndpoint: &authEndpoint,
|
||||
TokenEndpoint: &tokenEndpoint,
|
||||
KeysEndpoint: &keysEndpoint,
|
||||
|
||||
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeClientCreds},
|
||||
ResponseTypesSupported: []string{"code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IDTokenAlgValuesSupported: []string{"RS256"},
|
||||
IDTokenSigningAlgValues: []string{"RS256"},
|
||||
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
|
||||
}
|
||||
|
||||
if s.EnableClientRegistration {
|
||||
regEndpoint := s.absURL(httpPathClientRegistration)
|
||||
cfg.RegistrationEndpoint = ®Endpoint
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
@ -241,6 +248,10 @@ func (s *Server) HTTPHandler() http.Handler {
|
|||
redirectValidityWindow: s.SessionManager.ValidityWindow,
|
||||
})
|
||||
|
||||
if s.EnableClientRegistration {
|
||||
mux.HandleFunc(httpPathClientRegistration, s.handleClientRegistration)
|
||||
}
|
||||
|
||||
mux.HandleFunc(httpPathDebugVars, health.ExpvarHandler)
|
||||
|
||||
pcfg := s.ProviderConfig()
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/coreos/go-oidc/key"
|
||||
"github.com/coreos/go-oidc/oauth2"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
type StaticKeyManager struct {
|
||||
|
@ -100,20 +101,21 @@ func TestServerProviderConfig(t *testing.T) {
|
|||
srv := &Server{IssuerURL: url.URL{Scheme: "http", Host: "server.example.com"}}
|
||||
|
||||
want := oidc.ProviderConfig{
|
||||
Issuer: "http://server.example.com",
|
||||
AuthEndpoint: "http://server.example.com/auth",
|
||||
TokenEndpoint: "http://server.example.com/token",
|
||||
KeysEndpoint: "http://server.example.com/keys",
|
||||
Issuer: &url.URL{Scheme: "http", Host: "server.example.com"},
|
||||
AuthEndpoint: &url.URL{Scheme: "http", Host: "server.example.com", Path: "/auth"},
|
||||
TokenEndpoint: &url.URL{Scheme: "http", Host: "server.example.com", Path: "/token"},
|
||||
KeysEndpoint: &url.URL{Scheme: "http", Host: "server.example.com", Path: "/keys"},
|
||||
|
||||
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeClientCreds},
|
||||
ResponseTypesSupported: []string{"code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IDTokenAlgValuesSupported: []string{"RS256"},
|
||||
IDTokenSigningAlgValues: []string{"RS256"},
|
||||
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
|
||||
}
|
||||
got := srv.ProviderConfig()
|
||||
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("want=%#v, got=%#v", want, got)
|
||||
if diff := pretty.Compare(want, got); diff != "" {
|
||||
t.Fatalf("provider config did not match expected: %s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +133,7 @@ func TestServerNewSession(t *testing.T) {
|
|||
Secret: "secrete",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{
|
||||
Scheme: "http",
|
||||
Host: "client.example.com",
|
||||
|
@ -141,7 +143,7 @@ func TestServerNewSession(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
key, err := srv.NewSession("bogus_idpc", ci.Credentials.ID, state, ci.Metadata.RedirectURLs[0], nonce, false, []string{"openid"})
|
||||
key, err := srv.NewSession("bogus_idpc", ci.Credentials.ID, state, ci.Metadata.RedirectURIs[0], nonce, false, []string{"openid"})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -156,8 +158,8 @@ func TestServerNewSession(t *testing.T) {
|
|||
t.Fatalf("Unable to add Identity to Session: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ci.Metadata.RedirectURLs[0], ses.RedirectURL) {
|
||||
t.Fatalf("Session created with incorrect RedirectURL: want=%#v got=%#v", ci.Metadata.RedirectURLs[0], ses.RedirectURL)
|
||||
if !reflect.DeepEqual(ci.Metadata.RedirectURIs[0], ses.RedirectURL) {
|
||||
t.Fatalf("Session created with incorrect RedirectURL: want=%#v got=%#v", ci.Metadata.RedirectURIs[0], ses.RedirectURL)
|
||||
}
|
||||
|
||||
if ci.Credentials.ID != ses.ClientID {
|
||||
|
@ -180,7 +182,7 @@ func TestServerLogin(t *testing.T) {
|
|||
Secret: "secrete",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{
|
||||
Scheme: "http",
|
||||
Host: "client.example.com",
|
||||
|
@ -197,7 +199,7 @@ func TestServerLogin(t *testing.T) {
|
|||
|
||||
sm := session.NewSessionManager(session.NewSessionRepo(), session.NewSessionKeyRepo())
|
||||
sm.GenerateCode = staticGenerateCodeFunc("fakecode")
|
||||
sessionID, err := sm.NewSession("test_connector_id", ci.Credentials.ID, "bogus", ci.Metadata.RedirectURLs[0], "", false, []string{"openid"})
|
||||
sessionID, err := sm.NewSession("test_connector_id", ci.Credentials.ID, "bogus", ci.Metadata.RedirectURIs[0], "", false, []string{"openid"})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -269,7 +271,7 @@ func TestServerLoginDisabledUser(t *testing.T) {
|
|||
Secret: "secrete",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
url.URL{
|
||||
Scheme: "http",
|
||||
Host: "client.example.com",
|
||||
|
@ -286,7 +288,7 @@ func TestServerLoginDisabledUser(t *testing.T) {
|
|||
|
||||
sm := session.NewSessionManager(session.NewSessionRepo(), session.NewSessionKeyRepo())
|
||||
sm.GenerateCode = staticGenerateCodeFunc("fakecode")
|
||||
sessionID, err := sm.NewSession("test_connector_id", ci.Credentials.ID, "bogus", ci.Metadata.RedirectURLs[0], "", false, []string{"openid"})
|
||||
sessionID, err := sm.NewSession("test_connector_id", ci.Credentials.ID, "bogus", ci.Metadata.RedirectURIs[0], "", false, []string{"openid"})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ func makeTestFixtures() (*testFixtures, error) {
|
|||
Secret: testClientSecret,
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
testRedirectURL,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -154,7 +154,7 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
|
|||
return schema.UserCreateResponse{}, mapError(err)
|
||||
}
|
||||
|
||||
validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURLs)
|
||||
validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURIs)
|
||||
if err != nil {
|
||||
return schema.UserCreateResponse{}, ErrorInvalidRedirectURL
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
|
|||
Secret: "secrete",
|
||||
},
|
||||
Metadata: oidc.ClientMetadata{
|
||||
RedirectURLs: []url.URL{
|
||||
RedirectURIs: []url.URL{
|
||||
validRedirURL,
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue