db, client: add data model for trusted peers

Trusted Peers are clients that are authorized to mint tokens
for another client.
This commit is contained in:
Bobby Rullo 2016-04-22 14:09:28 -07:00
parent 546463adcc
commit f9dbc8a3d2
15 changed files with 119 additions and 11 deletions

View file

@ -141,7 +141,7 @@ func (a *AdminAPI) CreateClient(req adminschema.ClientCreateRequest) (adminschem
} }
// metadata is guaranteed to have at least one redirect_uri by earlier validation. // metadata is guaranteed to have at least one redirect_uri by earlier validation.
creds, err := a.clientManager.New(cli) creds, err := a.clientManager.New(cli, nil)
if err != nil { if err != nil {
return adminschema.ClientCreateResponse{}, mapError(err) return adminschema.ClientCreateResponse{}, mapError(err)
} }

View file

@ -15,6 +15,7 @@ import (
) )
var ( var (
ErrorInvalidClientID = errors.New("not a valid client ID")
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.")
@ -60,6 +61,12 @@ type ClientRepo interface {
New(tx repo.Transaction, client Client) (*oidc.ClientCredentials, error) New(tx repo.Transaction, client Client) (*oidc.ClientCredentials, error)
Update(tx repo.Transaction, client Client) 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. // ValidRedirectURL returns the passed in URL if it is present in the redirectURLs list, and returns an error otherwise.

View file

@ -21,6 +21,10 @@ const (
maxSecretLength = 72 maxSecretLength = 72
) )
type ClientOptions struct {
TrustedPeers []string
}
type SecretGenerator func() ([]byte, error) type SecretGenerator func() ([]byte, error)
func DefaultSecretGenerator() ([]byte, error) { func DefaultSecretGenerator() ([]byte, error) {
@ -63,7 +67,7 @@ func NewClientManager(clientRepo client.ClientRepo, txnFactory repo.TransactionF
} }
} }
func (m *ClientManager) New(cli client.Client) (*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 {
return nil, err return nil, err
@ -83,6 +87,13 @@ func (m *ClientManager) New(cli client.Client) (*oidc.ClientCredentials, error)
return nil, err return nil, err
} }
if options != nil && len(options.TrustedPeers) > 0 {
err = m.clientRepo.SetTrustedPeers(tx, creds.ID, options.TrustedPeers)
if err != nil {
return nil, err
}
}
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -132,7 +132,7 @@ func TestAuthenticate(t *testing.T) {
cli := client.Client{ cli := client.Client{
Metadata: cm, Metadata: cm,
} }
cc, err := f.mgr.New(cli) cc, err := f.mgr.New(cli, nil)
if err != nil { if err != nil {
t.Fatalf(err.Error()) t.Fatalf(err.Error())
} }

View file

@ -34,7 +34,7 @@ func (d *dbDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials,
cli := client.Client{ cli := client.Client{
Metadata: meta, Metadata: meta,
} }
return d.ciManager.New(cli) return d.ciManager.New(cli, nil)
} }
func (d *dbDriver) ConnectorConfigs() ([]connector.ConnectorConfig, error) { func (d *dbDriver) ConnectorConfigs() ([]connector.ConnectorConfig, error) {

View file

@ -17,6 +17,7 @@ import (
const ( const (
clientTableName = "client_identity" clientTableName = "client_identity"
trustedPeerTableName = "trusted_peers"
// postgres error codes // postgres error codes
pgErrorCodeUniqueViolation = "23505" // unique_violation pgErrorCodeUniqueViolation = "23505" // unique_violation
@ -29,6 +30,13 @@ func init() {
autoinc: false, autoinc: false,
pkey: []string{"id"}, pkey: []string{"id"},
}) })
register(table{
name: trustedPeerTableName,
model: trustedPeerModel{},
autoinc: false,
pkey: []string{"client_id", "trusted_client_id"},
})
} }
func newClientModel(cli client.Client) (*clientModel, error) { func newClientModel(cli client.Client) (*clientModel, error) {
@ -58,6 +66,11 @@ type clientModel struct {
DexAdmin bool `db:"dex_admin"` DexAdmin bool `db:"dex_admin"`
} }
type trustedPeerModel struct {
ClientID string `db:"client_id"`
TrustedClientID string `db:"trusted_client_id"`
}
func (m *clientModel) Client() (*client.Client, error) { func (m *clientModel) Client() (*client.Client, error) {
ci := client.Client{ ci := client.Client{
Credentials: oidc.ClientCredentials{ Credentials: oidc.ClientCredentials{
@ -254,3 +267,63 @@ func (r *clientRepo) update(tx repo.Transaction, cli client.Client) error {
_, err = ex.Update(cm) _, err = ex.Update(cm)
return err return err
} }
func (r *clientRepo) GetTrustedPeers(tx repo.Transaction, clientID string) ([]string, error) {
ex := r.executor(tx)
if clientID == "" {
return nil, client.ErrorInvalidClientID
}
qt := r.quote(trustedPeerTableName)
var ids []string
_, err := ex.Select(&ids, fmt.Sprintf("SELECT trusted_client_id from %s where client_id = $1", qt), clientID)
if err != nil {
if err != sql.ErrNoRows {
return nil, err
}
return nil, nil
}
return ids, nil
}
func (r *clientRepo) SetTrustedPeers(tx repo.Transaction, clientID string, clientIDs []string) error {
ex := r.executor(tx)
qt := r.quote(trustedPeerTableName)
// First delete all existing rows
_, err := ex.Exec(fmt.Sprintf("DELETE from %s where client_id = $1", qt), clientID)
if err != nil {
return err
}
// Ensure that the client exists.
_, err = r.get(tx, clientID)
if err != nil {
return err
}
// Verify that all the clients are valid
for _, curID := range clientIDs {
_, err := r.get(tx, curID)
if err != nil {
return err
}
}
// Set the clients
rows := []interface{}{}
for _, curID := range clientIDs {
rows = append(rows, &trustedPeerModel{
ClientID: clientID,
TrustedClientID: curID,
})
}
err = ex.Insert(rows...)
if err != nil {
return err
}
return nil
}

View file

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"github.com/go-gorp/gorp" "github.com/go-gorp/gorp"
"github.com/rubenv/sql-migrate" migrate "github.com/rubenv/sql-migrate"
"github.com/coreos/dex/db/migrations" "github.com/coreos/dex/db/migrations"
) )

View file

@ -70,4 +70,10 @@ CREATE TABLE session_key (
expires_at bigint, expires_at bigint,
stale integer stale integer
); );
CREATE TABLE trusted_peers (
client_id text NOT NULL,
trusted_client_id text NOT NULL
);
` `

View file

@ -0,0 +1,5 @@
-- +migrate Up
CREATE TABLE IF NOT EXISTS "trusted_peers" (
"client_id" text not null,
"trusted_client_id" text not null,
primary key ("client_id", "trusted_client_id")) ;

View file

@ -72,5 +72,11 @@ var PostgresMigrations migrate.MigrationSource = &migrate.MemoryMigrationSource{
"-- +migrate Up\n\n-- This migration is a fix for a bug that allowed duplicate emails if they used different cases (see #338).\n-- When migrating, dex will not take the liberty of deleting rows for duplicate cases. Instead it will\n-- raise an exception and call for an admin to remove duplicates manually.\n\nCREATE OR REPLACE FUNCTION raise_exp() RETURNS VOID AS $$\nBEGIN\n RAISE EXCEPTION 'Found duplicate emails when using case insensitive comparision, cannot perform migration.';\nEND;\n$$ LANGUAGE plpgsql;\n\nSELECT LOWER(email),\n COUNT(email),\n CASE\n WHEN COUNT(email) > 1 THEN raise_exp()\n ELSE NULL\n END\nFROM authd_user\nGROUP BY LOWER(email);\n\nUPDATE authd_user SET email = LOWER(email);\n", "-- +migrate Up\n\n-- This migration is a fix for a bug that allowed duplicate emails if they used different cases (see #338).\n-- When migrating, dex will not take the liberty of deleting rows for duplicate cases. Instead it will\n-- raise an exception and call for an admin to remove duplicates manually.\n\nCREATE OR REPLACE FUNCTION raise_exp() RETURNS VOID AS $$\nBEGIN\n RAISE EXCEPTION 'Found duplicate emails when using case insensitive comparision, cannot perform migration.';\nEND;\n$$ LANGUAGE plpgsql;\n\nSELECT LOWER(email),\n COUNT(email),\n CASE\n WHEN COUNT(email) > 1 THEN raise_exp()\n ELSE NULL\n END\nFROM authd_user\nGROUP BY LOWER(email);\n\nUPDATE authd_user SET email = LOWER(email);\n",
}, },
}, },
{
Id: "0012_add_cross_client_authorizers.sql",
Up: []string{
"-- +migrate Up\nCREATE TABLE IF NOT EXISTS \"trusted_peers\" (\n \"client_id\" text not null,\n \"trusted_client_id\" text not null,\n primary key (\"client_id\", \"trusted_client_id\")) ;\n",
},
},
}, },
} }

