Commit af790e46 authored by Eric Chiang's avatar Eric Chiang

Merge pull request #267 from ericchiang/metadata

add dynamic client registration
parents 0ceecbaa 04cd1851
...@@ -14,7 +14,7 @@ We'll also start the example web app, so we can try registering and logging in. ...@@ -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: Before continuing, you must have the following installed on your system:
* Go 1.4 or greater * 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: In addition, if you wish to try out authenticating against Google's OIDC backend, you must have a new client registered with Google:
......
...@@ -16,23 +16,23 @@ ...@@ -16,23 +16,23 @@
}, },
{ {
"ImportPath": "github.com/coreos/go-oidc/http", "ImportPath": "github.com/coreos/go-oidc/http",
"Rev": "145916abb78708694762ff359ab1e34c47c7947f" "Rev": "6039032c0b15517897116d333ead8edf38792437"
}, },
{ {
"ImportPath": "github.com/coreos/go-oidc/jose", "ImportPath": "github.com/coreos/go-oidc/jose",
"Rev": "145916abb78708694762ff359ab1e34c47c7947f" "Rev": "6039032c0b15517897116d333ead8edf38792437"
}, },
{ {
"ImportPath": "github.com/coreos/go-oidc/key", "ImportPath": "github.com/coreos/go-oidc/key",
"Rev": "145916abb78708694762ff359ab1e34c47c7947f" "Rev": "6039032c0b15517897116d333ead8edf38792437"
}, },
{ {
"ImportPath": "github.com/coreos/go-oidc/oauth2", "ImportPath": "github.com/coreos/go-oidc/oauth2",
"Rev": "145916abb78708694762ff359ab1e34c47c7947f" "Rev": "6039032c0b15517897116d333ead8edf38792437"
}, },
{ {
"ImportPath": "github.com/coreos/go-oidc/oidc", "ImportPath": "github.com/coreos/go-oidc/oidc",
"Rev": "145916abb78708694762ff359ab1e34c47c7947f" "Rev": "6039032c0b15517897116d333ead8edf38792437"
}, },
{ {
"ImportPath": "github.com/coreos/pkg/capnslog", "ImportPath": "github.com/coreos/pkg/capnslog",
......
...@@ -13,6 +13,57 @@ const ( ...@@ -13,6 +13,57 @@ const (
HeaderKeyID = "kid" HeaderKeyID = "kid"
) )
const (
// Encryption Algorithm Header Parameter Values for JWS
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
AlgHS256 = "HS256"
AlgHS384 = "HS384"
AlgHS512 = "HS512"
AlgRS256 = "RS256"
AlgRS384 = "RS384"
AlgRS512 = "RS512"
AlgES256 = "ES256"
AlgES384 = "ES384"
AlgES512 = "ES512"
AlgPS256 = "PS256"
AlgPS384 = "PS384"
AlgPS512 = "PS512"
AlgNone = "none"
)
const (
// Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
AlgRSA15 = "RSA1_5"
AlgRSAOAEP = "RSA-OAEP"
AlgRSAOAEP256 = "RSA-OAEP-256"
AlgA128KW = "A128KW"
AlgA192KW = "A192KW"
AlgA256KW = "A256KW"
AlgDir = "dir"
AlgECDHES = "ECDH-ES"
AlgECDHESA128KW = "ECDH-ES+A128KW"
AlgECDHESA192KW = "ECDH-ES+A192KW"
AlgECDHESA256KW = "ECDH-ES+A256KW"
AlgA128GCMKW = "A128GCMKW"
AlgA192GCMKW = "A192GCMKW"
AlgA256GCMKW = "A256GCMKW"
AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
)
const (
// Encryption Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
EncA128CBCHS256 = "A128CBC-HS256"
EncA128CBCHS384 = "A128CBC-HS384"
EncA256CBCHS512 = "A256CBC-HS512"
EncA128GCM = "A128GCM"
EncA192GCM = "A192GCM"
EncA256GCM = "A256GCM"
)
type JOSEHeader map[string]string type JOSEHeader map[string]string
func (j JOSEHeader) Validate() error { func (j JOSEHeader) Validate() error {
......
...@@ -70,6 +70,10 @@ func (j *JWK) UnmarshalJSON(data []byte) error { ...@@ -70,6 +70,10 @@ func (j *JWK) UnmarshalJSON(data []byte) error {
return nil return nil
} }
type JWKSet struct {
Keys []JWK `json:"keys"`
}
func decodeExponent(e string) (int, error) { func decodeExponent(e string) (int, error) {
decE, err := decodeBase64URLPaddingOptional(e) decE, err := decodeBase64URLPaddingOptional(e)
if err != nil { if err != nil {
......
...@@ -8,14 +8,49 @@ import ( ...@@ -8,14 +8,49 @@ import (
"mime" "mime"
"net/http" "net/http"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings" "strings"
phttp "github.com/coreos/go-oidc/http" phttp "github.com/coreos/go-oidc/http"
) )
// ResponseTypesEqual compares two response_type values. If either
// contains a space, it is treated as an unordered list. For example,
// comparing "code id_token" and "id_token code" would evaluate to true.
func ResponseTypesEqual(r1, r2 string) bool {
if !strings.Contains(r1, " ") || !strings.Contains(r2, " ") {
// fast route, no split needed
return r1 == r2
}
// split, sort, and compare
r1Fields := strings.Fields(r1)
r2Fields := strings.Fields(r2)
if len(r1Fields) != len(r2Fields) {
return false
}
sort.Strings(r1Fields)
sort.Strings(r2Fields)
for i, r1Field := range r1Fields {
if r1Field != r2Fields[i] {
return false
}
}
return true
}
const ( const (
// OAuth2.0 response types registered by OIDC.
//
// See: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#RegistryContents
ResponseTypeCode = "code" ResponseTypeCode = "code"
ResponseTypeCodeIDToken = "code id_token"
ResponseTypeCodeIDTokenToken = "code id_token token"
ResponseTypeIDToken = "id_token"
ResponseTypeIDTokenToken = "id_token token"
ResponseTypeToken = "token"
ResponseTypeNone = "none"
) )
const ( const (
......
...@@ -26,7 +26,7 @@ dex consists of multiple components: ...@@ -26,7 +26,7 @@ dex consists of multiple components:
- configure identity provider connectors - configure identity provider connectors
- administer OIDC client identities - administer OIDC client identities
- **database**; a database is used to for persistent storage for keys, users, - **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. database.
A typical dex deployment consists of N dex-workers behind a load balanacer, and one dex-overlord. 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 { ...@@ -172,7 +172,7 @@ func (ci *clientIdentity) UnmarshalJSON(data []byte) error {
Secret: c.Secret, Secret: c.Secret,
} }
ci.Metadata = oidc.ClientMetadata{ ci.Metadata = oidc.ClientMetadata{
RedirectURLs: make([]url.URL, len(c.RedirectURLs)), RedirectURIs: make([]url.URL, len(c.RedirectURLs)),
} }
for i, us := range c.RedirectURLs { for i, us := range c.RedirectURLs {
...@@ -180,7 +180,7 @@ func (ci *clientIdentity) UnmarshalJSON(data []byte) error { ...@@ -180,7 +180,7 @@ func (ci *clientIdentity) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
ci.Metadata.RedirectURLs[i] = *up ci.Metadata.RedirectURIs[i] = *up
} }
return nil return nil
......
...@@ -18,7 +18,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) { ...@@ -18,7 +18,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
{ {
id: "foo", id: "foo",
meta: oidc.ClientMetadata{ meta: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{ url.URL{
Scheme: "https", Scheme: "https",
Host: "example.com", Host: "example.com",
...@@ -29,7 +29,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) { ...@@ -29,7 +29,7 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
{ {
id: "bar", id: "bar",
meta: oidc.ClientMetadata{ meta: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "https", Host: "example.com/foo"}, url.URL{Scheme: "https", Host: "example.com/foo"},
url.URL{Scheme: "https", Host: "example.com/bar"}, url.URL{Scheme: "https", Host: "example.com/bar"},
}, },
...@@ -60,8 +60,8 @@ func TestMemClientIdentityRepoNew(t *testing.T) { ...@@ -60,8 +60,8 @@ func TestMemClientIdentityRepoNew(t *testing.T) {
t.Errorf("case %d: expected repo to contain newly created Client", i) t.Errorf("case %d: expected repo to contain newly created Client", i)
} }
wantURLs := tt.meta.RedirectURLs wantURLs := tt.meta.RedirectURIs
gotURLs := all[0].Metadata.RedirectURLs gotURLs := all[0].Metadata.RedirectURIs
if !reflect.DeepEqual(wantURLs, gotURLs) { if !reflect.DeepEqual(wantURLs, gotURLs) {
t.Errorf("case %d: redirect url mismatch, want=%v, got=%v", i, 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) { ...@@ -72,7 +72,7 @@ func TestMemClientIdentityRepoNewDuplicate(t *testing.T) {
cr := NewClientIdentityRepo(nil) cr := NewClientIdentityRepo(nil)
meta1 := oidc.ClientMetadata{ meta1 := oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "https", Host: "foo.example.com"}, url.URL{Scheme: "https", Host: "foo.example.com"},
}, },
} }
...@@ -82,7 +82,7 @@ func TestMemClientIdentityRepoNewDuplicate(t *testing.T) { ...@@ -82,7 +82,7 @@ func TestMemClientIdentityRepoNewDuplicate(t *testing.T) {
} }
meta2 := oidc.ClientMetadata{ meta2 := oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "https", Host: "bar.example.com"}, url.URL{Scheme: "https", Host: "bar.example.com"},
}, },
} }
...@@ -174,7 +174,7 @@ func TestClientIdentityUnmarshalJSON(t *testing.T) { ...@@ -174,7 +174,7 @@ func TestClientIdentityUnmarshalJSON(t *testing.T) {
sort.Strings(expectedURLs) sort.Strings(expectedURLs)
actualURLs := make([]string, 0) actualURLs := make([]string, 0)
for _, u := range actual.Metadata.RedirectURLs { for _, u := range actual.Metadata.RedirectURIs {
actualURLs = append(actualURLs, u.String()) actualURLs = append(actualURLs, u.String())
} }
sort.Strings(actualURLs) sort.Strings(actualURLs)
......
...@@ -45,6 +45,7 @@ func main() { ...@@ -45,6 +45,7 @@ func main() {
emailConfig := fs.String("email-cfg", "./static/fixtures/emailer.json", "configures emailer.") emailConfig := fs.String("email-cfg", "./static/fixtures/emailer.json", "configures emailer.")
enableRegistration := fs.Bool("enable-registration", false, "Allows users to self-register") 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") 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() { ...@@ -125,6 +126,7 @@ func main() {
IssuerName: *issuerName, IssuerName: *issuerName,
IssuerLogoURL: *issuerLogoURL, IssuerLogoURL: *issuerLogoURL,
EnableRegistration: *enableRegistration, EnableRegistration: *enableRegistration,
EnableClientRegistration: *enableClientRegistration,
} }
if *noDB { if *noDB {
......
...@@ -37,7 +37,7 @@ func runNewClient(cmd *cobra.Command, args []string) int { ...@@ -37,7 +37,7 @@ func runNewClient(cmd *cobra.Command, args []string) int {
redirectURLs[i] = *u redirectURLs[i] = *u
} }
cc, err := getDriver().NewClient(oidc.ClientMetadata{RedirectURLs: redirectURLs}) cc, err := getDriver().NewClient(oidc.ClientMetadata{RedirectURIs: redirectURLs})
if err != nil { if err != nil {
stderr("Failed creating new client: %v", err) stderr("Failed creating new client: %v", err)
return 1 return 1
......
...@@ -21,13 +21,13 @@ func newAPIDriver(pcfg oidc.ProviderConfig, creds oidc.ClientCredentials) (drive ...@@ -21,13 +21,13 @@ func newAPIDriver(pcfg oidc.ProviderConfig, creds oidc.ClientCredentials) (drive
trans := &oidc.AuthenticatedTransport{ trans := &oidc.AuthenticatedTransport{
TokenRefresher: &oidc.ClientCredsTokenRefresher{ TokenRefresher: &oidc.ClientCredsTokenRefresher{
Issuer: pcfg.Issuer, Issuer: pcfg.Issuer.String(),
OIDCClient: oc, OIDCClient: oc,
}, },
RoundTripper: http.DefaultTransport, RoundTripper: http.DefaultTransport,
} }
hc := &http.Client{Transport: trans} hc := &http.Client{Transport: trans}
svc, err := schema.NewWithBasePath(hc, pcfg.Issuer) svc, err := schema.NewWithBasePath(hc, pcfg.Issuer.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -41,10 +41,10 @@ type apiDriver struct { ...@@ -41,10 +41,10 @@ type apiDriver struct {
func (d *apiDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials, error) { func (d *apiDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials, error) {
sc := &schema.Client{ 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() sc.RedirectURIs[i] = u.String()
} }
......
...@@ -31,7 +31,7 @@ func (d *dbDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials, ...@@ -31,7 +31,7 @@ func (d *dbDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials,
return nil, err return nil, err
} }
clientID, err := oidc.GenClientID(meta.RedirectURLs[0].Host) clientID, err := oidc.GenClientID(meta.RedirectURIs[0].Host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -89,8 +89,8 @@ func TestLoginURL(t *testing.T) { ...@@ -89,8 +89,8 @@ func TestLoginURL(t *testing.T) {
Credentials: oidc.ClientCredentials{ID: tt.cid, Secret: "fake-client-secret"}, Credentials: oidc.ClientCredentials{ID: tt.cid, Secret: "fake-client-secret"},
RedirectURL: tt.redir, RedirectURL: tt.redir,
ProviderConfig: oidc.ProviderConfig{ ProviderConfig: oidc.ProviderConfig{
AuthEndpoint: "http://example.com/authorize", AuthEndpoint: &url.URL{Scheme: "http", Host: "example.com", Path: "/authorize"},
TokenEndpoint: "http://example.com/token", TokenEndpoint: &url.URL{Scheme: "http", Host: "example.com", Path: "/token"},
}, },
Scope: tt.scope, Scope: tt.scope,
} }
......
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url"
"reflect" "reflect"
"github.com/coreos/go-oidc/oidc" "github.com/coreos/go-oidc/oidc"
...@@ -49,7 +48,7 @@ func newClientIdentityModel(id string, secret []byte, meta *oidc.ClientMetadata) ...@@ -49,7 +48,7 @@ func newClientIdentityModel(id string, secret []byte, meta *oidc.ClientMetadata)
return nil, err return nil, err
} }
bmeta, err := json.Marshal(newClientMetadataJSON(meta)) bmeta, err := json.Marshal(meta)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -70,38 +69,6 @@ type clientIdentityModel struct { ...@@ -70,38 +69,6 @@ type clientIdentityModel struct {
DexAdmin bool `db:"dex_admin"` 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) { func (m *clientIdentityModel) ClientIdentity() (*oidc.ClientIdentity, error) {
ci := oidc.ClientIdentity{ ci := oidc.ClientIdentity{
Credentials: oidc.ClientCredentials{ Credentials: oidc.ClientCredentials{
...@@ -110,18 +77,10 @@ func (m *clientIdentityModel) ClientIdentity() (*oidc.ClientIdentity, error) { ...@@ -110,18 +77,10 @@ func (m *clientIdentityModel) ClientIdentity() (*oidc.ClientIdentity, error) {
}, },
} }
var cmj clientMetadataJSON if err := json.Unmarshal([]byte(m.Metadata), &ci.Metadata); err != nil {
err := json.Unmarshal([]byte(m.Metadata), &cmj)
if err != nil {
return nil, err
}
cm, err := cmj.ClientMetadata()
if err != nil {
return nil, err return nil, err
} }
ci.Metadata = *cm
return &ci, nil return &ci, nil
} }
......
...@@ -3,6 +3,7 @@ package db ...@@ -3,6 +3,7 @@ package db
import ( import (
"fmt" "fmt"
"os" "os"
"strconv"
"testing" "testing"
"github.com/go-gorp/gorp" "github.com/go-gorp/gorp"
...@@ -25,7 +26,7 @@ func initDB(dsn string) *gorp.DbMap { ...@@ -25,7 +26,7 @@ func initDB(dsn string) *gorp.DbMap {
func TestGetPlannedMigrations(t *testing.T) { func TestGetPlannedMigrations(t *testing.T) {
dsn := os.Getenv("DEX_TEST_DSN") dsn := os.Getenv("DEX_TEST_DSN")
if 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 return
} }
dbMap := initDB(dsn) dbMap := initDB(dsn)
...@@ -40,3 +41,81 @@ func TestGetPlannedMigrations(t *testing.T) { ...@@ -40,3 +41,81 @@ func TestGetPlannedMigrations(t *testing.T) {
t.Fatalf("expected non-empty migrations") 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)
}
}
}
-- +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 @@ ...@@ -9,6 +9,7 @@
// 0007_session_scope.sql // 0007_session_scope.sql
// 0008_users_active_or_inactive.sql // 0008_users_active_or_inactive.sql
// 0009_key_not_primary_key.sql // 0009_key_not_primary_key.sql
// 0010_client_metadata_field_changed.sql
// DO NOT EDIT! // DO NOT EDIT!
package migrations package migrations
...@@ -91,7 +92,7 @@ func dbMigrations0001_initial_migrationSql() (*asset, error) { ...@@ -91,7 +92,7 @@ func dbMigrations0001_initial_migrationSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -111,7 +112,7 @@ func dbMigrations0002_dex_adminSql() (*asset, error) { ...@@ -111,7 +112,7 @@ func dbMigrations0002_dex_adminSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -131,7 +132,7 @@ func dbMigrations0003_user_created_atSql() (*asset, error) { ...@@ -131,7 +132,7 @@ func dbMigrations0003_user_created_atSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -151,7 +152,7 @@ func dbMigrations0004_session_nonceSql() (*asset, error) { ...@@ -151,7 +152,7 @@ func dbMigrations0004_session_nonceSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -171,7 +172,7 @@ func dbMigrations0005_refresh_token_createSql() (*asset, error) { ...@@ -171,7 +172,7 @@ func dbMigrations0005_refresh_token_createSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -191,7 +192,7 @@ func dbMigrations0006_user_email_uniqueSql() (*asset, error) { ...@@ -191,7 +192,7 @@ func dbMigrations0006_user_email_uniqueSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -211,7 +212,7 @@ func dbMigrations0007_session_scopeSql() (*asset, error) { ...@@ -211,7 +212,7 @@ func dbMigrations0007_session_scopeSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -231,7 +232,7 @@ func dbMigrations0008_users_active_or_inactiveSql() (*asset, error) { ...@@ -231,7 +232,7 @@ func dbMigrations0008_users_active_or_inactiveSql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -251,7 +252,27 @@ func dbMigrations0009_key_not_primary_keySql() (*asset, error) { ...@@ -251,7 +252,27 @@ func dbMigrations0009_key_not_primary_keySql() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
...@@ -317,6 +338,7 @@ var _bindata = map[string]func() (*asset, error){ ...@@ -317,6 +338,7 @@ var _bindata = map[string]func() (*asset, error){
"db/migrations/0007_session_scope.sql": dbMigrations0007_session_scopeSql, "db/migrations/0007_session_scope.sql": dbMigrations0007_session_scopeSql,
"db/migrations/0008_users_active_or_inactive.sql": dbMigrations0008_users_active_or_inactiveSql, "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/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 // AssetDir returns the file names below a certain
...@@ -371,6 +393,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ ...@@ -371,6 +393,7 @@ var _bintree = &bintree{nil, map[string]*bintree{
"0007_session_scope.sql": &bintree{dbMigrations0007_session_scopeSql, 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{}}, "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{}}, "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) { ...@@ -193,7 +193,7 @@ func TestDBClientIdentityRepoMetadata(t *testing.T) {
r := db.NewClientIdentityRepo(connect(t)) r := db.NewClientIdentityRepo(connect(t))
cm := oidc.ClientMetadata{ cm := oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"}, url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"},
url.URL{Scheme: "https", Host: "example.com", Path: "/callback"}, url.URL{Scheme: "https", Host: "example.com", Path: "/callback"},
}, },
...@@ -230,7 +230,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) { ...@@ -230,7 +230,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) {
r := db.NewClientIdentityRepo(connect(t)) r := db.NewClientIdentityRepo(connect(t))
meta1 := oidc.ClientMetadata{ meta1 := oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "foo.example.com"}, url.URL{Scheme: "http", Host: "foo.example.com"},
}, },
} }
...@@ -240,7 +240,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) { ...@@ -240,7 +240,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) {
} }
meta2 := oidc.ClientMetadata{ meta2 := oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "bar.example.com"}, url.URL{Scheme: "http", Host: "bar.example.com"},
}, },
} }
...@@ -254,7 +254,7 @@ func TestDBClientIdentityRepoAuthenticate(t *testing.T) { ...@@ -254,7 +254,7 @@ func TestDBClientIdentityRepoAuthenticate(t *testing.T) {
r := db.NewClientIdentityRepo(connect(t)) r := db.NewClientIdentityRepo(connect(t))
cm := oidc.ClientMetadata{ cm := oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"}, url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"},
}, },
} }
...@@ -302,7 +302,7 @@ func TestDBClientIdentityAll(t *testing.T) { ...@@ -302,7 +302,7 @@ func TestDBClientIdentityAll(t *testing.T) {
r := db.NewClientIdentityRepo(connect(t)) r := db.NewClientIdentityRepo(connect(t))
cm := oidc.ClientMetadata{ cm := oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"}, url.URL{Scheme: "http", Host: "127.0.0.1:5556", Path: "/cb"},
}, },
} }
...@@ -326,7 +326,7 @@ func TestDBClientIdentityAll(t *testing.T) { ...@@ -326,7 +326,7 @@ func TestDBClientIdentityAll(t *testing.T) {
} }
cm = oidc.ClientMetadata{ cm = oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "foo.com", Path: "/cb"}, url.URL{Scheme: "http", Host: "foo.com", Path: "/cb"},
}, },
} }
......
...@@ -22,7 +22,7 @@ var ( ...@@ -22,7 +22,7 @@ var (
Secret: "secret-1", Secret: "secret-1",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{ url.URL{
Scheme: "https", Scheme: "https",
Host: "client1.example.com/callback", Host: "client1.example.com/callback",
...@@ -36,7 +36,7 @@ var ( ...@@ -36,7 +36,7 @@ var (
Secret: "secret-2", Secret: "secret-2",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{ url.URL{
Scheme: "https", Scheme: "https",
Host: "client2.example.com/callback", Host: "client2.example.com/callback",
......
...@@ -72,8 +72,8 @@ func TestClientCreate(t *testing.T) { ...@@ -72,8 +72,8 @@ func TestClientCreate(t *testing.T) {
t.Error("Expected new client to exist in repo") t.Error("Expected new client to exist in repo")
} }
gotURLs := make([]string, len(meta.RedirectURLs)) gotURLs := make([]string, len(meta.RedirectURIs))
for i, u := range meta.RedirectURLs { for i, u := range meta.RedirectURIs {
gotURLs[i] = u.String() gotURLs[i] = u.String()
} }
if !reflect.DeepEqual(newClientInput.RedirectURIs, gotURLs) { if !reflect.DeepEqual(newClientInput.RedirectURIs, gotURLs) {
......
...@@ -104,7 +104,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures { ...@@ -104,7 +104,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures {
Secret: testClientSecret, Secret: testClientSecret,
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
testRedirectURL, testRedirectURL,
}, },
}, },
...@@ -115,7 +115,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures { ...@@ -115,7 +115,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures {
Secret: "secret", Secret: "secret",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
testRedirectURL, testRedirectURL,
}, },
}, },
......
...@@ -13,7 +13,7 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) { ...@@ -13,7 +13,7 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) {
ID: sc.Id, ID: sc.Id,
}, },
Metadata: oidc.ClientMetadata{ 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) { ...@@ -27,7 +27,7 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) {
return oidc.ClientIdentity{}, errors.New("redirect URL invalid") return oidc.ClientIdentity{}, errors.New("redirect URL invalid")
} }
ci.Metadata.RedirectURLs[i] = *u ci.Metadata.RedirectURIs[i] = *u
} }
return ci, nil return ci, nil
...@@ -36,9 +36,9 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) { ...@@ -36,9 +36,9 @@ func MapSchemaClientToClientIdentity(sc Client) (oidc.ClientIdentity, error) {
func MapClientIdentityToSchemaClient(c oidc.ClientIdentity) Client { func MapClientIdentityToSchemaClient(c oidc.ClientIdentity) Client {
cl := Client{ cl := Client{
Id: c.Credentials.ID, 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() cl.RedirectURIs[i] = u.String()
} }
return cl return cl
...@@ -48,9 +48,9 @@ func MapClientIdentityToSchemaClientWithSecret(c oidc.ClientIdentity) ClientWith ...@@ -48,9 +48,9 @@ func MapClientIdentityToSchemaClientWithSecret(c oidc.ClientIdentity) ClientWith
cl := ClientWithSecret{ cl := ClientWithSecret{
Id: c.Credentials.ID, Id: c.Credentials.ID,
Secret: c.Credentials.Secret, 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() cl.RedirectURIs[i] = u.String()
} }
return cl return cl
......
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
}
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) { ...@@ -89,7 +89,7 @@ func (c *clientResource) create(w http.ResponseWriter, r *http.Request) {
return return
} }
clientID, err := oidc.GenClientID(ci.Metadata.RedirectURLs[0].Host) clientID, err := oidc.GenClientID(ci.Metadata.RedirectURIs[0].Host)
if err != nil { if err != nil {
log.Errorf("Failed generating ID for new client: %v", err) log.Errorf("Failed generating ID for new client: %v", err)
writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "unable to generate client ID")) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "unable to generate client ID"))
......
...@@ -89,13 +89,13 @@ func TestCreateInvalidRequest(t *testing.T) { ...@@ -89,13 +89,13 @@ func TestCreateInvalidRequest(t *testing.T) {
{ {
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["asdf.com"]}`)}, req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["asdf.com"]}`)},
wantCode: http.StatusBadRequest, 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 // uri missing host
{ {
req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["http://"]}`)}, req: &http.Request{Method: "POST", URL: u, Header: h, Body: makeBody(`{"redirectURIs":["http://"]}`)},
wantCode: http.StatusBadRequest, 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) { ...@@ -183,7 +183,7 @@ func TestList(t *testing.T) {
oidc.ClientIdentity{ oidc.ClientIdentity{
Credentials: oidc.ClientCredentials{ID: "foo", Secret: "bar"}, Credentials: oidc.ClientCredentials{ID: "foo", Secret: "bar"},
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "example.com"}, url.URL{Scheme: "http", Host: "example.com"},
}, },
}, },
...@@ -202,7 +202,7 @@ func TestList(t *testing.T) { ...@@ -202,7 +202,7 @@ func TestList(t *testing.T) {
oidc.ClientIdentity{ oidc.ClientIdentity{
Credentials: oidc.ClientCredentials{ID: "foo", Secret: "bar"}, Credentials: oidc.ClientCredentials{ID: "foo", Secret: "bar"},
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "example.com"}, url.URL{Scheme: "http", Host: "example.com"},
}, },
}, },
...@@ -210,7 +210,7 @@ func TestList(t *testing.T) { ...@@ -210,7 +210,7 @@ func TestList(t *testing.T) {
oidc.ClientIdentity{ oidc.ClientIdentity{
Credentials: oidc.ClientCredentials{ID: "biz", Secret: "bang"}, Credentials: oidc.ClientCredentials{ID: "biz", Secret: "bang"},
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "https", Host: "example.com", Path: "one/two/three"}, url.URL{Scheme: "https", Host: "example.com", Path: "one/two/three"},
}, },
}, },
......
...@@ -35,6 +35,7 @@ type ServerConfig struct { ...@@ -35,6 +35,7 @@ type ServerConfig struct {
EmailerConfigFile string EmailerConfigFile string
StateConfig StateConfigurer StateConfig StateConfigurer
EnableRegistration bool EnableRegistration bool
EnableClientRegistration bool
} }
type StateConfigurer interface { type StateConfigurer interface {
...@@ -74,6 +75,7 @@ func (cfg *ServerConfig) Server() (*Server, error) { ...@@ -74,6 +75,7 @@ func (cfg *ServerConfig) Server() (*Server, error) {
Connectors: []connector.Connector{}, Connectors: []connector.Connector{},
EnableRegistration: cfg.EnableRegistration, EnableRegistration: cfg.EnableRegistration,
EnableClientRegistration: cfg.EnableClientRegistration,
} }
err = cfg.StateConfig.Configure(&srv) err = cfg.StateConfig.Configure(&srv)
......
...@@ -158,7 +158,7 @@ func handleVerifyEmailResendFunc( ...@@ -158,7 +158,7 @@ func handleVerifyEmailResendFunc(
return return
} }
*redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURLs) *redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURIs)
if err != nil { if err != nil {
switch err { switch err {
case (client.ErrorInvalidRedirectURL): case (client.ErrorInvalidRedirectURL):
......
...@@ -42,6 +42,7 @@ var ( ...@@ -42,6 +42,7 @@ var (
httpPathResetPassword = "/reset-password" httpPathResetPassword = "/reset-password"
httpPathAcceptInvitation = "/accept-invitation" httpPathAcceptInvitation = "/accept-invitation"
httpPathDebugVars = "/debug/vars" httpPathDebugVars = "/debug/vars"
httpPathClientRegistration = "/registration"
cookieLastSeen = "LastSeen" cookieLastSeen = "LastSeen"
cookieShowEmailVerifiedMessage = "ShowEmailVerifiedMessage" cookieShowEmailVerifiedMessage = "ShowEmailVerifiedMessage"
...@@ -55,7 +56,7 @@ func handleDiscoveryFunc(cfg oidc.ProviderConfig) http.HandlerFunc { ...@@ -55,7 +56,7 @@ func handleDiscoveryFunc(cfg oidc.ProviderConfig) http.HandlerFunc {
return return
} }
b, err := json.Marshal(cfg) b, err := json.Marshal(&cfg)
if err != nil { if err != nil {
log.Errorf("Unable to marshal %#v to JSON: %v", cfg, err) 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 ...@@ -309,13 +310,13 @@ func handleAuthFunc(srv OIDCServer, idpcs []connector.Connector, tpl *template.T
return return
} }
if len(cm.RedirectURLs) == 0 { if len(cm.RedirectURIs) == 0 {
log.Errorf("Client %q has no redirect URLs", acr.ClientID) log.Errorf("Client %q has no redirect URLs", acr.ClientID)
writeAuthError(w, oauth2.NewError(oauth2.ErrorServerError), acr.State) writeAuthError(w, oauth2.NewError(oauth2.ErrorServerError), acr.State)
return return
} }
redirectURL, err := client.ValidRedirectURL(acr.RedirectURL, cm.RedirectURLs) redirectURL, err := client.ValidRedirectURL(acr.RedirectURL, cm.RedirectURIs)
if err != nil { if err != nil {
switch err { switch err {
case (client.ErrorCantChooseRedirectURL): case (client.ErrorCantChooseRedirectURL):
......
...@@ -83,7 +83,7 @@ func TestHandleAuthFuncResponsesSingleRedirectURL(t *testing.T) { ...@@ -83,7 +83,7 @@ func TestHandleAuthFuncResponsesSingleRedirectURL(t *testing.T) {
Secret: "secrete", Secret: "secrete",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "client.example.com", Path: "/callback"}, url.URL{Scheme: "http", Host: "client.example.com", Path: "/callback"},
}, },
}, },
...@@ -206,7 +206,7 @@ func TestHandleAuthFuncResponsesMultipleRedirectURLs(t *testing.T) { ...@@ -206,7 +206,7 @@ func TestHandleAuthFuncResponsesMultipleRedirectURLs(t *testing.T) {
Secret: "secrete", Secret: "secrete",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "foo.example.com", Path: "/callback"}, url.URL{Scheme: "http", Host: "foo.example.com", Path: "/callback"},
url.URL{Scheme: "http", Host: "bar.example.com", Path: "/callback"}, url.URL{Scheme: "http", Host: "bar.example.com", Path: "/callback"},
}, },
...@@ -363,17 +363,22 @@ func TestHandleDiscoveryFuncMethodNotAllowed(t *testing.T) { ...@@ -363,17 +363,22 @@ func TestHandleDiscoveryFuncMethodNotAllowed(t *testing.T) {
} }
func TestHandleDiscoveryFunc(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{ cfg := oidc.ProviderConfig{
Issuer: u, Issuer: &u,
AuthEndpoint: u + httpPathAuth, AuthEndpoint: pathURL(httpPathAuth),
TokenEndpoint: u + httpPathToken, TokenEndpoint: pathURL(httpPathToken),
KeysEndpoint: u + httpPathKeys, KeysEndpoint: pathURL(httpPathKeys),
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode}, GrantTypesSupported: []string{oauth2.GrantTypeAuthCode},
ResponseTypesSupported: []string{"code"}, ResponseTypesSupported: []string{"code"},
SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: []string{"public"},
IDTokenAlgValuesSupported: []string{"RS256"}, IDTokenSigningAlgValues: []string{"RS256"},
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"}, TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
} }
......
...@@ -134,7 +134,7 @@ func (h *SendResetPasswordEmailHandler) validateRedirectURL(clientID string, red ...@@ -134,7 +134,7 @@ func (h *SendResetPasswordEmailHandler) validateRedirectURL(clientID string, red
return url.URL{}, false return url.URL{}, false
} }
validURL, err := client.ValidRedirectURL(parsed, cm.RedirectURLs) validURL, err := client.ValidRedirectURL(parsed, cm.RedirectURIs)
if err != nil { if err != nil {
log.Errorf("Invalid redirectURL for clientID: redirectURL:%q, clientID:%q", redirectURL, clientID) log.Errorf("Invalid redirectURL for clientID: redirectURL:%q, clientID:%q", redirectURL, clientID)
return url.URL{}, false return url.URL{}, false
......
...@@ -74,6 +74,7 @@ type Server struct { ...@@ -74,6 +74,7 @@ type Server struct {
RefreshTokenRepo refresh.RefreshTokenRepo RefreshTokenRepo refresh.RefreshTokenRepo
UserEmailer *useremail.UserEmailer UserEmailer *useremail.UserEmailer
EnableRegistration bool EnableRegistration bool
EnableClientRegistration bool
localConnectorID string localConnectorID string
} }
...@@ -111,21 +112,27 @@ func (s *Server) KillSession(sessionKey string) error { ...@@ -111,21 +112,27 @@ func (s *Server) KillSession(sessionKey string) error {
} }
func (s *Server) ProviderConfig() oidc.ProviderConfig { 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{ cfg := oidc.ProviderConfig{
Issuer: iss, Issuer: &s.IssuerURL,
AuthEndpoint: &authEndpoint,
AuthEndpoint: iss + httpPathAuth, TokenEndpoint: &tokenEndpoint,
TokenEndpoint: iss + httpPathToken, KeysEndpoint: &keysEndpoint,
KeysEndpoint: iss + httpPathKeys,
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeClientCreds}, GrantTypesSupported: []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeClientCreds},
ResponseTypesSupported: []string{"code"}, ResponseTypesSupported: []string{"code"},
SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: []string{"public"},
IDTokenAlgValuesSupported: []string{"RS256"}, IDTokenSigningAlgValues: []string{"RS256"},
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"}, TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
} }
if s.EnableClientRegistration {
regEndpoint := s.absURL(httpPathClientRegistration)
cfg.RegistrationEndpoint = &regEndpoint
}
return cfg return cfg
} }
...@@ -241,6 +248,10 @@ func (s *Server) HTTPHandler() http.Handler { ...@@ -241,6 +248,10 @@ func (s *Server) HTTPHandler() http.Handler {
redirectValidityWindow: s.SessionManager.ValidityWindow, redirectValidityWindow: s.SessionManager.ValidityWindow,
}) })
if s.EnableClientRegistration {
mux.HandleFunc(httpPathClientRegistration, s.handleClientRegistration)
}
mux.HandleFunc(httpPathDebugVars, health.ExpvarHandler) mux.HandleFunc(httpPathDebugVars, health.ExpvarHandler)
pcfg := s.ProviderConfig() pcfg := s.ProviderConfig()
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"github.com/coreos/go-oidc/key" "github.com/coreos/go-oidc/key"
"github.com/coreos/go-oidc/oauth2" "github.com/coreos/go-oidc/oauth2"
"github.com/coreos/go-oidc/oidc" "github.com/coreos/go-oidc/oidc"
"github.com/kylelemons/godebug/pretty"
) )
type StaticKeyManager struct { type StaticKeyManager struct {
...@@ -100,20 +101,21 @@ func TestServerProviderConfig(t *testing.T) { ...@@ -100,20 +101,21 @@ func TestServerProviderConfig(t *testing.T) {
srv := &Server{IssuerURL: url.URL{Scheme: "http", Host: "server.example.com"}} srv := &Server{IssuerURL: url.URL{Scheme: "http", Host: "server.example.com"}}
want := oidc.ProviderConfig{ want := oidc.ProviderConfig{
Issuer: "http://server.example.com", Issuer: &url.URL{Scheme: "http", Host: "server.example.com"},
AuthEndpoint: "http://server.example.com/auth", AuthEndpoint: &url.URL{Scheme: "http", Host: "server.example.com", Path: "/auth"},
TokenEndpoint: "http://server.example.com/token", TokenEndpoint: &url.URL{Scheme: "http", Host: "server.example.com", Path: "/token"},
KeysEndpoint: "http://server.example.com/keys", KeysEndpoint: &url.URL{Scheme: "http", Host: "server.example.com", Path: "/keys"},
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeClientCreds}, GrantTypesSupported: []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeClientCreds},
ResponseTypesSupported: []string{"code"}, ResponseTypesSupported: []string{"code"},
SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: []string{"public"},
IDTokenAlgValuesSupported: []string{"RS256"}, IDTokenSigningAlgValues: []string{"RS256"},
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"}, TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
} }
got := srv.ProviderConfig() got := srv.ProviderConfig()
if !reflect.DeepEqual(want, got) { if diff := pretty.Compare(want, got); diff != "" {
t.Fatalf("want=%#v, got=%#v", want, got) t.Fatalf("provider config did not match expected: %s", diff)
} }
} }
...@@ -131,7 +133,7 @@ func TestServerNewSession(t *testing.T) { ...@@ -131,7 +133,7 @@ func TestServerNewSession(t *testing.T) {
Secret: "secrete", Secret: "secrete",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{ url.URL{
Scheme: "http", Scheme: "http",
Host: "client.example.com", Host: "client.example.com",
...@@ -141,7 +143,7 @@ func TestServerNewSession(t *testing.T) { ...@@ -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 { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
...@@ -156,8 +158,8 @@ func TestServerNewSession(t *testing.T) { ...@@ -156,8 +158,8 @@ func TestServerNewSession(t *testing.T) {
t.Fatalf("Unable to add Identity to Session: %v", err) t.Fatalf("Unable to add Identity to Session: %v", err)
} }
if !reflect.DeepEqual(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.RedirectURLs[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 { if ci.Credentials.ID != ses.ClientID {
...@@ -180,7 +182,7 @@ func TestServerLogin(t *testing.T) { ...@@ -180,7 +182,7 @@ func TestServerLogin(t *testing.T) {
Secret: "secrete", Secret: "secrete",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{ url.URL{
Scheme: "http", Scheme: "http",
Host: "client.example.com", Host: "client.example.com",
...@@ -197,7 +199,7 @@ func TestServerLogin(t *testing.T) { ...@@ -197,7 +199,7 @@ func TestServerLogin(t *testing.T) {
sm := session.NewSessionManager(session.NewSessionRepo(), session.NewSessionKeyRepo()) sm := session.NewSessionManager(session.NewSessionRepo(), session.NewSessionKeyRepo())
sm.GenerateCode = staticGenerateCodeFunc("fakecode") 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 { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
...@@ -269,7 +271,7 @@ func TestServerLoginDisabledUser(t *testing.T) { ...@@ -269,7 +271,7 @@ func TestServerLoginDisabledUser(t *testing.T) {
Secret: "secrete", Secret: "secrete",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
url.URL{ url.URL{
Scheme: "http", Scheme: "http",
Host: "client.example.com", Host: "client.example.com",
...@@ -286,7 +288,7 @@ func TestServerLoginDisabledUser(t *testing.T) { ...@@ -286,7 +288,7 @@ func TestServerLoginDisabledUser(t *testing.T) {
sm := session.NewSessionManager(session.NewSessionRepo(), session.NewSessionKeyRepo()) sm := session.NewSessionManager(session.NewSessionRepo(), session.NewSessionKeyRepo())
sm.GenerateCode = staticGenerateCodeFunc("fakecode") 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 { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
......
...@@ -133,7 +133,7 @@ func makeTestFixtures() (*testFixtures, error) { ...@@ -133,7 +133,7 @@ func makeTestFixtures() (*testFixtures, error) {
Secret: testClientSecret, Secret: testClientSecret,
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
testRedirectURL, testRedirectURL,
}, },
}, },
......
...@@ -154,7 +154,7 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s ...@@ -154,7 +154,7 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
return schema.UserCreateResponse{}, mapError(err) return schema.UserCreateResponse{}, mapError(err)
} }
validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURLs) validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURIs)
if err != nil { if err != nil {
return schema.UserCreateResponse{}, ErrorInvalidRedirectURL return schema.UserCreateResponse{}, ErrorInvalidRedirectURL
} }
......
...@@ -136,7 +136,7 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) { ...@@ -136,7 +136,7 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
Secret: "secrete", Secret: "secrete",
}, },
Metadata: oidc.ClientMetadata{ Metadata: oidc.ClientMetadata{
RedirectURLs: []url.URL{ RedirectURIs: []url.URL{
validRedirURL, validRedirURL,
}, },
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment