mirror of https://github.com/dexidp/dex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
5.2 KiB
199 lines
5.2 KiB
package client |
|
|
|
import ( |
|
"encoding/base64" |
|
"encoding/json" |
|
"errors" |
|
"io" |
|
"net/url" |
|
"reflect" |
|
"strings" |
|
|
|
"golang.org/x/crypto/bcrypt" |
|
|
|
"github.com/coreos/dex/repo" |
|
"github.com/coreos/go-oidc/oidc" |
|
) |
|
|
|
var ( |
|
ErrorInvalidClientID = errors.New("not a valid client ID") |
|
|
|
ErrorInvalidClientSecret = errors.New("not a valid client Secret") |
|
|
|
ErrorDuplicateClientID = errors.New("client ID already exists") |
|
|
|
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.") |
|
|
|
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") |
|
|
|
ErrorNotFound = errors.New("no data found") |
|
) |
|
|
|
type ValidationError struct { |
|
Err error |
|
} |
|
|
|
func (v ValidationError) Error() string { |
|
return v.Err.Error() |
|
} |
|
|
|
const ( |
|
bcryptHashCost = 10 |
|
|
|
OOBRedirectURI = "urn:ietf:wg:oauth:2.0:oob" |
|
) |
|
|
|
func HashSecret(creds oidc.ClientCredentials) ([]byte, error) { |
|
secretBytes, err := base64.URLEncoding.DecodeString(creds.Secret) |
|
if err != nil { |
|
return nil, ErrorInvalidClientSecret |
|
} |
|
hashed, err := bcrypt.GenerateFromPassword([]byte( |
|
secretBytes), |
|
bcryptHashCost) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return hashed, nil |
|
} |
|
|
|
type Client struct { |
|
Credentials oidc.ClientCredentials |
|
Metadata oidc.ClientMetadata |
|
Admin bool |
|
Public bool |
|
} |
|
|
|
func (c Client) ValidRedirectURL(u *url.URL) (url.URL, error) { |
|
if c.Public { |
|
if u == nil { |
|
return url.URL{}, ErrorInvalidRedirectURL |
|
} |
|
if u.String() == OOBRedirectURI { |
|
return *u, nil |
|
} |
|
|
|
if u.Scheme != "http" { |
|
return url.URL{}, ErrorInvalidRedirectURL |
|
} |
|
|
|
hostPort := strings.Split(u.Host, ":") |
|
if len(hostPort) != 2 { |
|
return url.URL{}, ErrorInvalidRedirectURL |
|
} |
|
|
|
if hostPort[0] != "localhost" || u.Path != "" || u.RawPath != "" || u.RawQuery != "" || u.Fragment != "" { |
|
return url.URL{}, ErrorInvalidRedirectURL |
|
} |
|
|
|
return *u, nil |
|
} |
|
|
|
return ValidRedirectURL(u, c.Metadata.RedirectURIs) |
|
} |
|
|
|
type ClientRepo interface { |
|
Get(tx repo.Transaction, clientID string) (Client, error) |
|
|
|
// GetSecret returns the (base64 encoded) hashed client secret |
|
GetSecret(tx repo.Transaction, clientID string) ([]byte, error) |
|
|
|
// All returns all registered Clients |
|
All(tx repo.Transaction) ([]Client, error) |
|
|
|
// New registers a Client with the repo. |
|
// An unused ID must be provided. A corresponding secret will be returned |
|
// in a ClientCredentials struct along with the provided ID. |
|
New(tx repo.Transaction, client Client) (*oidc.ClientCredentials, error) |
|
|
|
Update(tx repo.Transaction, client Client) error |
|
|
|
// GetTrustedPeers returns the list of clients authorized to mint ID token for the given client. |
|
GetTrustedPeers(tx repo.Transaction, clientID string) ([]string, error) |
|
|
|
// SetTrustedPeers sets the list of clients authorized to mint ID token for the given client. |
|
SetTrustedPeers(tx repo.Transaction, clientID string, clientIDs []string) error |
|
} |
|
|
|
// ValidRedirectURL returns the passed in URL if it is present in the redirectURLs list, and returns an error otherwise. |
|
// If nil is passed in as the rURL and there is only one URL in redirectURLs, |
|
// that URL will be returned. If nil is passed but theres >1 URL in the slice, |
|
// then an error is returned. |
|
func ValidRedirectURL(rURL *url.URL, redirectURLs []url.URL) (url.URL, error) { |
|
if len(redirectURLs) == 0 { |
|
return url.URL{}, ErrorNoValidRedirectURLs |
|
} |
|
|
|
if rURL == nil { |
|
if len(redirectURLs) > 1 { |
|
return url.URL{}, ErrorCantChooseRedirectURL |
|
} |
|
|
|
return redirectURLs[0], nil |
|
} |
|
|
|
for _, ru := range redirectURLs { |
|
if reflect.DeepEqual(ru, *rURL) { |
|
return ru, nil |
|
} |
|
} |
|
return url.URL{}, ErrorInvalidRedirectURL |
|
} |
|
|
|
// LoadableClient contains sufficient information for creating a Client and its related entities. |
|
type LoadableClient struct { |
|
Client Client |
|
TrustedPeers []string |
|
} |
|
|
|
func ClientsFromReader(r io.Reader) ([]LoadableClient, error) { |
|
var c []struct { |
|
ID string `json:"id"` |
|
Secret string `json:"secret"` |
|
RedirectURLs []string `json:"redirectURLs"` |
|
Admin bool `json:"admin"` |
|
Public bool `json:"public"` |
|
TrustedPeers []string `json:"trustedPeers"` |
|
} |
|
if err := json.NewDecoder(r).Decode(&c); err != nil { |
|
return nil, err |
|
} |
|
clients := make([]LoadableClient, len(c)) |
|
for i, client := range c { |
|
if client.ID == "" { |
|
return nil, errors.New("clients must have an ID") |
|
} |
|
if len(client.Secret) == 0 { |
|
return nil, errors.New("clients must have a Secret") |
|
} |
|
redirectURIs := make([]url.URL, len(client.RedirectURLs)) |
|
for j, u := range client.RedirectURLs { |
|
uri, err := url.Parse(u) |
|
if err != nil { |
|
return nil, err |
|
} |
|
redirectURIs[j] = *uri |
|
} |
|
|
|
clients[i] = LoadableClient{ |
|
Client: Client{ |
|
Credentials: oidc.ClientCredentials{ |
|
ID: client.ID, |
|
Secret: client.Secret, |
|
}, |
|
Metadata: oidc.ClientMetadata{ |
|
RedirectURIs: redirectURIs, |
|
}, |
|
Admin: client.Admin, |
|
Public: client.Public, |
|
}, |
|
TrustedPeers: client.TrustedPeers, |
|
} |
|
} |
|
return clients, nil |
|
}
|
|
|