View file

@ -248,7 +248,7 @@ func (r *userRepo) GetRemoteIdentities(tx repo.Transaction, userID string) ([]us
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
return nil, err return nil, err
} }
return nil, err return nil, nil
} }
if len(rims) == 0 { if len(rims) == 0 {
return nil, nil return nil, nil

View file

@ -316,7 +316,7 @@ func TestDBClientRepoAuthenticate(t *testing.T) {
cli := client.Client{ cli := client.Client{
Metadata: cm, Metadata: cm,
} }
cc, err := m.New(cli) cc, err := m.New(cli, nil)
if err != nil { if err != nil {
t.Fatalf(err.Error()) t.Fatalf(err.Error())
} }

View file

@ -37,7 +37,7 @@ func TestClientToken(t *testing.T) {
cli := client.Client{ cli := client.Client{
Metadata: clientMetadata, Metadata: clientMetadata,
} }
creds, err := clientManager.New(cli) creds, err := clientManager.New(cli, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to create client: %v", err) t.Fatalf("Failed to create client: %v", err)
} }

View file

@ -42,7 +42,7 @@ func (s *Server) handleClientRegistrationRequest(r *http.Request) (*oidc.ClientR
cli := client.Client{ cli := client.Client{
Metadata: clientMetadata, Metadata: clientMetadata,
} }
creds, err := s.ClientManager.New(cli) creds, err := s.ClientManager.New(cli, nil)
if err != nil { if err != nil {
log.Errorf("Failed to create new client identity: %v", err) log.Errorf("Failed to create new client identity: %v", err)
return nil, newAPIError(oauth2.ErrorServerError, "unable to save client metadata") return nil, newAPIError(oauth2.ErrorServerError, "unable to save client metadata")

View file

@ -87,7 +87,7 @@ func (c *clientResource) create(w http.ResponseWriter, r *http.Request) {
writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidClientMetadata, err.Error())) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidClientMetadata, err.Error()))
return return
} }
creds, err := c.manager.New(ci) creds, err := c.manager.New(ci, nil)
if err != nil { if err != nil {
log.Errorf("Failed creating client: %v", err) log.Errorf("Failed creating client: %v", err)