client: Manager.New deals with public clients
* validation of client moved into its own method and tested * public clients have different validation - must have no redirect URIs and must have a clientName set
This commit is contained in:
parent
09e889e7bc
commit
3ec692297b
3 changed files with 112 additions and 11 deletions
|
@ -19,8 +19,9 @@ var (
|
||||||
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.")
|
||||||
ErrorPublicClientRedirectURIs = errors.New("native clients cannot have redirect URIs")
|
|
||||||
ErrorPublicClientMissingName = errors.New("native clients must have a name")
|
ErrorPublicClientRedirectURIs = errors.New("public clients cannot have redirect URIs")
|
||||||
|
ErrorPublicClientMissingName = errors.New("public clients must have a name")
|
||||||
|
|
||||||
ErrorMissingRedirectURI = errors.New("no client redirect url given")
|
ErrorMissingRedirectURI = errors.New("no client redirect url given")
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
@ -21,6 +22,10 @@ const (
|
||||||
maxSecretLength = 72
|
maxSecretLength = 72
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
localHostRedirectURL = mustParseURL("http://localhost:0")
|
||||||
|
)
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
TrustedPeers []string
|
TrustedPeers []string
|
||||||
}
|
}
|
||||||
|
@ -67,6 +72,10 @@ func NewClientManager(clientRepo client.ClientRepo, txnFactory repo.TransactionF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New creates and persists a new client with the given options, returning the generated credentials.
|
||||||
|
// Any Credenials provided with the client are ignored and overwritten by the generated ID and Secret.
|
||||||
|
// "Normal" (i.e. non-Public) clients must have at least one valid RedirectURI in their Metadata.
|
||||||
|
// Public clients must not have any RedirectURIs and must have a client name.
|
||||||
func (m *ClientManager) New(cli client.Client, options *ClientOptions) (*oidc.ClientCredentials, error) {
|
func (m *ClientManager) New(cli client.Client, options *ClientOptions) (*oidc.ClientCredentials, error) {
|
||||||
tx, err := m.begin()
|
tx, err := m.begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,11 +83,14 @@ func (m *ClientManager) New(cli client.Client, options *ClientOptions) (*oidc.Cl
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
if err := validateClient(cli); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = m.addClientCredentials(&cli)
|
err = m.addClientCredentials(&cli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
creds := cli.Credentials
|
creds := cli.Credentials
|
||||||
|
|
||||||
// Save Client
|
// Save Client
|
||||||
|
@ -171,11 +183,15 @@ func (m *ClientManager) Authenticate(creds oidc.ClientCredentials) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ClientManager) addClientCredentials(cli *client.Client) error {
|
func (m *ClientManager) addClientCredentials(cli *client.Client) error {
|
||||||
// Generate Client ID
|
var seed string
|
||||||
if len(cli.Metadata.RedirectURIs) < 1 {
|
if cli.Public {
|
||||||
return errors.New("no client redirect url given")
|
seed = cli.Metadata.ClientName
|
||||||
|
} else {
|
||||||
|
seed = cli.Metadata.RedirectURIs[0].Host
|
||||||
}
|
}
|
||||||
clientID, err := m.clientIDGenerator(cli.Metadata.RedirectURIs[0].Host)
|
|
||||||
|
// Generate Client ID
|
||||||
|
clientID, err := m.clientIDGenerator(seed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -192,3 +208,37 @@ func (m *ClientManager) addClientCredentials(cli *client.Client) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateClient(cli client.Client) error {
|
||||||
|
// NOTE: please be careful changing the errors returned here; they are used
|
||||||
|
// downstream (eg. in the admin API) to determine the http errors returned.
|
||||||
|
if cli.Public {
|
||||||
|
if len(cli.Metadata.RedirectURIs) > 0 {
|
||||||
|
return client.ErrorPublicClientRedirectURIs
|
||||||
|
}
|
||||||
|
if cli.Metadata.ClientName == "" {
|
||||||
|
return client.ErrorPublicClientMissingName
|
||||||
|
}
|
||||||
|
cli.Metadata.RedirectURIs = []url.URL{
|
||||||
|
localHostRedirectURL,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(cli.Metadata.RedirectURIs) < 1 {
|
||||||
|
return client.ErrorMissingRedirectURI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cli.Metadata.Valid()
|
||||||
|
if err != nil {
|
||||||
|
return client.ValidationError{Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseURL(s string) url.URL {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return *u
|
||||||
|
}
|
||||||
|
|
|
@ -168,3 +168,53 @@ func TestAuthenticate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateClient(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
cli client.Client
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cli: client.Client{
|
||||||
|
Metadata: oidc.ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{mustParseURL("http://auth.google.com")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cli: client.Client{},
|
||||||
|
wantErr: client.ErrorMissingRedirectURI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cli: client.Client{
|
||||||
|
Metadata: oidc.ClientMetadata{
|
||||||
|
ClientName: "frank",
|
||||||
|
},
|
||||||
|
Public: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cli: client.Client{
|
||||||
|
Metadata: oidc.ClientMetadata{
|
||||||
|
RedirectURIs: []url.URL{mustParseURL("http://auth.google.com")},
|
||||||
|
ClientName: "frank",
|
||||||
|
},
|
||||||
|
Public: true,
|
||||||
|
},
|
||||||
|
wantErr: client.ErrorPublicClientRedirectURIs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cli: client.Client{
|
||||||
|
Public: true,
|
||||||
|
},
|
||||||
|
wantErr: client.ErrorPublicClientMissingName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
err := validateClient(tt.cli)
|
||||||
|
if err != tt.wantErr {
|
||||||
|
t.Errorf("case %d: want=%v, got=%v", i, tt.wantErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in a new issue