Compare commits

...

2 Commits

Author SHA1 Message Date
bcmmbaga
7f498d8022 Preserve Dex connector config on partial updates 2026-05-05 00:18:17 +03:00
bcmmbaga
e57a123971 Use Entra oid claim as Dex user ID 2026-05-04 19:18:50 +03:00

View File

@@ -89,21 +89,29 @@ func (p *Provider) ListConnectors(ctx context.Context) ([]*ConnectorConfig, erro
} }
// UpdateConnector updates an existing connector in Dex storage. // UpdateConnector updates an existing connector in Dex storage.
// It merges incoming updates with existing values to prevent data loss on partial updates. // It overlays user-mutable config fields (issuer, clientID, clientSecret,
// redirectURI) onto the stored connector config, and updates the connector name
// when cfg.Name is set. Empty fields on cfg leave stored values unchanged, so
// partial updates preserve create-time defaults such as scopes, claimMapping,
// and userIDKey.
func (p *Provider) UpdateConnector(ctx context.Context, cfg *ConnectorConfig) error { func (p *Provider) UpdateConnector(ctx context.Context, cfg *ConnectorConfig) error {
if err := p.storage.UpdateConnector(ctx, cfg.ID, func(old storage.Connector) (storage.Connector, error) { if err := p.storage.UpdateConnector(ctx, cfg.ID, func(old storage.Connector) (storage.Connector, error) {
oldCfg, err := p.parseStorageConnector(old) configData, err := overlayConnectorConfig(old.Config, cfg)
if err != nil { if err != nil {
return storage.Connector{}, fmt.Errorf("failed to parse existing connector: %w", err) return storage.Connector{}, fmt.Errorf("failed to overlay connector config: %w", err)
} }
mergeConnectorConfig(cfg, oldCfg) name := cfg.Name
if name == "" {
storageConn, err := p.buildStorageConnector(cfg) name = old.Name
if err != nil {
return storage.Connector{}, fmt.Errorf("failed to build connector: %w", err)
} }
return storageConn, nil
return storage.Connector{
ID: cfg.ID,
Type: old.Type,
Name: name,
Config: configData,
}, nil
}); err != nil { }); err != nil {
return fmt.Errorf("failed to update connector: %w", err) return fmt.Errorf("failed to update connector: %w", err)
} }
@@ -112,23 +120,27 @@ func (p *Provider) UpdateConnector(ctx context.Context, cfg *ConnectorConfig) er
return nil return nil
} }
// mergeConnectorConfig preserves existing values for empty fields in the update. // overlayConnectorConfig writes only the user-mutable fields onto the existing
func mergeConnectorConfig(cfg, oldCfg *ConnectorConfig) { // stored config, preserving every other field (scopes, claimMapping, userIDKey,
if cfg.ClientSecret == "" { // insecure flags, etc.). Empty fields on cfg leave the existing value alone.
cfg.ClientSecret = oldCfg.ClientSecret func overlayConnectorConfig(oldConfig []byte, cfg *ConnectorConfig) ([]byte, error) {
var m map[string]any
if err := decodeConnectorConfig(oldConfig, &m); err != nil {
return nil, err
} }
if cfg.RedirectURI == "" { if cfg.Issuer != "" {
cfg.RedirectURI = oldCfg.RedirectURI m["issuer"] = cfg.Issuer
} }
if cfg.Issuer == "" && cfg.Type == oldCfg.Type { if cfg.ClientID != "" {
cfg.Issuer = oldCfg.Issuer m["clientID"] = cfg.ClientID
} }
if cfg.ClientID == "" { if cfg.ClientSecret != "" {
cfg.ClientID = oldCfg.ClientID m["clientSecret"] = cfg.ClientSecret
} }
if cfg.Name == "" { if cfg.RedirectURI != "" {
cfg.Name = oldCfg.Name m["redirectURI"] = cfg.RedirectURI
} }
return encodeConnectorConfig(m)
} }
// DeleteConnector removes a connector from Dex storage. // DeleteConnector removes a connector from Dex storage.
@@ -216,6 +228,7 @@ func buildOIDCConnectorConfig(cfg *ConnectorConfig, redirectURI string) ([]byte,
oidcConfig["getUserInfo"] = true oidcConfig["getUserInfo"] = true
case "entra": case "entra":
oidcConfig["claimMapping"] = map[string]string{"email": "preferred_username"} oidcConfig["claimMapping"] = map[string]string{"email": "preferred_username"}
oidcConfig["userIDKey"] = "oid"
case "okta": case "okta":
oidcConfig["scopes"] = []string{"openid", "profile", "email", "groups"} oidcConfig["scopes"] = []string{"openid", "profile", "email", "groups"}
case "pocketid": case "pocketid":