Merge pull request #449 from ericchiang/add-connectors-to-api

Set and list connectors from admin API
This commit is contained in:
Eric Chiang 2016-06-01 10:51:30 -07:00
commit 4440b3a085
15 changed files with 889 additions and 311 deletions

View file

@ -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)

View file

@ -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)
}
}
}

View file

@ -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())

View file

@ -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 {

View file

@ -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)

View file

@ -34,7 +34,7 @@ __Version:__ v1
redirectURIs: [
string
],
secret: string
secret: string // The client secret. Ignored in client create requests.
}
```
@ -58,6 +58,38 @@ Upon successful registration, an ID and secret is assigned to the client.
}
```
### Connector
An object which describes a federating identity strategy. For documentation see Documentation/connectors-configuration.md. Since different connectors expect different object fields the scheme is omitted here.
```
```
### ConnectorsGetResponse
A list of all connector responses.
```
{
connectors: [
Connector
]
}
```
### ConnectorsSetRequest
A request to set all the connectors in the dex database.
```
{
connectors: [
Connector
]
}
```
### State
@ -150,6 +182,50 @@ Upon successful registration, an ID and secret is assigned to the client.
| default | Unexpected error | |
### GET /connectors
> __Summary__
> Get Connectors
> __Description__
> Return a list of the connectors for the dex system.
> __Responses__
> |Code|Description|Type|
|:-----|:-----|:-----|
| 200 | | [ConnectorsGetResponse](#connectorsgetresponse) |
| default | Unexpected error | |
### PUT /connectors
> __Summary__
> Set Connectors
> __Description__
> Set the list of connectors for the dex system, overwriting all previous connectors. A 200 status code indicates the action was successful.
> __Parameters__
> |Name|Located in|Description|Required|Type|
|:-----|:-----|:-----|:-----|:-----|
| | body | | Yes | [ConnectorsSetRequest](#connectorssetrequest) |
> __Responses__
> |Code|Description|Type|
|:-----|:-----|:-----|
| default | Unexpected error | |
### GET /state
> __Summary__

View file

@ -47,6 +47,7 @@ func New(client *http.Client) (*Service, error) {
s := &Service{client: client, BasePath: basePath}
s.Admin = NewAdminService(s)
s.Client = NewClientService(s)
s.Connectors = NewConnectorsService(s)
s.State = NewStateService(s)
return s, nil
}
@ -59,6 +60,8 @@ type Service struct {
Client *ClientService
Connectors *ConnectorsService
State *StateService
}
@ -80,6 +83,15 @@ type ClientService struct {
s *Service
}
func NewConnectorsService(s *Service) *ConnectorsService {
rs := &ConnectorsService{s: s}
return rs
}
type ConnectorsService struct {
s *Service
}
func NewStateService(s *Service) *StateService {
rs := &StateService{s: s}
return rs
@ -134,6 +146,7 @@ type Client struct {
// 2005. ) (Simple String Comparison).
RedirectURIs []string `json:"redirectURIs,omitempty"`
// Secret: The client secret. Ignored in client create requests.
Secret string `json:"secret,omitempty"`
}
@ -145,6 +158,16 @@ type ClientCreateResponse struct {
Client *Client `json:"client,omitempty"`
}
type Connector interface{}
type ConnectorsGetResponse struct {
Connectors []interface{} `json:"connectors,omitempty"`
}
type ConnectorsSetRequest struct {
Connectors []interface{} `json:"connectors,omitempty"`
}
type State struct {
AdminUserCreated bool `json:"AdminUserCreated,omitempty"`
}
@ -359,6 +382,128 @@ func (c *ClientCreateCall) Do() (*ClientCreateResponse, error) {
}
// method id "dex.admin.Connector.Get":
type ConnectorsGetCall struct {
s *Service
opt_ map[string]interface{}
}
// Get: Return a list of the connectors for the dex system.
func (r *ConnectorsService) Get() *ConnectorsGetCall {
c := &ConnectorsGetCall{s: r.s, opt_: make(map[string]interface{})}
return c
}
// Fields allows partial responses to be retrieved.
// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
// for more information.
func (c *ConnectorsGetCall) Fields(s ...googleapi.Field) *ConnectorsGetCall {
c.opt_["fields"] = googleapi.CombineFields(s)
return c
}
func (c *ConnectorsGetCall) Do() (*ConnectorsGetResponse, error) {
var body io.Reader = nil
params := make(url.Values)
params.Set("alt", "json")
if v, ok := c.opt_["fields"]; ok {
params.Set("fields", fmt.Sprintf("%v", v))
}
urls := googleapi.ResolveRelative(c.s.BasePath, "connectors")
urls += "?" + params.Encode()
req, _ := http.NewRequest("GET", urls, body)
googleapi.SetOpaque(req.URL)
req.Header.Set("User-Agent", "google-api-go-client/0.5")
res, err := c.s.client.Do(req)
if err != nil {
return nil, err
}
defer googleapi.CloseBody(res)
if err := googleapi.CheckResponse(res); err != nil {
return nil, err
}
var ret *ConnectorsGetResponse
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
return nil, err
}
return ret, nil
// {
// "description": "Return a list of the connectors for the dex system.",
// "httpMethod": "GET",
// "id": "dex.admin.Connector.Get",
// "path": "connectors",
// "response": {
// "$ref": "ConnectorsGetResponse"
// }
// }
}
// method id "dex.admin.Connector.Set":
type ConnectorsSetCall struct {
s *Service
connectorssetrequest *ConnectorsSetRequest
opt_ map[string]interface{}
}
// Set: Set the list of connectors for the dex system, overwriting all
// previous connectors. A 200 status code indicates the action was
// successful.
func (r *ConnectorsService) Set(connectorssetrequest *ConnectorsSetRequest) *ConnectorsSetCall {
c := &ConnectorsSetCall{s: r.s, opt_: make(map[string]interface{})}
c.connectorssetrequest = connectorssetrequest
return c
}
// Fields allows partial responses to be retrieved.
// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
// for more information.
func (c *ConnectorsSetCall) Fields(s ...googleapi.Field) *ConnectorsSetCall {
c.opt_["fields"] = googleapi.CombineFields(s)
return c
}
func (c *ConnectorsSetCall) Do() error {
var body io.Reader = nil
body, err := googleapi.WithoutDataWrapper.JSONReader(c.connectorssetrequest)
if err != nil {
return err
}
ctype := "application/json"
params := make(url.Values)
params.Set("alt", "json")
if v, ok := c.opt_["fields"]; ok {
params.Set("fields", fmt.Sprintf("%v", v))
}
urls := googleapi.ResolveRelative(c.s.BasePath, "connectors")
urls += "?" + params.Encode()
req, _ := http.NewRequest("PUT", urls, body)
googleapi.SetOpaque(req.URL)
req.Header.Set("Content-Type", ctype)
req.Header.Set("User-Agent", "google-api-go-client/0.5")
res, err := c.s.client.Do(req)
if err != nil {
return err
}
defer googleapi.CloseBody(res)
if err := googleapi.CheckResponse(res); err != nil {
return err
}
return nil
// {
// "description": "Set the list of connectors for the dex system, overwriting all previous connectors. A 200 status code indicates the action was successful.",
// "httpMethod": "PUT",
// "id": "dex.admin.Connector.Set",
// "path": "connectors",
// "request": {
// "$ref": "ConnectorsSetRequest"
// }
// }
}
// method id "dex.admin.State.Get":
type StateGetCall struct {

View file

@ -1,5 +1,4 @@
package adminschema
//
// This file is automatically generated by schema/generator
//
@ -28,70 +27,66 @@ const DiscoveryJSON = `{
"parameters": {},
"auth": {},
"schemas": {
"Admin": {
"id": "Admin",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"State": {
"id": "State",
"type": "object",
"properties": {
"AdminUserCreated": {
"type": "boolean"
}
}
},
"Client": {
"id": "Client",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The client ID. Ignored in client create requests."
},
"secret": {
"type": "string",
"description": "The client secret. Ignored in client create requests."
},
"secret": {
"type": "string",
"format": "byte"
},
"isAdmin": {
"type": "boolean"
},
"redirectURIs": {
"type": "array",
"items": {
"type": "string"
},
"description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)."
},
"clientName": {
"type": "string",
"description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"logoURI": {
"type": "string",
"description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"clientURI": {
"type": "string",
"description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
}
"Admin": {
"id": "Admin",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
}
},
}
},
"State": {
"id": "State",
"type": "object",
"properties": {
"AdminUserCreated": {
"type": "boolean"
}
}
},
"Client": {
"id": "Client",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The client ID. Ignored in client create requests."
},
"secret": {
"type": "string",
"description": "The client secret. Ignored in client create requests."
},
"isAdmin": {
"type": "boolean"
},
"redirectURIs": {
"type": "array",
"items": {
"type": "string"
},
"description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)."
},
"clientName": {
"type": "string",
"description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"logoURI": {
"type": "string",
"description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"clientURI": {
"type": "string",
"description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
}
}
},
"ClientCreateRequest": {
"id": "ClientCreateRequest",
"type": "object",
@ -107,78 +102,130 @@ const DiscoveryJSON = `{
"type": "object",
"description": "Upon successful registration, an ID and secret is assigned to the client.",
"properties": {
"client":{
"client": {
"$ref": "Client"
}
}
},
"Connector": {
"id": "Connector",
"type": "any",
"description": "An object which describes a federating identity strategy. For documentation see Documentation/connectors-configuration.md. Since different connectors expect different object fields the scheme is omitted here."
},
"ConnectorsSetRequest": {
"id": "ConnectorsSetRequest",
"type": "object",
"description": "A request to set all the connectors in the dex database.",
"properties": {
"connectors": {
"type": "array",
"items": {
"$ref": "Connector"
}
}
}
},
"ConnectorsGetResponse": {
"id": "ConnectorsGetResponse",
"type": "object",
"description": "A list of all connector responses.",
"properties": {
"connectors": {
"type": "array",
"items": {
"$ref": "Connector"
}
}
}
}
},
"resources": {
"Admin": {
"methods": {
"Get": {
"id": "dex.admin.Admin.Get",
"description": "Retrieve information about an admin user.",
"httpMethod": "GET",
"path": "admin/{id}",
"parameters": {
"id": {
"type": "string",
"required": true,
"location": "path"
}
},
"parameterOrder": [
"id"
],
"response": {
"$ref": "Admin"
}
},
"Create": {
"id": "dex.admin.Admin.Create",
"description": "Create a new admin user.",
"httpMethod": "POST",
"path": "admin",
"request": {
"$ref": "Admin"
},
"response": {
"$ref": "Admin"
}
}
"Admin": {
"methods": {
"Get": {
"id": "dex.admin.Admin.Get",
"description": "Retrieve information about an admin user.",
"httpMethod": "GET",
"path": "admin/{id}",
"parameters": {
"id": {
"type": "string",
"required": true,
"location": "path"
}
},
"parameterOrder": [
"id"
],
"response": {
"$ref": "Admin"
}
},
"State": {
"methods": {
"Get": {
"id": "dex.admin.State.Get",
"description": "Get the state of the Dex DB",
"httpMethod": "GET",
"path": "state",
"response": {
"$ref": "State"
}
}
}
},
"Client": {
"methods": {
"Create": {
"id": "dex.admin.Client.Create",
"description": "Register an OpenID Connect client.",
"httpMethod": "POST",
"path": "client",
"request": {
"$ref": "ClientCreateRequest"
},
"response": {
"$ref": "ClientCreateResponse"
}
}
},
"Create": {
"id": "dex.admin.Admin.Create",
"description": "Create a new admin user.",
"httpMethod": "POST",
"path": "admin",
"request": {
"$ref": "Admin"
},
"response": {
"$ref": "Admin"
}
}
}
},
"State": {
"methods": {
"Get": {
"id": "dex.admin.State.Get",
"description": "Get the state of the Dex DB",
"httpMethod": "GET",
"path": "state",
"response": {
"$ref": "State"
}
}
}
},
"Client": {
"methods": {
"Create": {
"id": "dex.admin.Client.Create",
"description": "Register an OpenID Connect client.",
"httpMethod": "POST",
"path": "client",
"request": {
"$ref": "ClientCreateRequest"
},
"response": {
"$ref": "ClientCreateResponse"
}
}
}
},
"Connectors": {
"methods": {
"Set": {
"id": "dex.admin.Connector.Set",
"description": "Set the list of connectors for the dex system, overwriting all previous connectors. A 200 status code indicates the action was successful.",
"httpMethod": "PUT",
"path": "connectors",
"request": {
"$ref": "ConnectorsSetRequest"
}
},
"Get": {
"id": "dex.admin.Connector.Get",
"description": "Return a list of the connectors for the dex system.",
"httpMethod": "GET",
"path": "connectors",
"response": {
"$ref": "ConnectorsGetResponse"
}
}
}
}
}
}
`
`

View file

@ -21,66 +21,66 @@
"parameters": {},
"auth": {},
"schemas": {
"Admin": {
"id": "Admin",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"State": {
"id": "State",
"type": "object",
"properties": {
"AdminUserCreated": {
"type": "boolean"
}
}
},
"Client": {
"id": "Client",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The client ID. Ignored in client create requests."
},
"secret": {
"type": "string",
"description": "The client secret. Ignored in client create requests."
},
"isAdmin": {
"type": "boolean"
},
"redirectURIs": {
"type": "array",
"items": {
"type": "string"
},
"description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)."
},
"clientName": {
"type": "string",
"description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"logoURI": {
"type": "string",
"description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"clientURI": {
"type": "string",
"description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
}
"Admin": {
"id": "Admin",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
}
},
}
},
"State": {
"id": "State",
"type": "object",
"properties": {
"AdminUserCreated": {
"type": "boolean"
}
}
},
"Client": {
"id": "Client",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The client ID. Ignored in client create requests."
},
"secret": {
"type": "string",
"description": "The client secret. Ignored in client create requests."
},
"isAdmin": {
"type": "boolean"
},
"redirectURIs": {
"type": "array",
"items": {
"type": "string"
},
"description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)."
},
"clientName": {
"type": "string",
"description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"logoURI": {
"type": "string",
"description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
},
"clientURI": {
"type": "string",
"description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
}
}
},
"ClientCreateRequest": {
"id": "ClientCreateRequest",
"type": "object",
@ -96,77 +96,129 @@
"type": "object",
"description": "Upon successful registration, an ID and secret is assigned to the client.",
"properties": {
"client":{
"client": {
"$ref": "Client"
}
}
},
"Connector": {
"id": "Connector",
"type": "any",
"description": "An object which describes a federating identity strategy. For documentation see Documentation/connectors-configuration.md. Since different connectors expect different object fields the scheme is omitted here."
},
"ConnectorsSetRequest": {
"id": "ConnectorsSetRequest",
"type": "object",
"description": "A request to set all the connectors in the dex database.",
"properties": {
"connectors": {
"type": "array",
"items": {
"$ref": "Connector"
}
}
}
},
"ConnectorsGetResponse": {
"id": "ConnectorsGetResponse",
"type": "object",
"description": "A list of all connector responses.",
"properties": {
"connectors": {
"type": "array",
"items": {
"$ref": "Connector"
}
}
}
}
},
"resources": {
"Admin": {
"methods": {
"Get": {
"id": "dex.admin.Admin.Get",
"description": "Retrieve information about an admin user.",
"httpMethod": "GET",
"path": "admin/{id}",
"parameters": {
"id": {
"type": "string",
"required": true,
"location": "path"
}
},
"parameterOrder": [
"id"
],
"response": {
"$ref": "Admin"
}
},
"Create": {
"id": "dex.admin.Admin.Create",
"description": "Create a new admin user.",
"httpMethod": "POST",
"path": "admin",
"request": {
"$ref": "Admin"
},
"response": {
"$ref": "Admin"
}
}
"Admin": {
"methods": {
"Get": {
"id": "dex.admin.Admin.Get",
"description": "Retrieve information about an admin user.",
"httpMethod": "GET",
"path": "admin/{id}",
"parameters": {
"id": {
"type": "string",
"required": true,
"location": "path"
}
},
"parameterOrder": [
"id"
],
"response": {
"$ref": "Admin"
}
},
"State": {
"methods": {
"Get": {
"id": "dex.admin.State.Get",
"description": "Get the state of the Dex DB",
"httpMethod": "GET",
"path": "state",
"response": {
"$ref": "State"
}
}
}
},
"Client": {
"methods": {
"Create": {
"id": "dex.admin.Client.Create",
"description": "Register an OpenID Connect client.",
"httpMethod": "POST",
"path": "client",
"request": {
"$ref": "ClientCreateRequest"
},
"response": {
"$ref": "ClientCreateResponse"
}
}
},
"Create": {
"id": "dex.admin.Admin.Create",
"description": "Create a new admin user.",
"httpMethod": "POST",
"path": "admin",
"request": {
"$ref": "Admin"
},
"response": {
"$ref": "Admin"
}
}
}
},
"State": {
"methods": {
"Get": {
"id": "dex.admin.State.Get",
"description": "Get the state of the Dex DB",
"httpMethod": "GET",
"path": "state",
"response": {
"$ref": "State"
}
}
}
},
"Client": {
"methods": {
"Create": {
"id": "dex.admin.Client.Create",
"description": "Register an OpenID Connect client.",
"httpMethod": "POST",
"path": "client",
"request": {
"$ref": "ClientCreateRequest"
},
"response": {
"$ref": "ClientCreateResponse"
}
}
}
},
"Connectors": {
"methods": {
"Set": {
"id": "dex.admin.Connector.Set",
"description": "Set the list of connectors for the dex system, overwriting all previous connectors. A 200 status code indicates the action was successful.",
"httpMethod": "PUT",
"path": "connectors",
"request": {
"$ref": "ConnectorsSetRequest"
}
},
"Get": {
"id": "dex.admin.Connector.Get",
"description": "Return a list of the connectors for the dex system.",
"httpMethod": "GET",
"path": "connectors",
"response": {
"$ref": "ConnectorsGetResponse"
}
}
}
}
}
}

View file

@ -31,6 +31,8 @@ if [ ! -f $GENDOC ]; then
exit 1
fi
go run schema/jsonfmt.go $IN
$GENDOC --f $IN --o $DOC
# Though google-api-go-generator is a main, dex vendors the app using the same

54
schema/jsonfmt.go Normal file
View file

@ -0,0 +1,54 @@
// +build ignore
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"os"
)
func init() {
log.SetPrefix("fmtjson: ")
log.SetFlags(0)
}
func main() {
if len(os.Args) < 1 {
log.Fatal(`usage: go run jsonfmt.go [files...]
This is a small utility that standardizes the formatting of JSON files.
`)
}
for _, path := range os.Args[1:] {
data, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("reading file %s: %v", path, err)
}
buff := new(bytes.Buffer)
if err := json.Indent(buff, data, "", " "); err != nil {
if err, ok := err.(*json.SyntaxError); ok {
// Calculate line number and column of error.
data := data[:err.Offset]
lineNum := 1 + bytes.Count(data, []byte{'\n'})
lastIndex := bytes.LastIndex(data, []byte{'\n'})
colNum := err.Offset
if lastIndex > -1 {
colNum = int64(len(data) - lastIndex)
}
log.Fatalf("file %s: invalid json at line %d, column %d: %v", path, lineNum, colNum, err)
}
log.Fatalf("file %s: invalid json: %v", path, err)
}
if err := ioutil.WriteFile(path, buff.Bytes(), 0644); err != nil {
log.Fatalf("write file %s: %v", path, err)
}
}
}

View file

@ -232,8 +232,8 @@ A client with associated public metadata.
> |Name|Located in|Description|Required|Type|
|:-----|:-----|:-----|:-----|:-----|
| clientid | path | | Yes | string |
| userid | path | | Yes | string |
| clientid | path | | Yes | string |
> __Responses__
@ -310,8 +310,8 @@ A client with associated public metadata.
> |Name|Located in|Description|Required|Type|
|:-----|:-----|:-----|:-----|:-----|
| nextPageToken | query | | No | string |
| maxResults | query | | No | integer |
| nextPageToken | query | | No | string |
> __Responses__

View file

@ -1,5 +1,4 @@
package workerschema
//
// This file is automatically generated by schema/generator
//
@ -56,26 +55,26 @@ const DiscoveryJSON = `{
}
}
},
"RefreshClient": {
"RefreshClient": {
"id": "Client",
"type": "object",
"description": "A client with associated public metadata.",
"description": "A client with associated public metadata.",
"properties": {
"clientID": {
"type": "string"
},
"clientName": {
"type": "string"
},
"logoURI": {
"type": "string"
},
"clientURI": {
"type": "string"
}
"clientID": {
"type": "string"
},
"clientName": {
"type": "string"
},
"logoURI": {
"type": "string"
},
"clientURI": {
"type": "string"
}
}
},
"RefreshClientList": {
},
"RefreshClientList": {
"id": "RefreshClientList",
"type": "object",
"properties": {
@ -86,7 +85,7 @@ const DiscoveryJSON = `{
}
}
}
},
},
"ClientWithSecret": {
"id": "Client",
"type": "object",
@ -154,7 +153,7 @@ const DiscoveryJSON = `{
"type": "object",
"properties": {
"user": {
"$ref": "User"
"$ref": "User"
}
}
},
@ -280,7 +279,8 @@ const DiscoveryJSON = `{
"httpMethod": "DELETE",
"path": "account/{userid}/refresh/{clientid}",
"parameterOrder": [
"userid","clientid"
"userid",
"clientid"
],
"parameters": {
"clientid": {
@ -420,4 +420,4 @@ const DiscoveryJSON = `{
}
}
}
`
`

View file

@ -49,26 +49,26 @@
}
}
},
"RefreshClient": {
"RefreshClient": {
"id": "Client",
"type": "object",
"description": "A client with associated public metadata.",
"description": "A client with associated public metadata.",
"properties": {
"clientID": {
"type": "string"
},
"clientName": {
"type": "string"
},
"logoURI": {
"type": "string"
},
"clientURI": {
"type": "string"
}
"clientID": {
"type": "string"
},
"clientName": {
"type": "string"
},
"logoURI": {
"type": "string"
},
"clientURI": {
"type": "string"
}
}
},
"RefreshClientList": {
},
"RefreshClientList": {
"id": "RefreshClientList",
"type": "object",
"properties": {
@ -79,7 +79,7 @@
}
}
}
},
},
"ClientWithSecret": {
"id": "Client",
"type": "object",
@ -147,7 +147,7 @@
"type": "object",
"properties": {
"user": {
"$ref": "User"
"$ref": "User"
}
}
},
@ -273,7 +273,8 @@
"httpMethod": "DELETE",
"path": "account/{userid}/refresh/{clientid}",
"parameterOrder": [
"userid","clientid"
"userid",
"clientid"
],
"parameters": {
"clientid": {

View file

@ -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 {