forked from mystiq/dex
a418e1c4e7
adds a client manager to handle business logic, leaving the repo for basic crud operations. Also adds client to the test script
217 lines
5.1 KiB
Go
217 lines
5.1 KiB
Go
package manager
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
|
|
"errors"
|
|
|
|
"github.com/coreos/dex/client"
|
|
pcrypto "github.com/coreos/dex/pkg/crypto"
|
|
"github.com/coreos/dex/pkg/log"
|
|
"github.com/coreos/dex/repo"
|
|
"github.com/coreos/go-oidc/oidc"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const (
|
|
// Blowfish, the algorithm underlying bcrypt, has a maximum
|
|
// password length of 72. We explicitly track and check this
|
|
// since the bcrypt library will silently ignore portions of
|
|
// a password past the first 72 characters.
|
|
maxSecretLength = 72
|
|
)
|
|
|
|
type SecretGenerator func() ([]byte, error)
|
|
|
|
func DefaultSecretGenerator() ([]byte, error) {
|
|
return pcrypto.RandBytes(maxSecretLength)
|
|
}
|
|
|
|
func CompareHashAndPassword(hashedPassword, password []byte) error {
|
|
if len(password) > maxSecretLength {
|
|
return errors.New("password length greater than max secret length")
|
|
}
|
|
return bcrypt.CompareHashAndPassword(hashedPassword, password)
|
|
}
|
|
|
|
// ClientManager performs client-related "business-logic" functions on client and related objects.
|
|
// This is in contrast to the Repos which perform little more than CRUD operations.
|
|
type ClientManager struct {
|
|
clientRepo client.ClientRepo
|
|
begin repo.TransactionFactory
|
|
secretGenerator SecretGenerator
|
|
clientIDGenerator func(string) (string, error)
|
|
}
|
|
|
|
type ManagerOptions struct {
|
|
SecretGenerator func() ([]byte, error)
|
|
ClientIDGenerator func(string) (string, error)
|
|
}
|
|
|
|
func NewClientManager(clientRepo client.ClientRepo, txnFactory repo.TransactionFactory, options ManagerOptions) *ClientManager {
|
|
if options.SecretGenerator == nil {
|
|
options.SecretGenerator = DefaultSecretGenerator
|
|
}
|
|
if options.ClientIDGenerator == nil {
|
|
options.ClientIDGenerator = oidc.GenClientID
|
|
}
|
|
return &ClientManager{
|
|
clientRepo: clientRepo,
|
|
begin: txnFactory,
|
|
secretGenerator: options.SecretGenerator,
|
|
clientIDGenerator: options.ClientIDGenerator,
|
|
}
|
|
}
|
|
|
|
func NewClientManagerFromClients(clientRepo client.ClientRepo, txnFactory repo.TransactionFactory, clients []client.Client, options ManagerOptions) (*ClientManager, error) {
|
|
clientManager := NewClientManager(clientRepo, txnFactory, options)
|
|
tx, err := clientManager.begin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
for _, c := range clients {
|
|
if c.Credentials.Secret == "" {
|
|
return nil, fmt.Errorf("client %q has no secret", c.Credentials.ID)
|
|
}
|
|
|
|
cli, err := clientManager.clientFromMetadata(c.Metadata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cli.Admin = c.Admin
|
|
|
|
_, err = clientRepo.New(tx, cli)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return nil, err
|
|
}
|
|
return clientManager, nil
|
|
}
|
|
|
|
func (m *ClientManager) New(meta oidc.ClientMetadata) (*oidc.ClientCredentials, error) {
|
|
tx, err := m.begin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
cli, err := m.clientFromMetadata(meta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
creds := cli.Credentials
|
|
|
|
// Save Client
|
|
_, err = m.clientRepo.New(tx, cli)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Returns creds with unhashed secret
|
|
return &creds, nil
|
|
}
|
|
|
|
func (m *ClientManager) Get(id string) (client.Client, error) {
|
|
return m.clientRepo.Get(nil, id)
|
|
}
|
|
|
|
func (m *ClientManager) All() ([]client.Client, error) {
|
|
return m.clientRepo.All(nil)
|
|
}
|
|
|
|
func (m *ClientManager) Metadata(clientID string) (*oidc.ClientMetadata, error) {
|
|
c, err := m.clientRepo.Get(nil, clientID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &c.Metadata, nil
|
|
}
|
|
|
|
func (m *ClientManager) IsDexAdmin(clientID string) (bool, error) {
|
|
c, err := m.clientRepo.Get(nil, clientID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return c.Admin, nil
|
|
}
|
|
|
|
func (m *ClientManager) SetDexAdmin(clientID string, isAdmin bool) error {
|
|
tx, err := m.begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
c, err := m.clientRepo.Get(tx, clientID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Admin = isAdmin
|
|
err = m.clientRepo.Update(tx, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *ClientManager) Authenticate(creds oidc.ClientCredentials) (bool, error) {
|
|
clientSecret, err := m.clientRepo.GetSecret(nil, creds.ID)
|
|
if err != nil || clientSecret == nil {
|
|
return false, nil
|
|
}
|
|
|
|
dec, err := base64.URLEncoding.DecodeString(creds.Secret)
|
|
if err != nil {
|
|
log.Errorf("error Decoding client creds: %v", err)
|
|
return false, nil
|
|
}
|
|
|
|
ok := CompareHashAndPassword(clientSecret, dec) == nil
|
|
return ok, nil
|
|
}
|
|
|
|
func (m *ClientManager) clientFromMetadata(meta oidc.ClientMetadata) (client.Client, error) {
|
|
// Generate Client ID
|
|
if len(meta.RedirectURIs) < 1 {
|
|
return client.Client{}, errors.New("no client redirect url given")
|
|
}
|
|
clientID, err := m.clientIDGenerator(meta.RedirectURIs[0].Host)
|
|
if err != nil {
|
|
return client.Client{}, err
|
|
}
|
|
|
|
// Generate Secret
|
|
secret, err := m.secretGenerator()
|
|
if err != nil {
|
|
return client.Client{}, err
|
|
}
|
|
clientSecret := base64.URLEncoding.EncodeToString(secret)
|
|
cli := client.Client{
|
|
Credentials: oidc.ClientCredentials{
|
|
ID: clientID,
|
|
Secret: clientSecret,
|
|
},
|
|
Metadata: meta,
|
|
}
|
|
return cli, nil
|
|
}
|