From 35ea3d9ae128f80e07909109c35d8ba05e8a5a85 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Thu, 26 May 2016 14:42:15 -0700 Subject: [PATCH] *: add ability to set and list connectors from admin API closes #360 --- admin/api.go | 37 ++++++++----- admin/api_test.go | 55 +++++++++++++++++- cmd/dex-overlord/main.go | 3 +- connector/interface.go | 1 + integration/admin_api_test.go | 101 +++++++++++++++++++++++++++++++++- server/admin.go | 40 ++++++++++++++ 6 files changed, 219 insertions(+), 18 deletions(-) diff --git a/admin/api.go b/admin/api.go index d27bfe58..1e782d2b 100644 --- a/admin/api.go +++ b/admin/api.go @@ -6,6 +6,7 @@ import ( "github.com/coreos/dex/client" clientmanager "github.com/coreos/dex/client/manager" + "github.com/coreos/dex/connector" "github.com/coreos/dex/schema/adminschema" "github.com/coreos/dex/user" usermanager "github.com/coreos/dex/user/manager" @@ -13,25 +14,27 @@ import ( // AdminAPI provides the logic necessary to implement the Admin API. type AdminAPI struct { - userManager *usermanager.UserManager - userRepo user.UserRepo - passwordInfoRepo user.PasswordInfoRepo - clientRepo client.ClientRepo - clientManager *clientmanager.ClientManager - localConnectorID string + userManager *usermanager.UserManager + userRepo user.UserRepo + passwordInfoRepo user.PasswordInfoRepo + connectorConfigRepo connector.ConnectorConfigRepo + clientRepo client.ClientRepo + clientManager *clientmanager.ClientManager + localConnectorID string } -func NewAdminAPI(userRepo user.UserRepo, pwiRepo user.PasswordInfoRepo, clientRepo client.ClientRepo, userManager *usermanager.UserManager, clientManager *clientmanager.ClientManager, localConnectorID string) *AdminAPI { +func NewAdminAPI(userRepo user.UserRepo, pwiRepo user.PasswordInfoRepo, clientRepo client.ClientRepo, connectorConfigRepo connector.ConnectorConfigRepo, userManager *usermanager.UserManager, clientManager *clientmanager.ClientManager, localConnectorID string) *AdminAPI { if localConnectorID == "" { panic("must specify non-blank localConnectorID") } return &AdminAPI{ - userManager: userManager, - userRepo: userRepo, - passwordInfoRepo: pwiRepo, - clientRepo: clientRepo, - clientManager: clientManager, - localConnectorID: localConnectorID, + userManager: userManager, + userRepo: userRepo, + passwordInfoRepo: pwiRepo, + clientRepo: clientRepo, + clientManager: clientManager, + connectorConfigRepo: connectorConfigRepo, + localConnectorID: localConnectorID, } } @@ -150,6 +153,14 @@ func (a *AdminAPI) CreateClient(req adminschema.ClientCreateRequest) (adminschem }, nil } +func (a *AdminAPI) SetConnectors(connectorConfigs []connector.ConnectorConfig) error { + return a.connectorConfigRepo.Set(connectorConfigs) +} + +func (a *AdminAPI) GetConnectors() ([]connector.ConnectorConfig, error) { + return a.connectorConfigRepo.All() +} + func mapError(e error) error { if mapped, ok := errorMap[e]; ok { return mapped(e) diff --git a/admin/api_test.go b/admin/api_test.go index 327d7fe8..2a55003a 100644 --- a/admin/api_test.go +++ b/admin/api_test.go @@ -17,6 +17,7 @@ import ( type testFixtures struct { ur user.UserRepo pwr user.PasswordInfoRepo + ccr connector.ConnectorConfigRepo cr client.ClientRepo cm *clientmanager.ClientManager mgr *manager.UserManager @@ -63,7 +64,7 @@ func makeTestFixtures() *testFixtures { return repo }() - ccr := func() connector.ConnectorConfigRepo { + f.ccr = func() connector.ConnectorConfigRepo { c := []connector.ConnectorConfig{&connector.LocalConnectorConfig{ID: "local"}} repo := db.NewConnectorConfigRepo(dbMap) if err := repo.Set(c); err != nil { @@ -72,9 +73,9 @@ func makeTestFixtures() *testFixtures { return repo }() - f.mgr = manager.NewUserManager(f.ur, f.pwr, ccr, db.TransactionFactory(dbMap), manager.ManagerOptions{}) + f.mgr = manager.NewUserManager(f.ur, f.pwr, f.ccr, db.TransactionFactory(dbMap), manager.ManagerOptions{}) f.cm = clientmanager.NewClientManager(f.cr, db.TransactionFactory(dbMap), clientmanager.ManagerOptions{}) - f.adAPI = NewAdminAPI(f.ur, f.pwr, f.cr, f.mgr, f.cm, "local") + f.adAPI = NewAdminAPI(f.ur, f.pwr, f.cr, f.ccr, f.mgr, f.cm, "local") return f } @@ -252,3 +253,51 @@ func TestGetState(t *testing.T) { } } } + +func TestSetConnectors(t *testing.T) { + tests := []struct { + connectors []connector.ConnectorConfig + }{ + { + connectors: []connector.ConnectorConfig{ + &connector.GitHubConnectorConfig{ + ID: "github", + ClientID: "foo", + ClientSecret: "bar", + }, + }, + }, + { + connectors: []connector.ConnectorConfig{ + &connector.GitHubConnectorConfig{ + ID: "github", + ClientID: "foo", + ClientSecret: "bar", + }, + &connector.LocalConnectorConfig{ + ID: "local", + }, + &connector.BitbucketConnectorConfig{ + ID: "bitbucket", + ClientID: "foo", + ClientSecret: "bar", + }, + }, + }, + } + for i, tt := range tests { + f := makeTestFixtures() + if err := f.adAPI.SetConnectors(tt.connectors); err != nil { + t.Errorf("case %d: failed to set connectors: %v", i, err) + continue + } + got, err := f.adAPI.GetConnectors() + if err != nil { + t.Errorf("case %d: failed to get connectors: %v", i, err) + continue + } + if diff := pretty.Compare(tt.connectors, got); diff != "" { + t.Errorf("case %d: Compare(want, got) = %v", i, diff) + } + } +} diff --git a/cmd/dex-overlord/main.go b/cmd/dex-overlord/main.go index ba5b56b5..fbe0e45e 100644 --- a/cmd/dex-overlord/main.go +++ b/cmd/dex-overlord/main.go @@ -121,8 +121,9 @@ func main() { userManager := manager.NewUserManager(userRepo, pwiRepo, connCfgRepo, db.TransactionFactory(dbc), manager.ManagerOptions{}) clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbc), clientmanager.ManagerOptions{}) + connectorConfigRepo := db.NewConnectorConfigRepo(dbc) - adminAPI := admin.NewAdminAPI(userRepo, pwiRepo, clientRepo, userManager, clientManager, *localConnectorID) + adminAPI := admin.NewAdminAPI(userRepo, pwiRepo, clientRepo, connectorConfigRepo, userManager, clientManager, *localConnectorID) kRepo, err := db.NewPrivateKeySetRepo(dbc, *useOldFormat, keySecrets.BytesSlice()...) if err != nil { log.Fatalf(err.Error()) diff --git a/connector/interface.go b/connector/interface.go index e0348142..4996c249 100644 --- a/connector/interface.go +++ b/connector/interface.go @@ -63,6 +63,7 @@ type ConnectorConfig interface { type ConnectorConfigRepo interface { All() ([]ConnectorConfig, error) GetConnectorByID(repo.Transaction, string) (ConnectorConfig, error) + Set(cfgs []ConnectorConfig) error } type IdentityProvider interface { diff --git a/integration/admin_api_test.go b/integration/admin_api_test.go index 7c9eb847..ada83054 100644 --- a/integration/admin_api_test.go +++ b/integration/admin_api_test.go @@ -93,11 +93,12 @@ func makeAdminAPITestFixtures() *adminAPITestFixtures { return fmt.Sprintf("client_%v", hostport), nil } cm := manager.NewClientManager(cr, db.TransactionFactory(dbMap), manager.ManagerOptions{SecretGenerator: secGen, ClientIDGenerator: clientIDGenerator}) + ccr := db.NewConnectorConfigRepo(dbMap) f.cr = cr f.ur = ur f.pwr = pwr - f.adAPI = admin.NewAdminAPI(ur, pwr, cr, um, cm, "local") + f.adAPI = admin.NewAdminAPI(ur, pwr, cr, ccr, um, cm, "local") f.adSrv = server.NewAdminServer(f.adAPI, nil, adminAPITestSecret) f.hSrv = httptest.NewServer(f.adSrv.HTTPHandler()) f.hc = &http.Client{ @@ -272,6 +273,104 @@ func TestCreateAdmin(t *testing.T) { } } +func TestConnectors(t *testing.T) { + tests := []struct { + req adminschema.ConnectorsSetRequest + want adminschema.ConnectorsGetResponse + wantErr bool + }{ + { + req: adminschema.ConnectorsSetRequest{ + Connectors: []interface{}{ + map[string]string{ + "type": "local", + "id": "local", + }, + }, + }, + want: adminschema.ConnectorsGetResponse{ + Connectors: []interface{}{ + map[string]string{ + "id": "local", + }, + }, + }, + wantErr: false, + }, + { + req: adminschema.ConnectorsSetRequest{ + Connectors: []interface{}{ + map[string]string{ + "type": "github", + "id": "github", + "clientID": "foo", + "clientSecret": "bar", + }, + map[string]interface{}{ + "type": "oidc", + "id": "oidc", + "issuerURL": "https://auth.example.com", + "clientID": "foo", + "clientSecret": "bar", + "trustedEmailProvider": true, + }, + }, + }, + want: adminschema.ConnectorsGetResponse{ + Connectors: []interface{}{ + map[string]string{ + "id": "github", + "clientID": "foo", + "clientSecret": "bar", + }, + map[string]interface{}{ + "id": "oidc", + "issuerURL": "https://auth.example.com", + "clientID": "foo", + "clientSecret": "bar", + "trustedEmailProvider": true, + }, + }, + }, + wantErr: false, + }, + { + // Missing "type" argument + req: adminschema.ConnectorsSetRequest{ + Connectors: []interface{}{ + map[string]string{ + "id": "local", + }, + }, + }, + wantErr: true, + }, + } + + for i, tt := range tests { + f := makeAdminAPITestFixtures() + if err := f.adClient.Connectors.Set(&tt.req).Do(); err != nil { + if !tt.wantErr { + t.Errorf("case %d: failed to set connectors: %v", i, err) + } + continue + } + if tt.wantErr { + t.Errorf("case %d: expected error setting connectors", i) + continue + } + + resp, err := f.adClient.Connectors.Get().Do() + if err != nil { + t.Errorf("case %d: failed toget connectors: %v", i, err) + continue + } + if diff := pretty.Compare(tt.want, resp); diff != "" { + t.Errorf("case %d: Compare(want, got) = %s", i, diff) + } + } +} + func TestCreateClient(t *testing.T) { mustParseURL := func(s string) *url.URL { u, err := url.Parse(s) diff --git a/server/admin.go b/server/admin.go index 43d29c4f..bcbd05d5 100644 --- a/server/admin.go +++ b/server/admin.go @@ -1,6 +1,7 @@ package server import ( + "bytes" "encoding/json" "net/http" "path" @@ -9,6 +10,7 @@ import ( "github.com/julienschmidt/httprouter" "github.com/coreos/dex/admin" + "github.com/coreos/dex/connector" "github.com/coreos/dex/pkg/log" "github.com/coreos/dex/schema/adminschema" "github.com/coreos/go-oidc/key" @@ -24,6 +26,7 @@ var ( AdminCreateEndpoint = addBasePath("/admin") AdminGetStateEndpoint = addBasePath("/state") AdminCreateClientEndpoint = addBasePath("/client") + AdminConnectorsEndpoint = addBasePath("/connectors") ) // AdminServer serves the admin API. @@ -53,6 +56,8 @@ func (s *AdminServer) HTTPHandler() http.Handler { r.POST(AdminCreateClientEndpoint, s.createClient) r.Handler("GET", httpPathHealth, s.checker) r.HandlerFunc("GET", httpPathDebugVars, health.ExpvarHandler) + r.PUT(AdminConnectorsEndpoint, s.setConnectors) + r.GET(AdminConnectorsEndpoint, s.getConnectors) return authorizer(r, s.secret, httpPathHealth, httpPathDebugVars) } @@ -130,6 +135,41 @@ func (s *AdminServer) createClient(w http.ResponseWriter, r *http.Request, ps ht writeResponseWithBody(w, http.StatusOK, &resp) } +func (s *AdminServer) setConnectors(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + var req struct { + Connectors json.RawMessage `json:"connectors"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeInvalidRequest(w, "cannot parse JSON body") + return + } + + connectorConfigs, err := connector.ReadConfigs(bytes.NewReader([]byte(req.Connectors))) + if err != nil { + writeInvalidRequest(w, "cannot parse JSON body") + return + } + if err := s.adminAPI.SetConnectors(connectorConfigs); err != nil { + s.writeError(w, err) + return + } + w.WriteHeader(http.StatusOK) +} + +func (s *AdminServer) getConnectors(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + connectorConfigs, err := s.adminAPI.GetConnectors() + if err != nil { + s.writeError(w, err) + return + } + var resp adminschema.ConnectorsGetResponse + resp.Connectors = make([]interface{}, len(connectorConfigs)) + for i, connectorConfig := range connectorConfigs { + resp.Connectors[i] = connectorConfig + } + writeResponseWithBody(w, http.StatusOK, &resp) +} + func (s *AdminServer) writeError(w http.ResponseWriter, err error) { log.Errorf("Error calling admin API: %v: ", err) if adminErr, ok := err.(admin.Error); ok {