server: account for dynamically changing connector object in storage.

This commit is contained in:
rithu john
2017-04-17 15:41:41 -07:00
parent 2b8caf9b39
commit 8c9c2518f5
10 changed files with 400 additions and 137 deletions

View File

@@ -628,6 +628,10 @@ func testConnectorCRUD(t *testing.T, s storage.Storage) {
c1.Type = "oidc"
getAndCompare(id1, c1)
if _, err := s.ListConnectors(); err != nil {
t.Fatalf("failed to list connectors: %v", err)
}
if err := s.DeleteConnector(c1.ID); err != nil {
t.Fatalf("failed to delete connector: %v", err)
}

View File

@@ -190,3 +190,94 @@ func TestStaticPasswords(t *testing.T) {
}
}
}
func TestStaticConnectors(t *testing.T) {
logger := &logrus.Logger{
Out: os.Stderr,
Formatter: &logrus.TextFormatter{DisableColors: true},
Level: logrus.DebugLevel,
}
backing := New(logger)
config1 := []byte(`{"issuer": "https://accounts.google.com"}`)
config2 := []byte(`{"host": "ldap.example.com:636"}`)
config3 := []byte(`{"issuer": "https://example.com"}`)
c1 := storage.Connector{ID: storage.NewID(), Type: "oidc", Name: "oidc", ResourceVersion: "1", Config: config1}
c2 := storage.Connector{ID: storage.NewID(), Type: "ldap", Name: "ldap", ResourceVersion: "1", Config: config2}
c3 := storage.Connector{ID: storage.NewID(), Type: "saml", Name: "saml", ResourceVersion: "1", Config: config3}
backing.CreateConnector(c1)
s := storage.WithStaticConnectors(backing, []storage.Connector{c2})
tests := []struct {
name string
action func() error
wantErr bool
}{
{
name: "get connector from static storage",
action: func() error {
_, err := s.GetConnector(c2.ID)
return err
},
},
{
name: "get connector from backing storage",
action: func() error {
_, err := s.GetConnector(c1.ID)
return err
},
},
{
name: "update static connector",
action: func() error {
updater := func(c storage.Connector) (storage.Connector, error) {
c.Name = "New"
return c, nil
}
return s.UpdateConnector(c2.ID, updater)
},
wantErr: true,
},
{
name: "update non-static connector",
action: func() error {
updater := func(c storage.Connector) (storage.Connector, error) {
c.Name = "New"
return c, nil
}
return s.UpdateConnector(c1.ID, updater)
},
},
{
name: "list connectors",
action: func() error {
connectors, err := s.ListConnectors()
if err != nil {
return err
}
if n := len(connectors); n != 2 {
return fmt.Errorf("expected 2 connectors got %d", n)
}
return nil
},
},
{
name: "create connector",
action: func() error {
return s.CreateConnector(c3)
},
},
}
for _, tc := range tests {
err := tc.action()
if err != nil && !tc.wantErr {
t.Errorf("%s: %v", tc.name, err)
}
if err == nil && tc.wantErr {
t.Errorf("%s: expected error, didn't get one", tc.name)
}
}
}

View File

@@ -150,3 +150,73 @@ func (s staticPasswordsStorage) UpdatePassword(email string, updater func(old Pa
}
return s.Storage.UpdatePassword(email, updater)
}
// staticConnectorsStorage represents a storage with read-only set of connectors.
type staticConnectorsStorage struct {
Storage
// A read-only set of connectors.
connectors []Connector
connectorsByID map[string]Connector
}
// WithStaticConnectors returns a storage with a read-only set of Connectors. Write actions,
// such as updating existing Connectors, will fail.
func WithStaticConnectors(s Storage, staticConnectors []Connector) Storage {
connectorsByID := make(map[string]Connector, len(staticConnectors))
for _, c := range staticConnectors {
connectorsByID[c.ID] = c
}
return staticConnectorsStorage{s, staticConnectors, connectorsByID}
}
func (s staticConnectorsStorage) isStatic(id string) bool {
_, ok := s.connectorsByID[id]
return ok
}
func (s staticConnectorsStorage) GetConnector(id string) (Connector, error) {
if connector, ok := s.connectorsByID[id]; ok {
return connector, nil
}
return s.Storage.GetConnector(id)
}
func (s staticConnectorsStorage) ListConnectors() ([]Connector, error) {
connectors, err := s.Storage.ListConnectors()
if err != nil {
return nil, err
}
n := 0
for _, connector := range connectors {
// If an entry has the same id as those provided in the static
// values, prefer the static value.
if !s.isStatic(connector.ID) {
connectors[n] = connector
n++
}
}
return append(connectors[:n], s.connectors...), nil
}
func (s staticConnectorsStorage) CreateConnector(c Connector) error {
if s.isStatic(c.ID) {
return errors.New("static connectors: read-only cannot create connector")
}
return s.Storage.CreateConnector(c)
}
func (s staticConnectorsStorage) DeleteConnector(id string) error {
if s.isStatic(id) {
return errors.New("static connectors: read-only cannot delete connector")
}
return s.Storage.DeleteConnector(id)
}
func (s staticConnectorsStorage) UpdateConnector(id string, updater func(old Connector) (Connector, error)) error {
if s.isStatic(id) {
return errors.New("static connectors: read-only cannot update connector")
}
return s.Storage.UpdateConnector(id, updater)
}

View File

@@ -298,17 +298,17 @@ type Password struct {
// Connector is an object that contains the metadata about connectors used to login to Dex.
type Connector struct {
// ID that will uniquely identify the connector object.
ID string
ID string `json:"id"`
// The Type of the connector. E.g. 'oidc' or 'ldap'
Type string
Type string `json:"type"`
// The Name of the connector that is used when displaying it to the end user.
Name string
Name string `json:"name"`
// ResourceVersion is the static versioning used to keep track of dynamic configuration
// changes to the connector object made by the API calls.
ResourceVersion string
ResourceVersion string `json:"resourceVersion"`
// Config holds all the configuration information specific to the connector type. Since there
// no generic struct we can use for this purpose, it is stored as a byte stream.
Config []byte
Config []byte `json:"email"`
}
// VerificationKey is a rotated signing key which can still be used to verify