forked from mystiq/dex
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:
parent
546463adcc
commit
f9dbc8a3d2
15 changed files with 119 additions and 11 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
75
db/client.go
75
db/client.go
|
@ -16,7 +16,8 @@ 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
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
5
db/migrations/0012_add_cross_client_authorizers.sql
Normal file
5
db/migrations/0012_add_cross_client_authorizers.sql
Normal 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")) ;
|
|
@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue