From f39191a92d1222118778d4b935717843eb8db668 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Thu, 26 May 2016 10:41:16 -0700 Subject: [PATCH 1/4] schema: add small tool to standardize JSON formatting --- schema/generator | 2 ++ schema/jsonfmt.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 schema/jsonfmt.go diff --git a/schema/generator b/schema/generator index 56c5e5aa..cafd4e54 100755 --- a/schema/generator +++ b/schema/generator @@ -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 diff --git a/schema/jsonfmt.go b/schema/jsonfmt.go new file mode 100644 index 00000000..83ba4234 --- /dev/null +++ b/schema/jsonfmt.go @@ -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) + } + } +} From 2003df83cf3539d1b9b6e8e959f9e220a9b1209a Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Thu, 26 May 2016 10:41:33 -0700 Subject: [PATCH 2/4] schema: standardize JSON formatting --- schema/adminschema/README.md | 2 +- schema/adminschema/v1-gen.go | 1 + schema/adminschema/v1-json.go | 250 ++++++++++++++++----------------- schema/adminschema/v1.json | 243 ++++++++++++++++---------------- schema/workerschema/README.md | 4 +- schema/workerschema/v1-json.go | 42 +++--- schema/workerschema/v1.json | 39 ++--- 7 files changed, 288 insertions(+), 293 deletions(-) diff --git a/schema/adminschema/README.md b/schema/adminschema/README.md index 48699989..1b5b35cc 100644 --- a/schema/adminschema/README.md +++ b/schema/adminschema/README.md @@ -34,7 +34,7 @@ __Version:__ v1 redirectURIs: [ string ], - secret: string + secret: string // The client secret. Ignored in client create requests. } ``` diff --git a/schema/adminschema/v1-gen.go b/schema/adminschema/v1-gen.go index bd49ddf3..a8209eb7 100644 --- a/schema/adminschema/v1-gen.go +++ b/schema/adminschema/v1-gen.go @@ -134,6 +134,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"` } diff --git a/schema/adminschema/v1-json.go b/schema/adminschema/v1-json.go index 5d943e99..ec7e74ab 100644 --- a/schema/adminschema/v1-json.go +++ b/schema/adminschema/v1-json.go @@ -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,77 @@ const DiscoveryJSON = `{ "type": "object", "description": "Upon successful registration, an ID and secret is assigned to the client.", "properties": { - "client":{ + "client": { "$ref": "Client" } } } }, "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" + } + } + } + } } } -` +` \ No newline at end of file diff --git a/schema/adminschema/v1.json b/schema/adminschema/v1.json index 4aa8573c..ae03da8b 100644 --- a/schema/adminschema/v1.json +++ b/schema/adminschema/v1.json @@ -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,76 @@ "type": "object", "description": "Upon successful registration, an ID and secret is assigned to the client.", "properties": { - "client":{ + "client": { "$ref": "Client" } } } }, "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" + } + } + } + } } } diff --git a/schema/workerschema/README.md b/schema/workerschema/README.md index 203b3154..0e396d66 100644 --- a/schema/workerschema/README.md +++ b/schema/workerschema/README.md @@ -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__ diff --git a/schema/workerschema/v1-json.go b/schema/workerschema/v1-json.go index 06a5a6bf..77707848 100644 --- a/schema/workerschema/v1-json.go +++ b/schema/workerschema/v1-json.go @@ -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 = `{ } } } -` +` \ No newline at end of file diff --git a/schema/workerschema/v1.json b/schema/workerschema/v1.json index 80343d1e..76be8e57 100644 --- a/schema/workerschema/v1.json +++ b/schema/workerschema/v1.json @@ -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": { From adbf4862466ca453aafe061954a44c0b66b376f6 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Thu, 26 May 2016 13:13:28 -0700 Subject: [PATCH 3/4] schema/adminschema: add route for setting and listing connectors --- schema/adminschema/README.md | 76 ++++++++++++++++++ schema/adminschema/v1-gen.go | 144 ++++++++++++++++++++++++++++++++++ schema/adminschema/v1-json.go | 53 +++++++++++++ schema/adminschema/v1.json | 53 +++++++++++++ 4 files changed, 326 insertions(+) diff --git a/schema/adminschema/README.md b/schema/adminschema/README.md index 1b5b35cc..4b1b42df 100644 --- a/schema/adminschema/README.md +++ b/schema/adminschema/README.md @@ -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__ diff --git a/schema/adminschema/v1-gen.go b/schema/adminschema/v1-gen.go index a8209eb7..472aa28e 100644 --- a/schema/adminschema/v1-gen.go +++ b/schema/adminschema/v1-gen.go @@ -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 @@ -146,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"` } @@ -360,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 { diff --git a/schema/adminschema/v1-json.go b/schema/adminschema/v1-json.go index ec7e74ab..f57ef741 100644 --- a/schema/adminschema/v1-json.go +++ b/schema/adminschema/v1-json.go @@ -106,6 +106,37 @@ const DiscoveryJSON = `{ "$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": { @@ -172,6 +203,28 @@ const DiscoveryJSON = `{ } } } + }, + "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" + } + } + } } } } diff --git a/schema/adminschema/v1.json b/schema/adminschema/v1.json index ae03da8b..72f11249 100644 --- a/schema/adminschema/v1.json +++ b/schema/adminschema/v1.json @@ -100,6 +100,37 @@ "$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": { @@ -166,6 +197,28 @@ } } } + }, + "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" + } + } + } } } } From 35ea3d9ae128f80e07909109c35d8ba05e8a5a85 Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Thu, 26 May 2016 14:42:15 -0700 Subject: [PATCH 4/4] *: 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 {