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

@@ -167,24 +167,31 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
return
}
if len(s.connectors) == 1 {
for id := range s.connectors {
connectors, e := s.storage.ListConnectors()
if e != nil {
s.logger.Errorf("Failed to get list of connectors: %v", err)
s.renderError(w, http.StatusInternalServerError, "Failed to retrieve connector list.")
return
}
if len(connectors) == 1 {
for _, c := range connectors {
// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
// on create the auth request.
http.Redirect(w, r, s.absPath("/auth", id)+"?req="+authReq.ID, http.StatusFound)
http.Redirect(w, r, s.absPath("/auth", c.ID)+"?req="+authReq.ID, http.StatusFound)
return
}
}
connectorInfos := make([]connectorInfo, len(s.connectors))
connectorInfos := make([]connectorInfo, len(connectors))
i := 0
for id, conn := range s.connectors {
for _, conn := range connectors {
connectorInfos[i] = connectorInfo{
ID: id,
Name: conn.DisplayName,
ID: conn.ID,
Name: conn.Name,
// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
// on create the auth request.
URL: s.absPath("/auth", id) + "?req=" + authReq.ID,
URL: s.absPath("/auth", conn.ID) + "?req=" + authReq.ID,
}
i++
}
@@ -196,10 +203,10 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
connID := mux.Vars(r)["connector"]
conn, ok := s.connectors[connID]
if !ok {
s.logger.Errorf("Failed to create authorization request.")
s.renderError(w, http.StatusBadRequest, "Requested resource does not exist.")
conn, err := s.getConnector(connID)
if err != nil {
s.logger.Errorf("Failed to create authorization request: %v", err)
s.renderError(w, http.StatusBadRequest, "Requested resource does not exist")
return
}
@@ -339,8 +346,9 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
return
}
conn, ok := s.connectors[authReq.ConnectorID]
if !ok {
conn, err := s.getConnector(authReq.ConnectorID)
if err != nil {
s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err)
s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.")
return
}
@@ -649,13 +657,14 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
// Ensure the connector supports refresh tokens.
//
// Connectors like `saml` do not implement RefreshConnector.
conn, ok := s.connectors[authCode.ConnectorID]
if !ok {
s.logger.Errorf("connector ID not found: %q", authCode.ConnectorID)
conn, err := s.getConnector(authCode.ConnectorID)
if err != nil {
s.logger.Errorf("connector with ID %q not found: %v", authCode.ConnectorID, err)
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
return false
}
_, ok = conn.Connector.(connector.RefreshConnector)
_, ok := conn.Connector.(connector.RefreshConnector)
if !ok {
return false
}
@@ -841,9 +850,9 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
scopes = requestedScopes
}
conn, ok := s.connectors[refresh.ConnectorID]
if !ok {
s.logger.Errorf("connector ID not found: %q", refresh.ConnectorID)
conn, err := s.getConnector(refresh.ConnectorID)
if err != nil {
s.logger.Errorf("connector with ID %q not found: %v", refresh.ConnectorID, err)
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
return
}

View File

@@ -2,11 +2,13 @@ package server
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"path"
"sync"
"sync/atomic"
"time"
@@ -17,14 +19,23 @@ import (
"github.com/gorilla/mux"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/connector/github"
"github.com/coreos/dex/connector/gitlab"
"github.com/coreos/dex/connector/ldap"
"github.com/coreos/dex/connector/mock"
"github.com/coreos/dex/connector/oidc"
"github.com/coreos/dex/connector/saml"
"github.com/coreos/dex/storage"
)
// Connector is a connector with metadata.
// LocalConnector is the local passwordDB connector which is an internal
// connector maintained by the server.
const LocalConnector = "local"
// Connector is a connector with resource version metadata.
type Connector struct {
ID string
DisplayName string
Connector connector.Connector
ResourceVersion string
Connector connector.Connector
}
// Config holds the server's configuration options.
@@ -36,9 +47,6 @@ type Config struct {
// The backing persistence layer.
Storage storage.Storage
// Strategies for federated identity.
Connectors []Connector
// Valid values are "code" to enable the code flow and "token" to enable the implicit
// flow. If no response types are supplied this value defaults to "code".
SupportedResponseTypes []string
@@ -60,8 +68,6 @@ type Config struct {
// If specified, the server will use this function for determining time.
Now func() time.Time
EnablePasswordDB bool
Web WebConfig
Logger logrus.FieldLogger
@@ -103,7 +109,9 @@ func value(val, defaultValue time.Duration) time.Duration {
type Server struct {
issuerURL url.URL
// Read-only map of connector IDs to connectors.
// mutex for the connectors map.
mu sync.Mutex
// Map of connector IDs to connectors.
connectors map[string]Connector
storage storage.Storage
@@ -137,17 +145,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
if err != nil {
return nil, fmt.Errorf("server: can't parse issuer URL")
}
if c.EnablePasswordDB {
c.Connectors = append(c.Connectors, Connector{
ID: "local",
DisplayName: "Email",
Connector: newPasswordDB(c.Storage),
})
}
if len(c.Connectors) == 0 {
return nil, errors.New("server: no connectors specified")
}
if c.Storage == nil {
return nil, errors.New("server: storage cannot be nil")
}
@@ -195,8 +193,21 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
logger: c.Logger,
}
for _, conn := range c.Connectors {
s.connectors[conn.ID] = conn
// Retrieves connector objects in backend storage. This list includes the static connectors
// defined in the ConfigMap and dynamic connectors retrieved from the storage.
storageConnectors, err := c.Storage.ListConnectors()
if err != nil {
return nil, fmt.Errorf("server: failed to list connector objects from storage: %v", err)
}
if len(storageConnectors) == 0 && len(s.connectors) == 0 {
return nil, errors.New("server: no connectors specified")
}
for _, conn := range storageConnectors {
if _, err := s.OpenConnector(conn); err != nil {
return nil, fmt.Errorf("server: Failed to open connector %s: %v", conn.ID, err)
}
}
r := mux.NewRouter()
@@ -362,3 +373,99 @@ func (s *Server) startGarbageCollection(ctx context.Context, frequency time.Dura
}()
return
}
// ConnectorConfig is a configuration that can open a connector.
type ConnectorConfig interface {
Open(logrus.FieldLogger) (connector.Connector, error)
}
// ConnectorsConfig variable provides an easy way to return a config struct
// depending on the connector type.
var ConnectorsConfig = map[string]func() ConnectorConfig{
"mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) },
"mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) },
"ldap": func() ConnectorConfig { return new(ldap.Config) },
"github": func() ConnectorConfig { return new(github.Config) },
"gitlab": func() ConnectorConfig { return new(gitlab.Config) },
"oidc": func() ConnectorConfig { return new(oidc.Config) },
"saml": func() ConnectorConfig { return new(saml.Config) },
// Keep around for backwards compatibility.
"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
}
// openConnector will parse the connector config and open the connector.
func openConnector(logger logrus.FieldLogger, conn storage.Connector) (connector.Connector, error) {
var c connector.Connector
f, ok := ConnectorsConfig[conn.Type]
if !ok {
return c, fmt.Errorf("unknown connector type %q", conn.Type)
}
connConfig := f()
if len(conn.Config) != 0 {
data := []byte(string(conn.Config))
if err := json.Unmarshal(data, connConfig); err != nil {
return c, fmt.Errorf("parse connector config: %v", err)
}
}
c, err := connConfig.Open(logger)
if err != nil {
return c, fmt.Errorf("failed to create connector %s: %v", conn.ID, err)
}
return c, nil
}
// OpenConnector updates server connector map with specified connector object.
func (s *Server) OpenConnector(conn storage.Connector) (Connector, error) {
var c connector.Connector
if conn.Type == LocalConnector {
c = newPasswordDB(s.storage)
} else {
var err error
c, err = openConnector(s.logger.WithField("connector", conn.Name), conn)
if err != nil {
return Connector{}, fmt.Errorf("failed to open connector: %v", err)
}
}
connector := Connector{
ResourceVersion: conn.ResourceVersion,
Connector: c,
}
s.mu.Lock()
s.connectors[conn.ID] = connector
s.mu.Unlock()
return connector, nil
}
// getConnector retrieves the connector object with the given id from the storage
// and updates the connector list for server if necessary.
func (s *Server) getConnector(id string) (Connector, error) {
storageConnector, err := s.storage.GetConnector(id)
if err != nil {
return Connector{}, fmt.Errorf("failed to get connector object from storage: %v", err)
}
var conn Connector
var ok bool
s.mu.Lock()
conn, ok = s.connectors[id]
s.mu.Unlock()
if !ok || storageConnector.ResourceVersion != conn.ResourceVersion {
// Connector object does not exist in server connectors map or
// has been updated in the storage. Need to get latest.
conn, err := s.OpenConnector(storageConnector)
if err != nil {
return Connector{}, fmt.Errorf("failed to open connector: %v", err)
}
return conn, nil
}
return conn, nil
}

View File

@@ -89,13 +89,6 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
config := Config{
Issuer: s.URL,
Storage: memory.New(logger),
Connectors: []Connector{
{
ID: "mock",
DisplayName: "Mock",
Connector: mock.NewCallbackConnector(logger),
},
},
Web: WebConfig{
Dir: filepath.Join(os.Getenv("GOPATH"), "src/github.com/coreos/dex/web"),
},
@@ -106,6 +99,16 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
}
s.URL = config.Issuer
connector := storage.Connector{
ID: "mock",
Type: "mockCallback",
Name: "Mock",
ResourceVersion: "1",
}
if err := config.Storage.CreateConnector(connector); err != nil {
t.Fatalf("create connector: %v", err)
}
var err error
if server, err = newServer(ctx, config, staticRotationStrategy(testKey)); err != nil {
t.Fatal(err)
@@ -416,29 +419,16 @@ func TestOAuth2CodeFlow(t *testing.T) {
defer cancel()
// Setup a dex server.
logger := &logrus.Logger{
Out: os.Stderr,
Formatter: &logrus.TextFormatter{DisableColors: true},
Level: logrus.DebugLevel,
}
httpServer, s := newTestServer(ctx, t, func(c *Config) {
c.Issuer = c.Issuer + "/non-root-path"
c.Now = now
c.IDTokensValidFor = idTokensValidFor
// Testing connector that redirects without interaction with
// the user.
conn = mock.NewCallbackConnector(logger).(*mock.Callback)
c.Connectors = []Connector{
{
ID: "mock",
DisplayName: "mock",
Connector: conn,
},
}
})
defer httpServer.Close()
mockConn := s.connectors["mock"]
conn = mockConn.Connector.(*mock.Callback)
// Query server's provider metadata.
p, err := oidc.NewProvider(ctx, httpServer.URL)
if err != nil {