From 07a8cec57063201b8e67125f0ffe8a39149cf90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Sun, 6 Sep 2020 11:51:33 +0200 Subject: [PATCH] Add federation pairing routes --- federation/rest.yaml | 74 +++++++++++++ federation/rest/handlers/identity.go | 86 +++++++++++++++ federation/rest/handlers/pair.go | 86 +++++++++++++++ federation/rest/handlers/pairRequest.go | 63 +++++++++++ federation/rest/node_identity.go | 26 +++++ federation/rest/node_pair.go | 25 +++++ federation/rest/node_pair_request.go | 21 ++++ federation/rest/request/identity.go | 141 ++++++++++++++++++++++++ federation/rest/request/pair.go | 138 +++++++++++++++++++++++ federation/rest/request/pairRequest.go | 104 +++++++++++++++++ federation/rest/router.go | 10 +- 11 files changed, 770 insertions(+), 4 deletions(-) create mode 100644 federation/rest/handlers/identity.go create mode 100644 federation/rest/handlers/pair.go create mode 100644 federation/rest/handlers/pairRequest.go create mode 100644 federation/rest/node_identity.go create mode 100644 federation/rest/node_pair.go create mode 100644 federation/rest/node_pair_request.go create mode 100644 federation/rest/request/identity.go create mode 100644 federation/rest/request/pair.go create mode 100644 federation/rest/request/pairRequest.go diff --git a/federation/rest.yaml b/federation/rest.yaml index 388dfd67b..c89dc5f78 100644 --- a/federation/rest.yaml +++ b/federation/rest.yaml @@ -5,6 +5,80 @@ # Next step: swagger. endpoints: + - title: Node identity + path: "/node/identity" + entrypoint: identity + authentication: [] + apis: + - name: generate node identity + method: POST + title: Generate an origin node identity + path: "/generate" + parameters: + post: + - name: domain + type: string + required: true + title: Domain of the destination node + + - name: register origin node + method: POST + title: Register a new origin node + path: "/register" + parameters: + post: + - name: identifier + type: string + required: true + title: Origin node identifier + + - title: Federation node pair request + path: "/node/pair/request" + entrypoint: pairRequest + authentication: [] + apis: + - name: request pairing + method: POST + title: Handle destination node pair request + path: "/" + parameters: + post: + - name: identifier + type: string + required: true + title: Origin node identifier + - name: token + type: string + required: true + title: Destination node token + + - title: Federation node pairing + path: "/node/pair" + entrypoint: pair + authentication: [] + apis: + - name: approve pairing + method: POST + title: Approve the destination node pair request + path: "/approve" + parameters: + get: + - name: requestID + type: uint64 + required: true + title: Pair requestID + + - name: complete pairing + method: POST + title: Complete pairing with the origin + path: "/complete" + parameters: + post: + - name: token + type: string + required: true + title: Auth token of the origin node + - title: Foobar entrypoint: foobar path: "/foobar" diff --git a/federation/rest/handlers/identity.go b/federation/rest/handlers/identity.go new file mode 100644 index 000000000..924720a0b --- /dev/null +++ b/federation/rest/handlers/identity.go @@ -0,0 +1,86 @@ +package handlers + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "context" + "github.com/go-chi/chi" + "github.com/titpetric/factory/resputil" + "net/http" + + "github.com/cortezaproject/corteza-server/federation/rest/request" + "github.com/cortezaproject/corteza-server/pkg/logger" +) + +type ( + // Internal API interface + IdentityAPI interface { + GenerateNodeIdentity(context.Context, *request.IdentityGenerateNodeIdentity) (interface{}, error) + RegisterOriginNode(context.Context, *request.IdentityRegisterOriginNode) (interface{}, error) + } + + // HTTP API interface + Identity struct { + GenerateNodeIdentity func(http.ResponseWriter, *http.Request) + RegisterOriginNode func(http.ResponseWriter, *http.Request) + } +) + +func NewIdentity(h IdentityAPI) *Identity { + return &Identity{ + GenerateNodeIdentity: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewIdentityGenerateNodeIdentity() + if err := params.Fill(r); err != nil { + logger.LogParamError("Identity.GenerateNodeIdentity", r, err) + resputil.JSON(w, err) + return + } + + value, err := h.GenerateNodeIdentity(r.Context(), params) + if err != nil { + logger.LogControllerError("Identity.GenerateNodeIdentity", r, err, params.Auditable()) + resputil.JSON(w, err) + return + } + logger.LogControllerCall("Identity.GenerateNodeIdentity", r, params.Auditable()) + if !serveHTTP(value, w, r) { + resputil.JSON(w, value) + } + }, + RegisterOriginNode: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewIdentityRegisterOriginNode() + if err := params.Fill(r); err != nil { + logger.LogParamError("Identity.RegisterOriginNode", r, err) + resputil.JSON(w, err) + return + } + + value, err := h.RegisterOriginNode(r.Context(), params) + if err != nil { + logger.LogControllerError("Identity.RegisterOriginNode", r, err, params.Auditable()) + resputil.JSON(w, err) + return + } + logger.LogControllerCall("Identity.RegisterOriginNode", r, params.Auditable()) + if !serveHTTP(value, w, r) { + resputil.JSON(w, value) + } + }, + } +} + +func (h Identity) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Post("/node/identity/generate", h.GenerateNodeIdentity) + r.Post("/node/identity/register", h.RegisterOriginNode) + }) +} diff --git a/federation/rest/handlers/pair.go b/federation/rest/handlers/pair.go new file mode 100644 index 000000000..70f491eb2 --- /dev/null +++ b/federation/rest/handlers/pair.go @@ -0,0 +1,86 @@ +package handlers + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "context" + "github.com/go-chi/chi" + "github.com/titpetric/factory/resputil" + "net/http" + + "github.com/cortezaproject/corteza-server/federation/rest/request" + "github.com/cortezaproject/corteza-server/pkg/logger" +) + +type ( + // Internal API interface + PairAPI interface { + ApprovePairing(context.Context, *request.PairApprovePairing) (interface{}, error) + CompletePairing(context.Context, *request.PairCompletePairing) (interface{}, error) + } + + // HTTP API interface + Pair struct { + ApprovePairing func(http.ResponseWriter, *http.Request) + CompletePairing func(http.ResponseWriter, *http.Request) + } +) + +func NewPair(h PairAPI) *Pair { + return &Pair{ + ApprovePairing: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPairApprovePairing() + if err := params.Fill(r); err != nil { + logger.LogParamError("Pair.ApprovePairing", r, err) + resputil.JSON(w, err) + return + } + + value, err := h.ApprovePairing(r.Context(), params) + if err != nil { + logger.LogControllerError("Pair.ApprovePairing", r, err, params.Auditable()) + resputil.JSON(w, err) + return + } + logger.LogControllerCall("Pair.ApprovePairing", r, params.Auditable()) + if !serveHTTP(value, w, r) { + resputil.JSON(w, value) + } + }, + CompletePairing: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPairCompletePairing() + if err := params.Fill(r); err != nil { + logger.LogParamError("Pair.CompletePairing", r, err) + resputil.JSON(w, err) + return + } + + value, err := h.CompletePairing(r.Context(), params) + if err != nil { + logger.LogControllerError("Pair.CompletePairing", r, err, params.Auditable()) + resputil.JSON(w, err) + return + } + logger.LogControllerCall("Pair.CompletePairing", r, params.Auditable()) + if !serveHTTP(value, w, r) { + resputil.JSON(w, value) + } + }, + } +} + +func (h Pair) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Post("/node/pair/approve", h.ApprovePairing) + r.Post("/node/pair/complete", h.CompletePairing) + }) +} diff --git a/federation/rest/handlers/pairRequest.go b/federation/rest/handlers/pairRequest.go new file mode 100644 index 000000000..7da1cfdf4 --- /dev/null +++ b/federation/rest/handlers/pairRequest.go @@ -0,0 +1,63 @@ +package handlers + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "context" + "github.com/go-chi/chi" + "github.com/titpetric/factory/resputil" + "net/http" + + "github.com/cortezaproject/corteza-server/federation/rest/request" + "github.com/cortezaproject/corteza-server/pkg/logger" +) + +type ( + // Internal API interface + PairRequestAPI interface { + RequestPairing(context.Context, *request.PairRequestRequestPairing) (interface{}, error) + } + + // HTTP API interface + PairRequest struct { + RequestPairing func(http.ResponseWriter, *http.Request) + } +) + +func NewPairRequest(h PairRequestAPI) *PairRequest { + return &PairRequest{ + RequestPairing: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPairRequestRequestPairing() + if err := params.Fill(r); err != nil { + logger.LogParamError("PairRequest.RequestPairing", r, err) + resputil.JSON(w, err) + return + } + + value, err := h.RequestPairing(r.Context(), params) + if err != nil { + logger.LogControllerError("PairRequest.RequestPairing", r, err, params.Auditable()) + resputil.JSON(w, err) + return + } + logger.LogControllerCall("PairRequest.RequestPairing", r, params.Auditable()) + if !serveHTTP(value, w, r) { + resputil.JSON(w, value) + } + }, + } +} + +func (h PairRequest) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Post("/node/pair/request/", h.RequestPairing) + }) +} diff --git a/federation/rest/node_identity.go b/federation/rest/node_identity.go new file mode 100644 index 000000000..c3643ccee --- /dev/null +++ b/federation/rest/node_identity.go @@ -0,0 +1,26 @@ +package rest + +import ( + "context" + "fmt" + + "github.com/cortezaproject/corteza-server/federation/rest/request" +) + +type ( + NodeIdentity struct{} +) + +func (NodeIdentity) New() *NodeIdentity { + return &NodeIdentity{} +} + +func (ctrl NodeIdentity) GenerateNodeIdentity(ctx context.Context, r *request.IdentityGenerateNodeIdentity) (interface{}, error) { + fmt.Println("GenerateNOdeIdentity") + return nil, nil +} + +func (ctrl NodeIdentity) RegisterOriginNode(ctx context.Context, r *request.IdentityRegisterOriginNode) (interface{}, error) { + fmt.Println("RegisterORiginNode") + return nil, nil +} diff --git a/federation/rest/node_pair.go b/federation/rest/node_pair.go new file mode 100644 index 000000000..a151d4395 --- /dev/null +++ b/federation/rest/node_pair.go @@ -0,0 +1,25 @@ +package rest + +import ( + "context" + "fmt" + + "github.com/cortezaproject/corteza-server/federation/rest/request" +) + +type ( + NodePair struct{} +) + +func (NodePair) New() *NodePair { + return &NodePair{} +} + +func (ctrl NodePair) ApprovePairing(ctx context.Context, r *request.PairApprovePairing) (interface{}, error) { + fmt.Println("ApprovePairing") + return nil, nil +} +func (ctrl NodePair) CompletePairing(ctx context.Context, r *request.PairCompletePairing) (interface{}, error) { + fmt.Println("CompletePairing") + return nil, nil +} diff --git a/federation/rest/node_pair_request.go b/federation/rest/node_pair_request.go new file mode 100644 index 000000000..185cb2d6c --- /dev/null +++ b/federation/rest/node_pair_request.go @@ -0,0 +1,21 @@ +package rest + +import ( + "context" + "fmt" + + "github.com/cortezaproject/corteza-server/federation/rest/request" +) + +type ( + NodePairRequest struct{} +) + +func (NodePairRequest) New() *NodePairRequest { + return &NodePairRequest{} +} + +func (ctrl NodePairRequest) RequestPairing(ctx context.Context, r *request.PairRequestRequestPairing) (interface{}, error) { + fmt.Println("RequestPairing") + return nil, nil +} diff --git a/federation/rest/request/identity.go b/federation/rest/request/identity.go new file mode 100644 index 000000000..e4fcc0239 --- /dev/null +++ b/federation/rest/request/identity.go @@ -0,0 +1,141 @@ +package request + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "encoding/json" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/go-chi/chi" + "io" + "mime/multipart" + "net/http" + "strings" +) + +// dummy vars to prevent +// unused imports complain +var ( + _ = chi.URLParam + _ = multipart.ErrMessageTooLarge + _ = payload.ParseUint64s +) + +type ( + // Internal API interface + IdentityGenerateNodeIdentity struct { + // Domain POST parameter + // + // Domain of the destination node + Domain string + } + + IdentityRegisterOriginNode struct { + // Identifier POST parameter + // + // Origin node identifier + Identifier string + } +) + +// NewIdentityGenerateNodeIdentity request +func NewIdentityGenerateNodeIdentity() *IdentityGenerateNodeIdentity { + return &IdentityGenerateNodeIdentity{} +} + +// Auditable returns all auditable/loggable parameters +func (r IdentityGenerateNodeIdentity) Auditable() map[string]interface{} { + return map[string]interface{}{ + "domain": r.Domain, + } +} + +// Auditable returns all auditable/loggable parameters +func (r IdentityGenerateNodeIdentity) GetDomain() string { + return r.Domain +} + +// Fill processes request and fills internal variables +func (r *IdentityGenerateNodeIdentity) Fill(req *http.Request) (err error) { + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + if val, ok := req.Form["domain"]; ok && len(val) > 0 { + r.Domain, err = val[0], nil + if err != nil { + return err + } + } + } + + return err +} + +// NewIdentityRegisterOriginNode request +func NewIdentityRegisterOriginNode() *IdentityRegisterOriginNode { + return &IdentityRegisterOriginNode{} +} + +// Auditable returns all auditable/loggable parameters +func (r IdentityRegisterOriginNode) Auditable() map[string]interface{} { + return map[string]interface{}{ + "identifier": r.Identifier, + } +} + +// Auditable returns all auditable/loggable parameters +func (r IdentityRegisterOriginNode) GetIdentifier() string { + return r.Identifier +} + +// Fill processes request and fills internal variables +func (r *IdentityRegisterOriginNode) Fill(req *http.Request) (err error) { + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + if val, ok := req.Form["identifier"]; ok && len(val) > 0 { + r.Identifier, err = val[0], nil + if err != nil { + return err + } + } + } + + return err +} diff --git a/federation/rest/request/pair.go b/federation/rest/request/pair.go new file mode 100644 index 000000000..45d97faeb --- /dev/null +++ b/federation/rest/request/pair.go @@ -0,0 +1,138 @@ +package request + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "encoding/json" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/go-chi/chi" + "io" + "mime/multipart" + "net/http" + "strings" +) + +// dummy vars to prevent +// unused imports complain +var ( + _ = chi.URLParam + _ = multipart.ErrMessageTooLarge + _ = payload.ParseUint64s +) + +type ( + // Internal API interface + PairApprovePairing struct { + // RequestID GET parameter + // + // Pair requestID + RequestID uint64 `json:",string"` + } + + PairCompletePairing struct { + // Token POST parameter + // + // Auth token of the origin node + Token string + } +) + +// NewPairApprovePairing request +func NewPairApprovePairing() *PairApprovePairing { + return &PairApprovePairing{} +} + +// Auditable returns all auditable/loggable parameters +func (r PairApprovePairing) Auditable() map[string]interface{} { + return map[string]interface{}{ + "requestID": r.RequestID, + } +} + +// Auditable returns all auditable/loggable parameters +func (r PairApprovePairing) GetRequestID() uint64 { + return r.RequestID +} + +// Fill processes request and fills internal variables +func (r *PairApprovePairing) Fill(req *http.Request) (err error) { + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + // GET params + tmp := req.URL.Query() + + if val, ok := tmp["requestID"]; ok && len(val) > 0 { + r.RequestID, err = payload.ParseUint64(val[0]), nil + if err != nil { + return err + } + } + } + + return err +} + +// NewPairCompletePairing request +func NewPairCompletePairing() *PairCompletePairing { + return &PairCompletePairing{} +} + +// Auditable returns all auditable/loggable parameters +func (r PairCompletePairing) Auditable() map[string]interface{} { + return map[string]interface{}{ + "token": r.Token, + } +} + +// Auditable returns all auditable/loggable parameters +func (r PairCompletePairing) GetToken() string { + return r.Token +} + +// Fill processes request and fills internal variables +func (r *PairCompletePairing) Fill(req *http.Request) (err error) { + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + if val, ok := req.Form["token"]; ok && len(val) > 0 { + r.Token, err = val[0], nil + if err != nil { + return err + } + } + } + + return err +} diff --git a/federation/rest/request/pairRequest.go b/federation/rest/request/pairRequest.go new file mode 100644 index 000000000..32fca9484 --- /dev/null +++ b/federation/rest/request/pairRequest.go @@ -0,0 +1,104 @@ +package request + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "encoding/json" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/go-chi/chi" + "io" + "mime/multipart" + "net/http" + "strings" +) + +// dummy vars to prevent +// unused imports complain +var ( + _ = chi.URLParam + _ = multipart.ErrMessageTooLarge + _ = payload.ParseUint64s +) + +type ( + // Internal API interface + PairRequestRequestPairing struct { + // Identifier POST parameter + // + // Origin node identifier + Identifier string + + // Token POST parameter + // + // Destination node token + Token string + } +) + +// NewPairRequestRequestPairing request +func NewPairRequestRequestPairing() *PairRequestRequestPairing { + return &PairRequestRequestPairing{} +} + +// Auditable returns all auditable/loggable parameters +func (r PairRequestRequestPairing) Auditable() map[string]interface{} { + return map[string]interface{}{ + "identifier": r.Identifier, + "token": r.Token, + } +} + +// Auditable returns all auditable/loggable parameters +func (r PairRequestRequestPairing) GetIdentifier() string { + return r.Identifier +} + +// Auditable returns all auditable/loggable parameters +func (r PairRequestRequestPairing) GetToken() string { + return r.Token +} + +// Fill processes request and fills internal variables +func (r *PairRequestRequestPairing) Fill(req *http.Request) (err error) { + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + if val, ok := req.Form["identifier"]; ok && len(val) > 0 { + r.Identifier, err = val[0], nil + if err != nil { + return err + } + } + + if val, ok := req.Form["token"]; ok && len(val) > 0 { + r.Token, err = val[0], nil + if err != nil { + return err + } + } + } + + return err +} diff --git a/federation/rest/router.go b/federation/rest/router.go index d25625011..38fec07cc 100644 --- a/federation/rest/router.go +++ b/federation/rest/router.go @@ -8,15 +8,17 @@ import ( ) func MountRoutes(r chi.Router) { - var ( - foobar = Foobar{}.New() - ) + r.Group(func(r chi.Router) { + handlers.NewPairRequest(NodePairRequest{}.New()).MountRoutes(r) + }) // Protect all _private_ routes r.Group(func(r chi.Router) { r.Use(auth.MiddlewareValidOnly) r.Use(middlewareAllowedAccess) - handlers.NewFoobar(foobar).MountRoutes(r) + handlers.NewIdentity(NodeIdentity{}.New()).MountRoutes(r) + handlers.NewPair(NodePair{}.New()).MountRoutes(r) + handlers.NewFoobar(Foobar{}.New()).MountRoutes(r) }) }