From 41740179afd334456f9f6c5f75641cd8f564a722 Mon Sep 17 00:00:00 2001 From: Bobby Rullo Date: Tue, 21 Jun 2016 12:03:47 -0700 Subject: [PATCH 1/2] client: admin API allows user defined creds You can specify your own client ID and secret! --- admin/api.go | 2 + client/client.go | 7 ++- client/manager/manager.go | 30 ++++++++---- integration/admin_api_test.go | 89 ++++++++++++++++++++++++++--------- 4 files changed, 94 insertions(+), 34 deletions(-) diff --git a/admin/api.go b/admin/api.go index 2b51672d..84e6c5ae 100644 --- a/admin/api.go +++ b/admin/api.go @@ -78,6 +78,8 @@ var ( client.ErrorPublicClientMissingName: errorMaker("bad_request", "Public clients require a ClientName", http.StatusBadRequest), + client.ErrorInvalidClientSecret: errorMaker("bad_request", "Secret must be a base64 encoded string", http.StatusBadRequest), + user.ErrorNotFound: errorMaker("resource_not_found", "Resource could not be found.", http.StatusNotFound), user.ErrorDuplicateEmail: errorMaker("bad_request", "Email already in use.", http.StatusBadRequest), user.ErrorInvalidEmail: errorMaker("bad_request", "invalid email.", http.StatusBadRequest), diff --git a/client/client.go b/client/client.go index 55590995..4105dba0 100644 --- a/client/client.go +++ b/client/client.go @@ -16,7 +16,10 @@ import ( ) var ( - ErrorInvalidClientID = errors.New("not a valid client ID") + ErrorInvalidClientID = errors.New("not a valid client ID") + + ErrorInvalidClientSecret = errors.New("not a valid client Secret") + ErrorInvalidRedirectURL = errors.New("not a valid redirect url for the given client") ErrorCantChooseRedirectURL = errors.New("must provide a redirect url; client has many") ErrorNoValidRedirectURLs = errors.New("no valid redirect URLs for this client.") @@ -46,7 +49,7 @@ const ( func HashSecret(creds oidc.ClientCredentials) ([]byte, error) { secretBytes, err := base64.URLEncoding.DecodeString(creds.Secret) if err != nil { - return nil, err + return nil, ErrorInvalidClientSecret } hashed, err := bcrypt.GenerateFromPassword([]byte( secretBytes), diff --git a/client/manager/manager.go b/client/manager/manager.go index 3fefff41..75ad7f30 100644 --- a/client/manager/manager.go +++ b/client/manager/manager.go @@ -196,18 +196,30 @@ func (m *ClientManager) addClientCredentials(cli *client.Client) error { seed = cli.Metadata.RedirectURIs[0].Host } - // Generate Client ID - clientID, err := m.clientIDGenerator(seed) - if err != nil { - return err + var err error + var clientID string + if cli.Credentials.ID != "" { + clientID = cli.Credentials.ID + } else { + // Generate Client ID + clientID, err = m.clientIDGenerator(seed) + if err != nil { + return err + } } - // Generate Secret - secret, err := m.secretGenerator() - if err != nil { - return err + var clientSecret string + if cli.Credentials.Secret != "" { + clientSecret = cli.Credentials.Secret + } else { + // Generate Secret + secret, err := m.secretGenerator() + if err != nil { + return err + } + clientSecret = base64.URLEncoding.EncodeToString(secret) } - clientSecret := base64.URLEncoding.EncodeToString(secret) + cli.Credentials = oidc.ClientCredentials{ ID: clientID, Secret: clientSecret, diff --git a/integration/admin_api_test.go b/integration/admin_api_test.go index 4fc7ac30..c1fa7916 100644 --- a/integration/admin_api_test.go +++ b/integration/admin_api_test.go @@ -383,12 +383,17 @@ func TestCreateClient(t *testing.T) { } addIDAndSecret := func(cli adminschema.Client) *adminschema.Client { - if cli.Public { - cli.Id = "client_" + cli.ClientName - } else { - cli.Id = "client_auth.example.com" + if cli.Id == "" { + if cli.Public { + cli.Id = "client_" + cli.ClientName + } else { + cli.Id = "client_auth.example.com" + } + } + + if cli.Secret == "" { + cli.Secret = base64.URLEncoding.EncodeToString([]byte("client_0")) } - cli.Secret = base64.URLEncoding.EncodeToString([]byte("client_0")) return &cli } @@ -436,6 +441,24 @@ func TestCreateClient(t *testing.T) { adminClientWithPeers := adminClientGood adminClientWithPeers.TrustedPeers = []string{"test_client_0"} + adminClientOwnID := adminClientGood + adminClientOwnID.Id = "my_own_id" + + clientGoodOwnID := clientGood + clientGoodOwnID.Credentials.ID = "my_own_id" + + adminClientOwnSecret := adminClientGood + adminClientOwnSecret.Secret = base64.URLEncoding.EncodeToString([]byte("my_own_secret")) + clientGoodOwnSecret := clientGood + + adminClientOwnIDAndSecret := adminClientGood + adminClientOwnIDAndSecret.Id = "my_own_id" + adminClientOwnIDAndSecret.Secret = base64.URLEncoding.EncodeToString([]byte("my_own_secret")) + clientGoodOwnIDAndSecret := clientGoodOwnID + + adminClientBadSecret := adminClientGood + adminClientBadSecret.Secret = "not_base64_encoded" + tests := []struct { req adminschema.ClientCreateRequest want adminschema.ClientCreateResponse @@ -446,24 +469,21 @@ func TestCreateClient(t *testing.T) { { req: adminschema.ClientCreateRequest{}, wantError: http.StatusBadRequest, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminschema.Client{ IsAdmin: true, }, }, wantError: http.StatusBadRequest, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminschema.Client{ RedirectURIs: []string{"909090"}, }, }, wantError: http.StatusBadRequest, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminClientGood, }, @@ -471,8 +491,7 @@ func TestCreateClient(t *testing.T) { Client: addIDAndSecret(adminClientGood), }, wantClient: clientGood, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminAdminClient, }, @@ -480,8 +499,7 @@ func TestCreateClient(t *testing.T) { Client: addIDAndSecret(adminAdminClient), }, wantClient: clientGoodAdmin, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminMultiRedirect, }, @@ -489,8 +507,7 @@ func TestCreateClient(t *testing.T) { Client: addIDAndSecret(adminMultiRedirect), }, wantClient: clientMultiRedirect, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminClientWithPeers, }, @@ -499,8 +516,7 @@ func TestCreateClient(t *testing.T) { }, wantClient: clientGood, wantTrustedPeers: []string{"test_client_0"}, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminPublicClientGood, }, @@ -508,18 +524,45 @@ func TestCreateClient(t *testing.T) { Client: addIDAndSecret(adminPublicClientGood), }, wantClient: clientPublicGood, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminPublicClientMissingName, }, wantError: http.StatusBadRequest, - }, - { + }, { req: adminschema.ClientCreateRequest{ Client: &adminPublicClientHasARedirect, }, wantError: http.StatusBadRequest, + }, { + req: adminschema.ClientCreateRequest{ + Client: &adminClientOwnID, + }, + want: adminschema.ClientCreateResponse{ + Client: addIDAndSecret(adminClientOwnID), + }, + wantClient: clientGoodOwnID, + }, { + req: adminschema.ClientCreateRequest{ + Client: &adminClientOwnSecret, + }, + want: adminschema.ClientCreateResponse{ + Client: addIDAndSecret(adminClientOwnSecret), + }, + wantClient: clientGoodOwnSecret, + }, { + req: adminschema.ClientCreateRequest{ + Client: &adminClientOwnIDAndSecret, + }, + want: adminschema.ClientCreateResponse{ + Client: addIDAndSecret(adminClientOwnIDAndSecret), + }, + wantClient: clientGoodOwnIDAndSecret, + }, { + req: adminschema.ClientCreateRequest{ + Client: &adminClientBadSecret, + }, + wantError: http.StatusBadRequest, }, } From 65ce2e5766123048d237e59662e1d829741d5b71 Mon Sep 17 00:00:00 2001 From: Bobby Rullo Date: Tue, 21 Jun 2016 12:06:34 -0700 Subject: [PATCH 2/2] schema: update schema docs --- schema/adminschema/README.md | 6 +++--- schema/adminschema/v1-gen.go | 9 ++++++--- schema/adminschema/v1-json.go | 6 +++--- schema/adminschema/v1.json | 4 ++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/schema/adminschema/README.md b/schema/adminschema/README.md index 133df9ba..5025ecf0 100644 --- a/schema/adminschema/README.md +++ b/schema/adminschema/README.md @@ -28,14 +28,14 @@ __Version:__ v1 { clientName: string // OPTIONAL for normal cliens. 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 ). REQUIRED for public clients, clientURI: string // 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 ) ., - id: string // The client ID. Ignored in client create requests., + id: string // The client ID. If specified in a client create request, it will be used as the ID. Otherwise, the server will choose the ID., isAdmin: boolean, logoURI: string // 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 ) ., - public: boolean // OPTIONAL. Determines if the client is public. Public clients have certain restrictions: They cannot use their credentials to obtain a client JWT. Their redirects URLs cannot be specified: they are always http://localhost:$PORT or urn:ietf:wg:oauth:2.0:oob, + public: boolean // OPTIONAL. Determines if the client is public. Public clients have certain restrictions: They cannot use their credentials to obtain a client JWT. Their redirects URLs cannot be specified: they are always http://localhost:$PORT or urn:ietf:wg:oauth:2.0:oob., redirectURIs: [ string ], - secret: string // The client secret. Ignored in client create requests., + secret: string // The client secret. If specified in a client create request, it will be used as the secret. Otherwise, the server will choose the secret. Must be a base64 URLEncoded string., trustedPeers: [ string ] diff --git a/schema/adminschema/v1-gen.go b/schema/adminschema/v1-gen.go index c441eae5..4ebabd33 100644 --- a/schema/adminschema/v1-gen.go +++ b/schema/adminschema/v1-gen.go @@ -125,7 +125,8 @@ type Client struct { // Languages and Scripts ) . ClientURI string `json:"clientURI,omitempty"` - // Id: The client ID. Ignored in client create requests. + // Id: The client ID. If specified in a client create request, it will + // be used as the ID. Otherwise, the server will choose the ID. Id string `json:"id,omitempty"` IsAdmin bool `json:"isAdmin,omitempty"` @@ -141,7 +142,7 @@ type Client struct { // Public: OPTIONAL. Determines if the client is public. Public clients // have certain restrictions: They cannot use their credentials to // obtain a client JWT. Their redirects URLs cannot be specified: they - // are always http://localhost:$PORT or urn:ietf:wg:oauth:2.0:oob + // are always http://localhost:$PORT or urn:ietf:wg:oauth:2.0:oob. Public bool `json:"public,omitempty"` // RedirectURIs: REQUIRED for normal clients. Array of Redirection URI @@ -154,7 +155,9 @@ type Client struct { // clients. RedirectURIs []string `json:"redirectURIs,omitempty"` - // Secret: The client secret. Ignored in client create requests. + // Secret: The client secret. If specified in a client create request, + // it will be used as the secret. Otherwise, the server will choose the + // secret. Must be a base64 URLEncoded string. Secret string `json:"secret,omitempty"` // TrustedPeers: Array of ClientIDs of clients that are allowed to mint diff --git a/schema/adminschema/v1-json.go b/schema/adminschema/v1-json.go index acb2345d..c9a878c5 100644 --- a/schema/adminschema/v1-json.go +++ b/schema/adminschema/v1-json.go @@ -58,11 +58,11 @@ const DiscoveryJSON = `{ "properties": { "id": { "type": "string", - "description": "The client ID. Ignored in client create requests." + "description": "The client ID. If specified in a client create request, it will be used as the ID. Otherwise, the server will choose the ID." }, "secret": { "type": "string", - "description": "The client secret. Ignored in client create requests." + "description": "The client secret. If specified in a client create request, it will be used as the secret. Otherwise, the server will choose the secret. Must be a base64 URLEncoded string." }, "isAdmin": { "type": "boolean" @@ -95,7 +95,7 @@ const DiscoveryJSON = `{ }, "public": { "type": "boolean", - "description": "OPTIONAL. Determines if the client is public. Public clients have certain restrictions: They cannot use their credentials to obtain a client JWT. Their redirects URLs cannot be specified: they are always http://localhost:$PORT or urn:ietf:wg:oauth:2.0:oob" + "description": "OPTIONAL. Determines if the client is public. Public clients have certain restrictions: They cannot use their credentials to obtain a client JWT. Their redirects URLs cannot be specified: they are always http://localhost:$PORT or urn:ietf:wg:oauth:2.0:oob." } } }, diff --git a/schema/adminschema/v1.json b/schema/adminschema/v1.json index 7ca75c1d..83fe76a1 100644 --- a/schema/adminschema/v1.json +++ b/schema/adminschema/v1.json @@ -51,11 +51,11 @@ "properties": { "id": { "type": "string", - "description": "The client ID. Ignored in client create requests." + "description": "The client ID. If specified in a client create request, it will be used as the ID. Otherwise, the server will choose the ID." }, "secret": { "type": "string", - "description": "The client secret. Ignored in client create requests." + "description": "The client secret. If specified in a client create request, it will be used as the secret. Otherwise, the server will choose the secret. Must be a base64 URLEncoded string." }, "isAdmin": { "type": "boolean"