Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
D
dex
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
dex
Commits
af790e46
Commit
af790e46
authored
Feb 02, 2016
by
Eric Chiang
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #267 from ericchiang/metadata
add dynamic client registration
parents
0ceecbaa
04cd1851
Show whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
1465 additions
and
160 deletions
+1465
-160
getting-started.md
Documentation/getting-started.md
+1
-1
Godeps.json
Godeps/Godeps.json
+5
-5
jose.go
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jose.go
+51
-0
jwk.go
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jwk.go
+4
-0
oauth2.go
..._workspace/src/github.com/coreos/go-oidc/oauth2/oauth2.go
+35
-0
client.go
...s/_workspace/src/github.com/coreos/go-oidc/oidc/client.go
+507
-10
provider.go
..._workspace/src/github.com/coreos/go-oidc/oidc/provider.go
+418
-11
README.md
README.md
+1
-1
client.go
client/client.go
+2
-2
client_test.go
client/client_test.go
+7
-7
main.go
cmd/dex-worker/main.go
+2
-0
command_client.go
cmd/dexctl/command_client.go
+1
-1
driver_api.go
cmd/dexctl/driver_api.go
+4
-4
driver_db.go
cmd/dexctl/driver_db.go
+1
-1
connector_oidc_test.go
connector/connector_oidc_test.go
+2
-2
client.go
db/client.go
+2
-43
migrate_test.go
db/migrate_test.go
+80
-1
0010_client_metadata_field_changed.sql
db/migrations/0010_client_metadata_field_changed.sql
+9
-0
assets.go
db/migrations/assets.go
+32
-9
db_test.go
functional/db_test.go
+6
-6
client_repo_test.go
functional/repo/client_repo_test.go
+2
-2
client_api_test.go
integration/client_api_test.go
+2
-2
user_api_test.go
integration/user_api_test.go
+2
-2
mapper.go
schema/workerschema/mapper.go
+6
-6
client_registration.go
server/client_registration.go
+57
-0
client_registration_test.go
server/client_registration_test.go
+161
-0
client_resource.go
server/client_resource.go
+1
-1
client_resource_test.go
server/client_resource_test.go
+5
-5
config.go
server/config.go
+2
-0
email_verification.go
server/email_verification.go
+1
-1
http.go
server/http.go
+4
-3
http_test.go
server/http_test.go
+13
-8
password.go
server/password.go
+1
-1
server.go
server/server.go
+18
-7
server_test.go
server/server_test.go
+17
-15
testutil.go
server/testutil.go
+1
-1
api.go
user/api/api.go
+1
-1
api_test.go
user/api/api_test.go
+1
-1
No files found.
Documentation/getting-started.md
View file @
af790e46
...
...
@@ -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:
...
...
Godeps/Godeps.json
View file @
af790e46
...
...
@@ -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"
,
...
...
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jose.go
View file @
af790e46
...
...
@@ -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
{
...
...
Godeps/_workspace/src/github.com/coreos/go-oidc/jose/jwk.go
View file @
af790e46
...
...
@@ -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
{
...
...
Godeps/_workspace/src/github.com/coreos/go-oidc/oauth2/oauth2.go
View file @
af790e46
...
...
@@ -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
(
// 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
(
...
...
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/client.go
View file @
af790e46
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
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
{
RedirectURLs
[]
url
.
URL
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
.
RedirectUR
L
s
)
==
0
{
if
len
(
m
.
RedirectUR
I
s
)
==
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
)
...
...
Godeps/_workspace/src/github.com/coreos/go-oidc/oidc/provider.go
View file @
af790e46
...
...
@@ -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
*
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"`
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:"-"`
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
}
...
...
README.md
View file @
af790e46
...
...
@@ -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.
...
...
client/client.go
View file @
af790e46
...
...
@@ -172,7 +172,7 @@ func (ci *clientIdentity) UnmarshalJSON(data []byte) error {
Secret
:
c
.
Secret
,
}
ci
.
Metadata
=
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
make
([]
url
.
URL
,
len
(
c
.
RedirectURLs
)),
RedirectUR
I
s
:
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
.
RedirectUR
L
s
[
i
]
=
*
up
ci
.
Metadata
.
RedirectUR
I
s
[
i
]
=
*
up
}
return
nil
...
...
client/client_test.go
View file @
af790e46
...
...
@@ -18,7 +18,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
{
id
:
"foo"
,
meta
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
url
.
URL
{
Scheme
:
"https"
,
Host
:
"example.com"
,
...
...
@@ -29,7 +29,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
{
id
:
"bar"
,
meta
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
.
RedirectUR
L
s
gotURLs
:=
all
[
0
]
.
Metadata
.
RedirectUR
L
s
wantURLs
:=
tt
.
meta
.
RedirectUR
I
s
gotURLs
:=
all
[
0
]
.
Metadata
.
RedirectUR
I
s
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
url
.
URL
{
Scheme
:
"https"
,
Host
:
"foo.example.com"
},
},
}
...
...
@@ -82,7 +82,7 @@ func TestMemClientIdentityRepoNewDuplicate(t *testing.T) {
}
meta2
:=
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
.
RedirectUR
L
s
{
for
_
,
u
:=
range
actual
.
Metadata
.
RedirectUR
I
s
{
actualURLs
=
append
(
actualURLs
,
u
.
String
())
}
sort
.
Strings
(
actualURLs
)
...
...
cmd/dex-worker/main.go
View file @
af790e46
...
...
@@ -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"
)
...
...
@@ -125,6 +126,7 @@ func main() {
IssuerName
:
*
issuerName
,
IssuerLogoURL
:
*
issuerLogoURL
,
EnableRegistration
:
*
enableRegistration
,
EnableClientRegistration
:
*
enableClientRegistration
,
}
if
*
noDB
{
...
...
cmd/dexctl/command_client.go
View file @
af790e46
...
...
@@ -37,7 +37,7 @@ func runNewClient(cmd *cobra.Command, args []string) int {
redirectURLs
[
i
]
=
*
u
}
cc
,
err
:=
getDriver
()
.
NewClient
(
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
redirectURLs
})
cc
,
err
:=
getDriver
()
.
NewClient
(
oidc
.
ClientMetadata
{
RedirectUR
I
s
:
redirectURLs
})
if
err
!=
nil
{
stderr
(
"Failed creating new client: %v"
,
err
)
return
1
...
...
cmd/dexctl/driver_api.go
View file @
af790e46
...
...
@@ -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
.
RedirectUR
L
s
)),
RedirectURIs
:
make
([]
string
,
len
(
meta
.
RedirectUR
I
s
)),
}
for
i
,
u
:=
range
meta
.
RedirectUR
L
s
{
for
i
,
u
:=
range
meta
.
RedirectUR
I
s
{
sc
.
RedirectURIs
[
i
]
=
u
.
String
()
}
...
...
cmd/dexctl/driver_db.go
View file @
af790e46
...
...
@@ -31,7 +31,7 @@ func (d *dbDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials,
return
nil
,
err
}
clientID
,
err
:=
oidc
.
GenClientID
(
meta
.
RedirectUR
L
s
[
0
]
.
Host
)
clientID
,
err
:=
oidc
.
GenClientID
(
meta
.
RedirectUR
I
s
[
0
]
.
Host
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
connector/connector_oidc_test.go
View file @
af790e46
...
...
@@ -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
,
}
...
...
db/client.go
View file @
af790e46
...
...
@@ -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
{
return
nil
,
err
}
cm
,
err
:=
cmj
.
ClientMetadata
()
if
err
!=
nil
{
if
err
:=
json
.
Unmarshal
([]
byte
(
m
.
Metadata
),
&
ci
.
Metadata
);
err
!=
nil
{
return
nil
,
err
}
ci
.
Metadata
=
*
cm
return
&
ci
,
nil
}
...
...
db/migrate_test.go
View file @
af790e46
...
...
@@ -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
)
}
}
}
db/migrations/0010_client_metadata_field_changed.sql
0 → 100644
View file @
af790e46
-- +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
;
db/migrations/assets.go
View file @
af790e46
...
...
@@ -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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0001_initial_migration.sql"
,
size
:
1388
,
mode
:
os
.
FileMode
(
4
36
),
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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0002_dex_admin.sql"
,
size
:
126
,
mode
:
os
.
FileMode
(
4
36
),
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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0003_user_created_at.sql"
,
size
:
111
,
mode
:
os
.
FileMode
(
4
36
),
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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0004_session_nonce.sql"
,
size
:
60
,
mode
:
os
.
FileMode
(
4
36
),
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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0005_refresh_token_create.sql"
,
size
:
506
,
mode
:
os
.
FileMode
(
4
36
),
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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0006_user_email_unique.sql"
,
size
:
99
,
mode
:
os
.
FileMode
(
4
36
),
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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0007_session_scope.sql"
,
size
:
60
,
mode
:
os
.
FileMode
(
4
36
),
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
(
4
20
),
modTime
:
time
.
Unix
(
1
,
0
)}
info
:=
bindataFileInfo
{
name
:
"db/migrations/0008_users_active_or_inactive.sql"
,
size
:
110
,
mode
:
os
.
FileMode
(
4
36
),
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
}
...
...
@@ -317,6 +338,7 @@ var _bindata = map[string]func() (*asset, error){
"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
...
...
@@ -371,6 +393,7 @@ var _bintree = &bintree{nil, 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
{}},
}},
}},
}}
...
...
functional/db_test.go
View file @
af790e46
...
...
@@ -193,7 +193,7 @@ func TestDBClientIdentityRepoMetadata(t *testing.T) {
r
:=
db
.
NewClientIdentityRepo
(
connect
(
t
))
cm
:=
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
url
.
URL
{
Scheme
:
"http"
,
Host
:
"foo.example.com"
},
},
}
...
...
@@ -240,7 +240,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) {
}
meta2
:=
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
url
.
URL
{
Scheme
:
"http"
,
Host
:
"foo.com"
,
Path
:
"/cb"
},
},
}
...
...
functional/repo/client_repo_test.go
View file @
af790e46
...
...
@@ -22,7 +22,7 @@ var (
Secret
:
"secret-1"
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
url
.
URL
{
Scheme
:
"https"
,
Host
:
"client1.example.com/callback"
,
...
...
@@ -36,7 +36,7 @@ var (
Secret
:
"secret-2"
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
url
.
URL
{
Scheme
:
"https"
,
Host
:
"client2.example.com/callback"
,
...
...
integration/client_api_test.go
View file @
af790e46
...
...
@@ -72,8 +72,8 @@ func TestClientCreate(t *testing.T) {
t
.
Error
(
"Expected new client to exist in repo"
)
}
gotURLs
:=
make
([]
string
,
len
(
meta
.
RedirectUR
L
s
))
for
i
,
u
:=
range
meta
.
RedirectUR
L
s
{
gotURLs
:=
make
([]
string
,
len
(
meta
.
RedirectUR
I
s
))
for
i
,
u
:=
range
meta
.
RedirectUR
I
s
{
gotURLs
[
i
]
=
u
.
String
()
}
if
!
reflect
.
DeepEqual
(
newClientInput
.
RedirectURIs
,
gotURLs
)
{
...
...
integration/user_api_test.go
View file @
af790e46
...
...
@@ -104,7 +104,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures {
Secret
:
testClientSecret
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
testRedirectURL
,
},
},
...
...
@@ -115,7 +115,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures {
Secret
:
"secret"
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
testRedirectURL
,
},
},
...
...
schema/workerschema/mapper.go
View file @
af790e46
...
...
@@ -13,7 +13,7 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) {
ID
:
sc
.
Id
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
make
([]
url
.
URL
,
len
(
sc
.
RedirectURIs
)),
RedirectUR
I
s
:
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
.
RedirectUR
L
s
[
i
]
=
*
u
ci
.
Metadata
.
RedirectUR
I
s
[
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
.
RedirectUR
L
s
)),
RedirectURIs
:
make
([]
string
,
len
(
c
.
Metadata
.
RedirectUR
I
s
)),
}
for
i
,
u
:=
range
c
.
Metadata
.
RedirectUR
L
s
{
for
i
,
u
:=
range
c
.
Metadata
.
RedirectUR
I
s
{
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
.
RedirectUR
L
s
)),
RedirectURIs
:
make
([]
string
,
len
(
c
.
Metadata
.
RedirectUR
I
s
)),
}
for
i
,
u
:=
range
c
.
Metadata
.
RedirectUR
L
s
{
for
i
,
u
:=
range
c
.
Metadata
.
RedirectUR
I
s
{
cl
.
RedirectURIs
[
i
]
=
u
.
String
()
}
return
cl
...
...
server/client_registration.go
0 → 100644
View file @
af790e46
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
}
server/client_registration_test.go
0 → 100644
View file @
af790e46
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
)
}
}
}
server/client_resource.go
View file @
af790e46
...
...
@@ -89,7 +89,7 @@ func (c *clientResource) create(w http.ResponseWriter, r *http.Request) {
return
}
clientID
,
err
:=
oidc
.
GenClientID
(
ci
.
Metadata
.
RedirectUR
L
s
[
0
]
.
Host
)
clientID
,
err
:=
oidc
.
GenClientID
(
ci
.
Metadata
.
RedirectUR
I
s
[
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"
))
...
...
server/client_resource_test.go
View file @
af790e46
...
...
@@ -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/http
s"}`
,
wantBody
:
`{"error":"invalid_client_metadata","error_description":"
no host for uri field redirect_uri
s"}`
,
},
// 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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
url
.
URL
{
Scheme
:
"https"
,
Host
:
"example.com"
,
Path
:
"one/two/three"
},
},
},
...
...
server/config.go
View file @
af790e46
...
...
@@ -35,6 +35,7 @@ type ServerConfig struct {
EmailerConfigFile
string
StateConfig
StateConfigurer
EnableRegistration
bool
EnableClientRegistration
bool
}
type
StateConfigurer
interface
{
...
...
@@ -74,6 +75,7 @@ func (cfg *ServerConfig) Server() (*Server, error) {
Connectors
:
[]
connector
.
Connector
{},
EnableRegistration
:
cfg
.
EnableRegistration
,
EnableClientRegistration
:
cfg
.
EnableClientRegistration
,
}
err
=
cfg
.
StateConfig
.
Configure
(
&
srv
)
...
...
server/email_verification.go
View file @
af790e46
...
...
@@ -158,7 +158,7 @@ func handleVerifyEmailResendFunc(
return
}
*
redirectURL
,
err
=
client
.
ValidRedirectURL
(
redirectURL
,
cm
.
RedirectUR
L
s
)
*
redirectURL
,
err
=
client
.
ValidRedirectURL
(
redirectURL
,
cm
.
RedirectUR
I
s
)
if
err
!=
nil
{
switch
err
{
case
(
client
.
ErrorInvalidRedirectURL
)
:
...
...
server/http.go
View file @
af790e46
...
...
@@ -42,6 +42,7 @@ var (
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
.
RedirectUR
L
s
)
==
0
{
if
len
(
cm
.
RedirectUR
I
s
)
==
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
.
RedirectUR
L
s
)
redirectURL
,
err
:=
client
.
ValidRedirectURL
(
acr
.
RedirectURL
,
cm
.
RedirectUR
I
s
)
if
err
!=
nil
{
switch
err
{
case
(
client
.
ErrorCantChooseRedirectURL
)
:
...
...
server/http_test.go
View file @
af790e46
...
...
@@ -83,7 +83,7 @@ func TestHandleAuthFuncResponsesSingleRedirectURL(t *testing.T) {
Secret
:
"secrete"
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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"
},
IDToken
AlgValuesSupported
:
[]
string
{
"RS256"
},
IDToken
SigningAlgValues
:
[]
string
{
"RS256"
},
TokenEndpointAuthMethodsSupported
:
[]
string
{
"client_secret_basic"
},
}
...
...
server/password.go
View file @
af790e46
...
...
@@ -134,7 +134,7 @@ func (h *SendResetPasswordEmailHandler) validateRedirectURL(clientID string, red
return
url
.
URL
{},
false
}
validURL
,
err
:=
client
.
ValidRedirectURL
(
parsed
,
cm
.
RedirectUR
L
s
)
validURL
,
err
:=
client
.
ValidRedirectURL
(
parsed
,
cm
.
RedirectUR
I
s
)
if
err
!=
nil
{
log
.
Errorf
(
"Invalid redirectURL for clientID: redirectURL:%q, clientID:%q"
,
redirectURL
,
clientID
)
return
url
.
URL
{},
false
...
...
server/server.go
View file @
af790e46
...
...
@@ -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"
},
IDToken
AlgValuesSupported
:
[]
string
{
"RS256"
},
IDToken
SigningAlgValues
:
[]
string
{
"RS256"
},
TokenEndpointAuthMethodsSupported
:
[]
string
{
"client_secret_basic"
},
}
if
s
.
EnableClientRegistration
{
regEndpoint
:=
s
.
absURL
(
httpPathClientRegistration
)
cfg
.
RegistrationEndpoint
=
&
regEndpoint
}
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
()
...
...
server/server_test.go
View file @
af790e46
...
...
@@ -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"
},
IDToken
AlgValuesSupported
:
[]
string
{
"RS256"
},
IDToken
SigningAlgValues
:
[]
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
.
RedirectUR
L
s
[
0
],
nonce
,
false
,
[]
string
{
"openid"
})
key
,
err
:=
srv
.
NewSession
(
"bogus_idpc"
,
ci
.
Credentials
.
ID
,
state
,
ci
.
Metadata
.
RedirectUR
I
s
[
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
.
RedirectUR
L
s
[
0
],
ses
.
RedirectURL
)
{
t
.
Fatalf
(
"Session created with incorrect RedirectURL: want=%#v got=%#v"
,
ci
.
Metadata
.
RedirectUR
L
s
[
0
],
ses
.
RedirectURL
)
if
!
reflect
.
DeepEqual
(
ci
.
Metadata
.
RedirectUR
I
s
[
0
],
ses
.
RedirectURL
)
{
t
.
Fatalf
(
"Session created with incorrect RedirectURL: want=%#v got=%#v"
,
ci
.
Metadata
.
RedirectUR
I
s
[
0
],
ses
.
RedirectURL
)
}
if
ci
.
Credentials
.
ID
!=
ses
.
ClientID
{
...
...
@@ -180,7 +182,7 @@ func TestServerLogin(t *testing.T) {
Secret
:
"secrete"
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
.
RedirectUR
L
s
[
0
],
""
,
false
,
[]
string
{
"openid"
})
sessionID
,
err
:=
sm
.
NewSession
(
"test_connector_id"
,
ci
.
Credentials
.
ID
,
"bogus"
,
ci
.
Metadata
.
RedirectUR
I
s
[
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
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
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
.
RedirectUR
L
s
[
0
],
""
,
false
,
[]
string
{
"openid"
})
sessionID
,
err
:=
sm
.
NewSession
(
"test_connector_id"
,
ci
.
Credentials
.
ID
,
"bogus"
,
ci
.
Metadata
.
RedirectUR
I
s
[
0
],
""
,
false
,
[]
string
{
"openid"
})
if
err
!=
nil
{
t
.
Fatalf
(
"Unexpected error: %v"
,
err
)
}
...
...
server/testutil.go
View file @
af790e46
...
...
@@ -133,7 +133,7 @@ func makeTestFixtures() (*testFixtures, error) {
Secret
:
testClientSecret
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
testRedirectURL
,
},
},
...
...
user/api/api.go
View file @
af790e46
...
...
@@ -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
.
RedirectUR
L
s
)
validRedirURL
,
err
:=
client
.
ValidRedirectURL
(
&
redirURL
,
metadata
.
RedirectUR
I
s
)
if
err
!=
nil
{
return
schema
.
UserCreateResponse
{},
ErrorInvalidRedirectURL
}
...
...
user/api/api_test.go
View file @
af790e46
...
...
@@ -136,7 +136,7 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
Secret
:
"secrete"
,
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectUR
L
s
:
[]
url
.
URL
{
RedirectUR
I
s
:
[]
url
.
URL
{
validRedirURL
,
},
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment