forked from mystiq/dex
Merge pull request #479 from bobbyrullo/specify_yer_client_creds
Allow specification of client_{id, secret} in admin api
This commit is contained in:
commit
a7b860b9c2
8 changed files with 108 additions and 45 deletions
|
@ -78,6 +78,8 @@ var (
|
||||||
|
|
||||||
client.ErrorPublicClientMissingName: errorMaker("bad_request", "Public clients require a ClientName", http.StatusBadRequest),
|
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.ErrorNotFound: errorMaker("resource_not_found", "Resource could not be found.", http.StatusNotFound),
|
||||||
user.ErrorDuplicateEmail: errorMaker("bad_request", "Email already in use.", http.StatusBadRequest),
|
user.ErrorDuplicateEmail: errorMaker("bad_request", "Email already in use.", http.StatusBadRequest),
|
||||||
user.ErrorInvalidEmail: errorMaker("bad_request", "invalid email.", http.StatusBadRequest),
|
user.ErrorInvalidEmail: errorMaker("bad_request", "invalid email.", http.StatusBadRequest),
|
||||||
|
|
|
@ -16,7 +16,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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")
|
ErrorInvalidRedirectURL = errors.New("not a valid redirect url for the given client")
|
||||||
ErrorCantChooseRedirectURL = errors.New("must provide a redirect url; client has many")
|
ErrorCantChooseRedirectURL = errors.New("must provide a redirect url; client has many")
|
||||||
ErrorNoValidRedirectURLs = errors.New("no valid redirect URLs for this client.")
|
ErrorNoValidRedirectURLs = errors.New("no valid redirect URLs for this client.")
|
||||||
|
@ -46,7 +49,7 @@ const (
|
||||||
func HashSecret(creds oidc.ClientCredentials) ([]byte, error) {
|
func HashSecret(creds oidc.ClientCredentials) ([]byte, error) {
|
||||||
secretBytes, err := base64.URLEncoding.DecodeString(creds.Secret)
|
secretBytes, err := base64.URLEncoding.DecodeString(creds.Secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, ErrorInvalidClientSecret
|
||||||
}
|
}
|
||||||
hashed, err := bcrypt.GenerateFromPassword([]byte(
|
hashed, err := bcrypt.GenerateFromPassword([]byte(
|
||||||
secretBytes),
|
secretBytes),
|
||||||
|
|
|
@ -196,18 +196,30 @@ func (m *ClientManager) addClientCredentials(cli *client.Client) error {
|
||||||
seed = cli.Metadata.RedirectURIs[0].Host
|
seed = cli.Metadata.RedirectURIs[0].Host
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Client ID
|
var err error
|
||||||
clientID, err := m.clientIDGenerator(seed)
|
var clientID string
|
||||||
if err != nil {
|
if cli.Credentials.ID != "" {
|
||||||
return err
|
clientID = cli.Credentials.ID
|
||||||
|
} else {
|
||||||
|
// Generate Client ID
|
||||||
|
clientID, err = m.clientIDGenerator(seed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Secret
|
var clientSecret string
|
||||||
secret, err := m.secretGenerator()
|
if cli.Credentials.Secret != "" {
|
||||||
if err != nil {
|
clientSecret = cli.Credentials.Secret
|
||||||
return err
|
} 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{
|
cli.Credentials = oidc.ClientCredentials{
|
||||||
ID: clientID,
|
ID: clientID,
|
||||||
Secret: clientSecret,
|
Secret: clientSecret,
|
||||||
|
|
|
@ -383,12 +383,17 @@ func TestCreateClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
addIDAndSecret := func(cli adminschema.Client) *adminschema.Client {
|
addIDAndSecret := func(cli adminschema.Client) *adminschema.Client {
|
||||||
if cli.Public {
|
if cli.Id == "" {
|
||||||
cli.Id = "client_" + cli.ClientName
|
if cli.Public {
|
||||||
} else {
|
cli.Id = "client_" + cli.ClientName
|
||||||
cli.Id = "client_auth.example.com"
|
} 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
|
return &cli
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,6 +441,24 @@ func TestCreateClient(t *testing.T) {
|
||||||
adminClientWithPeers := adminClientGood
|
adminClientWithPeers := adminClientGood
|
||||||
adminClientWithPeers.TrustedPeers = []string{"test_client_0"}
|
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 {
|
tests := []struct {
|
||||||
req adminschema.ClientCreateRequest
|
req adminschema.ClientCreateRequest
|
||||||
want adminschema.ClientCreateResponse
|
want adminschema.ClientCreateResponse
|
||||||
|
@ -446,24 +469,21 @@ func TestCreateClient(t *testing.T) {
|
||||||
{
|
{
|
||||||
req: adminschema.ClientCreateRequest{},
|
req: adminschema.ClientCreateRequest{},
|
||||||
wantError: http.StatusBadRequest,
|
wantError: http.StatusBadRequest,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminschema.Client{
|
Client: &adminschema.Client{
|
||||||
IsAdmin: true,
|
IsAdmin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantError: http.StatusBadRequest,
|
wantError: http.StatusBadRequest,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminschema.Client{
|
Client: &adminschema.Client{
|
||||||
RedirectURIs: []string{"909090"},
|
RedirectURIs: []string{"909090"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantError: http.StatusBadRequest,
|
wantError: http.StatusBadRequest,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminClientGood,
|
Client: &adminClientGood,
|
||||||
},
|
},
|
||||||
|
@ -471,8 +491,7 @@ func TestCreateClient(t *testing.T) {
|
||||||
Client: addIDAndSecret(adminClientGood),
|
Client: addIDAndSecret(adminClientGood),
|
||||||
},
|
},
|
||||||
wantClient: clientGood,
|
wantClient: clientGood,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminAdminClient,
|
Client: &adminAdminClient,
|
||||||
},
|
},
|
||||||
|
@ -480,8 +499,7 @@ func TestCreateClient(t *testing.T) {
|
||||||
Client: addIDAndSecret(adminAdminClient),
|
Client: addIDAndSecret(adminAdminClient),
|
||||||
},
|
},
|
||||||
wantClient: clientGoodAdmin,
|
wantClient: clientGoodAdmin,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminMultiRedirect,
|
Client: &adminMultiRedirect,
|
||||||
},
|
},
|
||||||
|
@ -489,8 +507,7 @@ func TestCreateClient(t *testing.T) {
|
||||||
Client: addIDAndSecret(adminMultiRedirect),
|
Client: addIDAndSecret(adminMultiRedirect),
|
||||||
},
|
},
|
||||||
wantClient: clientMultiRedirect,
|
wantClient: clientMultiRedirect,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminClientWithPeers,
|
Client: &adminClientWithPeers,
|
||||||
},
|
},
|
||||||
|
@ -499,8 +516,7 @@ func TestCreateClient(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantClient: clientGood,
|
wantClient: clientGood,
|
||||||
wantTrustedPeers: []string{"test_client_0"},
|
wantTrustedPeers: []string{"test_client_0"},
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminPublicClientGood,
|
Client: &adminPublicClientGood,
|
||||||
},
|
},
|
||||||
|
@ -508,18 +524,45 @@ func TestCreateClient(t *testing.T) {
|
||||||
Client: addIDAndSecret(adminPublicClientGood),
|
Client: addIDAndSecret(adminPublicClientGood),
|
||||||
},
|
},
|
||||||
wantClient: clientPublicGood,
|
wantClient: clientPublicGood,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminPublicClientMissingName,
|
Client: &adminPublicClientMissingName,
|
||||||
},
|
},
|
||||||
wantError: http.StatusBadRequest,
|
wantError: http.StatusBadRequest,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
req: adminschema.ClientCreateRequest{
|
req: adminschema.ClientCreateRequest{
|
||||||
Client: &adminPublicClientHasARedirect,
|
Client: &adminPublicClientHasARedirect,
|
||||||
},
|
},
|
||||||
wantError: http.StatusBadRequest,
|
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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
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 ) .,
|
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,
|
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 ) .,
|
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: [
|
redirectURIs: [
|
||||||
string
|
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: [
|
trustedPeers: [
|
||||||
string
|
string
|
||||||
]
|
]
|
||||||
|
|
|
@ -125,7 +125,8 @@ type Client struct {
|
||||||
// Languages and Scripts ) .
|
// Languages and Scripts ) .
|
||||||
ClientURI string `json:"clientURI,omitempty"`
|
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"`
|
Id string `json:"id,omitempty"`
|
||||||
|
|
||||||
IsAdmin bool `json:"isAdmin,omitempty"`
|
IsAdmin bool `json:"isAdmin,omitempty"`
|
||||||
|
@ -141,7 +142,7 @@ type Client struct {
|
||||||
// Public: OPTIONAL. Determines if the client is public. Public clients
|
// Public: OPTIONAL. Determines if the client is public. Public clients
|
||||||
// have certain restrictions: They cannot use their credentials to
|
// have certain restrictions: They cannot use their credentials to
|
||||||
// obtain a client JWT. Their redirects URLs cannot be specified: they
|
// 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"`
|
Public bool `json:"public,omitempty"`
|
||||||
|
|
||||||
// RedirectURIs: REQUIRED for normal clients. Array of Redirection URI
|
// RedirectURIs: REQUIRED for normal clients. Array of Redirection URI
|
||||||
|
@ -154,7 +155,9 @@ type Client struct {
|
||||||
// clients.
|
// clients.
|
||||||
RedirectURIs []string `json:"redirectURIs,omitempty"`
|
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"`
|
Secret string `json:"secret,omitempty"`
|
||||||
|
|
||||||
// TrustedPeers: Array of ClientIDs of clients that are allowed to mint
|
// TrustedPeers: Array of ClientIDs of clients that are allowed to mint
|
||||||
|
|
|
@ -58,11 +58,11 @@ const DiscoveryJSON = `{
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string",
|
"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": {
|
"secret": {
|
||||||
"type": "string",
|
"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": {
|
"isAdmin": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
@ -95,7 +95,7 @@ const DiscoveryJSON = `{
|
||||||
},
|
},
|
||||||
"public": {
|
"public": {
|
||||||
"type": "boolean",
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -51,11 +51,11 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string",
|
"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": {
|
"secret": {
|
||||||
"type": "string",
|
"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": {
|
"isAdmin": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
|
Loading…
Reference in a new issue