Commit 35ea3d9a authored by Eric Chiang's avatar Eric Chiang

*: add ability to set and list connectors from admin API

closes #360
parent adbf4862
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/coreos/dex/client" "github.com/coreos/dex/client"
clientmanager "github.com/coreos/dex/client/manager" clientmanager "github.com/coreos/dex/client/manager"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/schema/adminschema" "github.com/coreos/dex/schema/adminschema"
"github.com/coreos/dex/user" "github.com/coreos/dex/user"
usermanager "github.com/coreos/dex/user/manager" usermanager "github.com/coreos/dex/user/manager"
...@@ -13,25 +14,27 @@ import ( ...@@ -13,25 +14,27 @@ import (
// AdminAPI provides the logic necessary to implement the Admin API. // AdminAPI provides the logic necessary to implement the Admin API.
type AdminAPI struct { type AdminAPI struct {
userManager *usermanager.UserManager userManager *usermanager.UserManager
userRepo user.UserRepo userRepo user.UserRepo
passwordInfoRepo user.PasswordInfoRepo passwordInfoRepo user.PasswordInfoRepo
clientRepo client.ClientRepo connectorConfigRepo connector.ConnectorConfigRepo
clientManager *clientmanager.ClientManager clientRepo client.ClientRepo
localConnectorID string 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 == "" { if localConnectorID == "" {
panic("must specify non-blank localConnectorID") panic("must specify non-blank localConnectorID")
} }
return &AdminAPI{ return &AdminAPI{
userManager: userManager, userManager: userManager,
userRepo: userRepo, userRepo: userRepo,
passwordInfoRepo: pwiRepo, passwordInfoRepo: pwiRepo,
clientRepo: clientRepo, clientRepo: clientRepo,
clientManager: clientManager, clientManager: clientManager,
localConnectorID: localConnectorID, connectorConfigRepo: connectorConfigRepo,
localConnectorID: localConnectorID,
} }
} }
...@@ -150,6 +153,14 @@ func (a *AdminAPI) CreateClient(req adminschema.ClientCreateRequest) (adminschem ...@@ -150,6 +153,14 @@ func (a *AdminAPI) CreateClient(req adminschema.ClientCreateRequest) (adminschem
}, nil }, 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 { func mapError(e error) error {
if mapped, ok := errorMap[e]; ok { if mapped, ok := errorMap[e]; ok {
return mapped(e) return mapped(e)
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
type testFixtures struct { type testFixtures struct {
ur user.UserRepo ur user.UserRepo
pwr user.PasswordInfoRepo pwr user.PasswordInfoRepo
ccr connector.ConnectorConfigRepo
cr client.ClientRepo cr client.ClientRepo
cm *clientmanager.ClientManager cm *clientmanager.ClientManager
mgr *manager.UserManager mgr *manager.UserManager
...@@ -63,7 +64,7 @@ func makeTestFixtures() *testFixtures { ...@@ -63,7 +64,7 @@ func makeTestFixtures() *testFixtures {
return repo return repo
}() }()
ccr := func() connector.ConnectorConfigRepo { f.ccr = func() connector.ConnectorConfigRepo {
c := []connector.ConnectorConfig{&connector.LocalConnectorConfig{ID: "local"}} c := []connector.ConnectorConfig{&connector.LocalConnectorConfig{ID: "local"}}
repo := db.NewConnectorConfigRepo(dbMap) repo := db.NewConnectorConfigRepo(dbMap)
if err := repo.Set(c); err != nil { if err := repo.Set(c); err != nil {
...@@ -72,9 +73,9 @@ func makeTestFixtures() *testFixtures { ...@@ -72,9 +73,9 @@ func makeTestFixtures() *testFixtures {
return repo 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.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 return f
} }
...@@ -252,3 +253,51 @@ func TestGetState(t *testing.T) { ...@@ -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)
}
}
}
...@@ -121,8 +121,9 @@ func main() { ...@@ -121,8 +121,9 @@ func main() {
userManager := manager.NewUserManager(userRepo, userManager := manager.NewUserManager(userRepo,
pwiRepo, connCfgRepo, db.TransactionFactory(dbc), manager.ManagerOptions{}) pwiRepo, connCfgRepo, db.TransactionFactory(dbc), manager.ManagerOptions{})
clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbc), clientmanager.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()...) kRepo, err := db.NewPrivateKeySetRepo(dbc, *useOldFormat, keySecrets.BytesSlice()...)
if err != nil { if err != nil {
log.Fatalf(err.Error()) log.Fatalf(err.Error())
......
...@@ -63,6 +63,7 @@ type ConnectorConfig interface { ...@@ -63,6 +63,7 @@ type ConnectorConfig interface {
type ConnectorConfigRepo interface { type ConnectorConfigRepo interface {
All() ([]ConnectorConfig, error) All() ([]ConnectorConfig, error)
GetConnectorByID(repo.Transaction, string) (ConnectorConfig, error) GetConnectorByID(repo.Transaction, string) (ConnectorConfig, error)
Set(cfgs []ConnectorConfig) error
} }
type IdentityProvider interface { type IdentityProvider interface {
......
...@@ -93,11 +93,12 @@ func makeAdminAPITestFixtures() *adminAPITestFixtures { ...@@ -93,11 +93,12 @@ func makeAdminAPITestFixtures() *adminAPITestFixtures {
return fmt.Sprintf("client_%v", hostport), nil return fmt.Sprintf("client_%v", hostport), nil
} }
cm := manager.NewClientManager(cr, db.TransactionFactory(dbMap), manager.ManagerOptions{SecretGenerator: secGen, ClientIDGenerator: clientIDGenerator}) cm := manager.NewClientManager(cr, db.TransactionFactory(dbMap), manager.ManagerOptions{SecretGenerator: secGen, ClientIDGenerator: clientIDGenerator})
ccr := db.NewConnectorConfigRepo(dbMap)
f.cr = cr f.cr = cr
f.ur = ur f.ur = ur
f.pwr = pwr 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.adSrv = server.NewAdminServer(f.adAPI, nil, adminAPITestSecret)
f.hSrv = httptest.NewServer(f.adSrv.HTTPHandler()) f.hSrv = httptest.NewServer(f.adSrv.HTTPHandler())
f.hc = &http.Client{ f.hc = &http.Client{
...@@ -272,6 +273,104 @@ func TestCreateAdmin(t *testing.T) { ...@@ -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) { func TestCreateClient(t *testing.T) {
mustParseURL := func(s string) *url.URL { mustParseURL := func(s string) *url.URL {
u, err := url.Parse(s) u, err := url.Parse(s)
......
package server package server
import ( import (
"bytes"
"encoding/json" "encoding/json"
"net/http" "net/http"
"path" "path"
...@@ -9,6 +10,7 @@ import ( ...@@ -9,6 +10,7 @@ import (
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/coreos/dex/admin" "github.com/coreos/dex/admin"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/pkg/log" "github.com/coreos/dex/pkg/log"
"github.com/coreos/dex/schema/adminschema" "github.com/coreos/dex/schema/adminschema"
"github.com/coreos/go-oidc/key" "github.com/coreos/go-oidc/key"
...@@ -24,6 +26,7 @@ var ( ...@@ -24,6 +26,7 @@ var (
AdminCreateEndpoint = addBasePath("/admin") AdminCreateEndpoint = addBasePath("/admin")
AdminGetStateEndpoint = addBasePath("/state") AdminGetStateEndpoint = addBasePath("/state")
AdminCreateClientEndpoint = addBasePath("/client") AdminCreateClientEndpoint = addBasePath("/client")
AdminConnectorsEndpoint = addBasePath("/connectors")
) )
// AdminServer serves the admin API. // AdminServer serves the admin API.
...@@ -53,6 +56,8 @@ func (s *AdminServer) HTTPHandler() http.Handler { ...@@ -53,6 +56,8 @@ func (s *AdminServer) HTTPHandler() http.Handler {
r.POST(AdminCreateClientEndpoint, s.createClient) r.POST(AdminCreateClientEndpoint, s.createClient)
r.Handler("GET", httpPathHealth, s.checker) r.Handler("GET", httpPathHealth, s.checker)
r.HandlerFunc("GET", httpPathDebugVars, health.ExpvarHandler) r.HandlerFunc("GET", httpPathDebugVars, health.ExpvarHandler)
r.PUT(AdminConnectorsEndpoint, s.setConnectors)
r.GET(AdminConnectorsEndpoint, s.getConnectors)
return authorizer(r, s.secret, httpPathHealth, httpPathDebugVars) return authorizer(r, s.secret, httpPathHealth, httpPathDebugVars)
} }
...@@ -130,6 +135,41 @@ func (s *AdminServer) createClient(w http.ResponseWriter, r *http.Request, ps ht ...@@ -130,6 +135,41 @@ func (s *AdminServer) createClient(w http.ResponseWriter, r *http.Request, ps ht
writeResponseWithBody(w, http.StatusOK, &resp) 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) { func (s *AdminServer) writeError(w http.ResponseWriter, err error) {
log.Errorf("Error calling admin API: %v: ", err) log.Errorf("Error calling admin API: %v: ", err)
if adminErr, ok := err.(admin.Error); ok { if adminErr, ok := err.(admin.Error); ok {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment