server: account for dynamically changing connector object in storage.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user