diff --git a/schema/adminschema/mapper.go b/schema/adminschema/mapper.go new file mode 100644 index 00000000..3f6c32d1 --- /dev/null +++ b/schema/adminschema/mapper.go @@ -0,0 +1,73 @@ +package adminschema + +import ( + "errors" + "net/url" + + "github.com/coreos/dex/client" + "github.com/coreos/go-oidc/oidc" +) + +func MapSchemaClientToClient(sc Client) (client.Client, error) { + c := client.Client{ + Credentials: oidc.ClientCredentials{ + ID: sc.Id, + Secret: sc.Secret, + }, + Metadata: oidc.ClientMetadata{ + RedirectURIs: make([]url.URL, len(sc.RedirectURIs)), + }, + } + for i, ru := range sc.RedirectURIs { + if ru == "" { + return client.Client{}, errors.New("redirect URL empty") + } + + u, err := url.Parse(ru) + if err != nil { + return client.Client{}, errors.New("redirect URL invalid") + } + + c.Metadata.RedirectURIs[i] = *u + } + + c.Metadata.ClientName = sc.ClientName + + if sc.LogoURI != "" { + logoURI, err := url.Parse(sc.LogoURI) + if err != nil { + return client.Client{}, errors.New("logoURI invalid") + } + c.Metadata.LogoURI = logoURI + } + + if sc.ClientURI != "" { + clientURI, err := url.Parse(sc.ClientURI) + if err != nil { + return client.Client{}, errors.New("clientURI invalid") + } + c.Metadata.ClientURI = clientURI + } + return c, nil +} + +func MapClientToSchemaClient(c client.Client) Client { + cl := Client{ + Id: c.Credentials.ID, + Secret: c.Credentials.Secret, + RedirectURIs: make([]string, len(c.Metadata.RedirectURIs)), + } + for i, u := range c.Metadata.RedirectURIs { + cl.RedirectURIs[i] = u.String() + } + + cl.ClientName = c.Metadata.ClientName + + if c.Metadata.LogoURI != nil { + cl.LogoURI = c.Metadata.LogoURI.String() + } + if c.Metadata.ClientURI != nil { + cl.ClientURI = c.Metadata.ClientURI.String() + } + return cl +} diff --git a/schema/adminschema/mapper_test.go b/schema/adminschema/mapper_test.go new file mode 100644 index 00000000..608e9b53 --- /dev/null +++ b/schema/adminschema/mapper_test.go @@ -0,0 +1,129 @@ +package adminschema + +import ( + "net/url" + "testing" + + "github.com/coreos/go-oidc/oidc" + "github.com/kylelemons/godebug/pretty" + + "github.com/coreos/dex/client" +) + +func TestMapSchemaClientToClient(t *testing.T) { + tests := []struct { + sc Client + want client.Client + wantErr bool + }{ + { + sc: Client{ + Id: "123", + Secret: "sec_123", + RedirectURIs: []string{ + "https://client.example.com", + "https://client2.example.com", + }, + ClientName: "Bill", + LogoURI: "https://logo.example.com", + ClientURI: "https://clientURI.example.com", + }, + want: client.Client{ + Credentials: oidc.ClientCredentials{ + ID: "123", + Secret: "sec_123", + }, + Metadata: oidc.ClientMetadata{ + RedirectURIs: []url.URL{ + *mustParseURL(t, "https://client.example.com"), + *mustParseURL(t, "https://client2.example.com"), + }, + ClientName: "Bill", + LogoURI: mustParseURL(t, "https://logo.example.com"), + ClientURI: mustParseURL(t, "https://clientURI.example.com"), + }, + }, + }, { + sc: Client{ + Id: "123", + Secret: "sec_123", + RedirectURIs: []string{ + "ht.d://p * * *", + }, + }, + wantErr: true, + }, + } + + for i, tt := range tests { + got, err := MapSchemaClientToClient(tt.sc) + if tt.wantErr { + if err == nil { + t.Errorf("case %d: want non-nil error", i) + t.Logf(pretty.Sprint(got)) + } + continue + } + if err != nil { + t.Errorf("case %d: unexpected error mapping: %v", i, err) + } + + if diff := pretty.Compare(tt.want, got); diff != "" { + t.Errorf("case %d: Compare(want, got): %v", i, diff) + } + + } +} + +func TestMapClientToClientSchema(t *testing.T) { + tests := []struct { + c client.Client + want Client + }{ + { + want: Client{ + Id: "123", + Secret: "sec_123", + RedirectURIs: []string{ + "https://client.example.com", + "https://client2.example.com", + }, + ClientName: "Bill", + LogoURI: "https://logo.example.com", + ClientURI: "https://clientURI.example.com", + }, + c: client.Client{ + Credentials: oidc.ClientCredentials{ + ID: "123", + Secret: "sec_123", + }, + Metadata: oidc.ClientMetadata{ + RedirectURIs: []url.URL{ + *mustParseURL(t, "https://client.example.com"), + *mustParseURL(t, "https://client2.example.com"), + }, + ClientName: "Bill", + LogoURI: mustParseURL(t, "https://logo.example.com"), + ClientURI: mustParseURL(t, "https://clientURI.example.com"), + }, + }, + }, + } + + for i, tt := range tests { + got := MapClientToSchemaClient(tt.c) + + if diff := pretty.Compare(tt.want, got); diff != "" { + t.Errorf("case %d: Compare(want, got): %v", i, diff) + } + + } +} + +func mustParseURL(t *testing.T, s string) *url.URL { + u, err := url.Parse(s) + if err != nil { + t.Fatalf("Cannot parse %v as url: %v", s, err) + } + return u +} diff --git a/schema/adminschema/v1.json b/schema/adminschema/v1.json index f7719620..4aa8573c 100644 --- a/schema/adminschema/v1.json +++ b/schema/adminschema/v1.json @@ -45,53 +45,62 @@ } } }, - "ClientCreateRequest": { - "id": "ClientCreateRequest", - "type": "object", - "description": "A request to register a client with dex.", - "properties": { - "isAdmin": { - "type": "boolean" - }, - "client": { - "type": "object", - "properties": { - "redirect_uris": { - "type": "array", - "items": { - "type": "string" - }, - "description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)." - }, - "client_name": { - "type": "string", - "description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ." - }, - "logo_uri": { - "type": "string", - "description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ." - }, - "client_uri": { - "type": "string", - "description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ." - } - } - } + "Client": { + "id": "Client", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The client ID. Ignored in client create requests." + }, + "secret": { + "type": "string", + "description": "The client secret. Ignored in client create requests." + }, + "isAdmin": { + "type": "boolean" + }, + "redirectURIs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)." + }, + "clientName": { + "type": "string", + "description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ." + }, + "logoURI": { + "type": "string", + "description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ." + }, + "clientURI": { + "type": "string", + "description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ." } + } }, - "ClientRegistrationResponse": { - "id": "ClientRegistrationResponse", - "type": "object", - "description": "Upon successful registration, an ID and secret is assigned to the client.", - "properties": { - "client_id": { - "type": "string" - }, - "client_secret": { - "type": "string" - } - } + "ClientCreateRequest": { + "id": "ClientCreateRequest", + "type": "object", + "description": "A request to register a client with dex.", + "properties": { + "client": { + "$ref": "Client" + } } + }, + "ClientCreateResponse": { + "id": "ClientCreateResponse", + "type": "object", + "description": "Upon successful registration, an ID and secret is assigned to the client.", + "properties": { + "client":{ + "$ref": "Client" + } + } + } }, "resources": { "Admin": { @@ -154,7 +163,7 @@ "$ref": "ClientCreateRequest" }, "response": { - "$ref": "ClientRegistrationResponse" + "$ref": "ClientCreateResponse" } } }