dex/db/client.go

303 lines
6.3 KiB
Go
Raw Normal View History

2015-08-18 05:57:27 +05:30
package db
import (
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"reflect"
2015-08-18 05:57:27 +05:30
"github.com/coreos/go-oidc/oidc"
"github.com/go-gorp/gorp"
2015-08-18 05:57:27 +05:30
"golang.org/x/crypto/bcrypt"
"github.com/coreos/dex/client"
pcrypto "github.com/coreos/dex/pkg/crypto"
"github.com/coreos/dex/pkg/log"
)
const (
clientTableName = "client_identity"
2015-08-18 05:57:27 +05:30
bcryptHashCost = 10
// 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
// postgres error codes
pgErrorCodeUniqueViolation = "23505" // unique_violation
)
func init() {
register(table{
name: clientTableName,
model: clientModel{},
2015-08-18 05:57:27 +05:30
autoinc: false,
pkey: []string{"id"},
})
}
func newClientModel(cli client.Client) (*clientModel, error) {
secretBytes, err := base64.URLEncoding.DecodeString(cli.Credentials.Secret)
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
hashed, err := bcrypt.GenerateFromPassword([]byte(
secretBytes),
bcryptHashCost)
if err != nil {
return nil, err
}
bmeta, err := json.Marshal(&cli.Metadata)
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
cim := clientModel{
ID: cli.Credentials.ID,
2015-08-18 05:57:27 +05:30
Secret: hashed,
Metadata: string(bmeta),
DexAdmin: cli.Admin,
2015-08-18 05:57:27 +05:30
}
return &cim, nil
}
type clientModel struct {
2015-08-18 05:57:27 +05:30
ID string `db:"id"`
Secret []byte `db:"secret"`
Metadata string `db:"metadata"`
2015-08-20 04:10:36 +05:30
DexAdmin bool `db:"dex_admin"`
2015-08-18 05:57:27 +05:30
}
func (m *clientModel) Client() (*client.Client, error) {
ci := client.Client{
2015-08-18 05:57:27 +05:30
Credentials: oidc.ClientCredentials{
ID: m.ID,
2015-08-18 05:57:27 +05:30
},
Admin: m.DexAdmin,
2015-08-18 05:57:27 +05:30
}
if err := json.Unmarshal([]byte(m.Metadata), &ci.Metadata); err != nil {
2015-08-18 05:57:27 +05:30
return nil, err
}
return &ci, nil
}
func NewClientRepo(dbm *gorp.DbMap) client.ClientRepo {
return newClientRepo(dbm)
}
func newClientRepo(dbm *gorp.DbMap) *clientRepo {
return &clientRepo{db: &db{dbm}}
2015-08-18 05:57:27 +05:30
}
func NewClientRepoFromClients(dbm *gorp.DbMap, clients []client.Client) (client.ClientRepo, error) {
repo := newClientRepo(dbm)
tx, err := repo.begin()
2016-02-09 05:31:16 +05:30
if err != nil {
return nil, err
}
defer tx.Rollback()
exec := repo.executor(tx)
2015-08-18 05:57:27 +05:30
for _, c := range clients {
if c.Credentials.Secret == "" {
return nil, fmt.Errorf("client %q has no secret", c.Credentials.ID)
}
cm, err := newClientModel(c)
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
err = exec.Insert(cm)
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
}
2016-02-09 05:31:16 +05:30
if err := tx.Commit(); err != nil {
return nil, err
}
return repo, nil
2015-08-18 05:57:27 +05:30
}
type clientRepo struct {
*db
2015-08-18 05:57:27 +05:30
}
func (r *clientRepo) Get(clientID string) (client.Client, error) {
m, err := r.executor(nil).Get(clientModel{}, clientID)
2015-08-18 05:57:27 +05:30
if err == sql.ErrNoRows || m == nil {
return client.Client{}, client.ErrorNotFound
2015-08-18 05:57:27 +05:30
}
if err != nil {
return client.Client{}, err
2015-08-18 05:57:27 +05:30
}
cim, ok := m.(*clientModel)
2015-08-18 05:57:27 +05:30
if !ok {
log.Errorf("expected clientModel but found %v", reflect.TypeOf(m))
return client.Client{}, errors.New("unrecognized model")
}
ci, err := cim.Client()
if err != nil {
return client.Client{}, err
2015-08-18 05:57:27 +05:30
}
return *ci, nil
}
func (r *clientRepo) Metadata(clientID string) (*oidc.ClientMetadata, error) {
c, err := r.Get(clientID)
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
return &c.Metadata, nil
2015-08-18 05:57:27 +05:30
}
func (r *clientRepo) IsDexAdmin(clientID string) (bool, error) {
m, err := r.executor(nil).Get(clientModel{}, clientID)
2015-08-18 05:57:27 +05:30
if m == nil || err != nil {
return false, err
}
cim, ok := m.(*clientModel)
2015-08-18 05:57:27 +05:30
if !ok {
log.Errorf("expected clientModel but found %v", reflect.TypeOf(m))
2015-08-18 05:57:27 +05:30
return false, errors.New("unrecognized model")
}
return cim.DexAdmin, nil
}
func (r *clientRepo) SetDexAdmin(clientID string, isAdmin bool) error {
tx, err := r.begin()
2015-08-18 05:57:27 +05:30
if err != nil {
return err
}
2016-02-09 05:31:16 +05:30
defer tx.Rollback()
exec := r.executor(tx)
2015-08-18 05:57:27 +05:30
m, err := exec.Get(clientModel{}, clientID)
2015-08-18 05:57:27 +05:30
if m == nil || err != nil {
return err
}
cim, ok := m.(*clientModel)
2015-08-18 05:57:27 +05:30
if !ok {
log.Errorf("expected clientModel but found %v", reflect.TypeOf(m))
2015-08-18 05:57:27 +05:30
return errors.New("unrecognized model")
}
cim.DexAdmin = isAdmin
_, err = exec.Update(cim)
2015-08-18 05:57:27 +05:30
if err != nil {
return err
}
2016-02-09 05:31:16 +05:30
return tx.Commit()
2015-08-18 05:57:27 +05:30
}
func (r *clientRepo) Authenticate(creds oidc.ClientCredentials) (bool, error) {
m, err := r.executor(nil).Get(clientModel{}, creds.ID)
2015-08-18 05:57:27 +05:30
if m == nil || err != nil {
return false, err
}
cim, ok := m.(*clientModel)
2015-08-18 05:57:27 +05:30
if !ok {
log.Errorf("expected clientModel but found %v", reflect.TypeOf(m))
2015-08-18 05:57:27 +05:30
return false, errors.New("unrecognized model")
}
dec, err := base64.URLEncoding.DecodeString(creds.Secret)
if err != nil {
log.Errorf("error Decoding client creds: %v", err)
2015-08-18 05:57:27 +05:30
return false, nil
}
if len(dec) > maxSecretLength {
return false, nil
}
ok = bcrypt.CompareHashAndPassword(cim.Secret, dec) == nil
return ok, nil
}
var alreadyExistsCheckers []func(err error) bool
func registerAlreadyExistsChecker(f func(err error) bool) {
alreadyExistsCheckers = append(alreadyExistsCheckers, f)
}
// isAlreadyExistsErr detects database error codes for failing a unique constraint.
//
// Because database drivers are optionally compiled, use registerAlreadyExistsChecker to
// register driver specific implementations.
func isAlreadyExistsErr(err error) bool {
for _, checker := range alreadyExistsCheckers {
if checker(err) {
return true
}
}
return false
}
func (r *clientRepo) New(cli client.Client) (*oidc.ClientCredentials, error) {
2015-08-18 05:57:27 +05:30
secret, err := pcrypto.RandBytes(maxSecretLength)
if err != nil {
return nil, err
}
cli.Credentials.Secret = base64.URLEncoding.EncodeToString(secret)
cim, err := newClientModel(cli)
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
if err := r.executor(nil).Insert(cim); err != nil {
if isAlreadyExistsErr(err) {
err = errors.New("client ID already exists")
2015-08-18 05:57:27 +05:30
}
return nil, err
}
cc := oidc.ClientCredentials{
ID: cli.Credentials.ID,
Secret: cli.Credentials.Secret,
2015-08-18 05:57:27 +05:30
}
return &cc, nil
}
func (r *clientRepo) All() ([]client.Client, error) {
qt := r.quote(clientTableName)
2015-08-18 05:57:27 +05:30
q := fmt.Sprintf("SELECT * FROM %s", qt)
objs, err := r.executor(nil).Select(&clientModel{}, q)
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
cs := make([]client.Client, len(objs))
2015-08-18 05:57:27 +05:30
for i, obj := range objs {
m, ok := obj.(*clientModel)
2015-08-18 05:57:27 +05:30
if !ok {
return nil, errors.New("unable to cast client identity to clientModel")
2015-08-18 05:57:27 +05:30
}
ci, err := m.Client()
2015-08-18 05:57:27 +05:30
if err != nil {
return nil, err
}
cs[i] = *ci
}
return cs, nil
}