From da9100287c4b9c9f12ad9bdb548aefa3ad52736c Mon Sep 17 00:00:00 2001 From: Peter Grlica Date: Mon, 12 Jul 2021 08:32:07 +0200 Subject: [PATCH] Refactored services, rest and rdbms Added tests Added ac to routes and functions Proxy processer and auth servicer Added options and extensive logging Fixed calls to rbac in service, added default http handler on gateway root --- app/servers.go | 4 +- def/system.apigw-function.yaml | 8 + ...tem.route.yaml => system.apigw-route.yaml} | 0 def/system.yaml | 7 + pkg/apigw/apigw.go | 128 ++++--- pkg/apigw/apigw_test.go | 194 ++++++++-- pkg/apigw/auth.go | 128 +++++++ pkg/apigw/auth_test.go | 121 ++++++ pkg/apigw/error.go | 18 + pkg/apigw/expediter.go | 101 +++-- pkg/apigw/expediter_test.go | 74 ++++ pkg/apigw/function.go | 73 ++-- pkg/apigw/helpers.go | 21 + pkg/apigw/matcher_test.go | 74 ---- pkg/apigw/pipeline.go | 76 +++- pkg/apigw/pipeline_test.go | 69 ++++ pkg/apigw/processer.go | 323 ++++++++++++---- pkg/apigw/processer_test.go | 195 ++++++++++ pkg/apigw/registry.go | 19 +- pkg/apigw/registry_test.go | 100 +++++ pkg/apigw/route.go | 42 +- pkg/apigw/route_test.go | 117 ++++++ pkg/apigw/test.go | 67 ++++ pkg/apigw/validator.go | 87 +++++ pkg/apigw/validator_test.go | 71 ++-- pkg/apigw/verifier.go | 264 +++++++------ pkg/apigw/verifier_test.go | 141 +++++++ .../resource/rbac_references_system.gen.go | 42 +- pkg/envoy/resource/rbac_rules_parse.gen.go | 33 +- pkg/options/apigw.gen.go | 47 +++ pkg/options/apigw.yaml | 37 ++ store/apigw_function.gen.go | 26 +- store/apigw_function.yaml | 4 +- store/apigw_route.gen.go | 26 +- store/apigw_route.yaml | 4 +- store/rdbms/apigw_function.gen.go | 54 +-- store/rdbms/apigw_function.go | 8 +- store/rdbms/apigw_route.gen.go | 54 +-- store/rdbms/apigw_route.go | 2 +- system/rest.yaml | 29 +- .../rest/{function.go => apigw_function.go} | 51 +-- system/rest/{route.go => apigw_route.go} | 44 +-- .../{function.go => apigwFunction.go} | 38 +- .../rest/handlers/{route.go => apigwRoute.go} | 34 +- .../request/{function.go => apigwFunction.go} | 166 ++++---- .../rest/request/{route.go => apigwRoute.go} | 122 +++--- system/rest/router.go | 4 +- system/service/access_control.gen.go | 333 +++++++++++----- system/service/apigw_function.go | 249 ++++++++++++ ...s.gen.go => apigw_function_actions.gen.go} | 274 ++++++++++--- ...tions.yaml => apigw_function_actions.yaml} | 26 +- system/service/apigw_route.go | 241 ++++++++++++ ...ions.gen.go => apigw_route_actions.gen.go} | 202 +++++----- ..._actions.yaml => apigw_route_actions.yaml} | 10 +- system/service/function.go | 229 ----------- system/service/route.go | 230 ----------- system/service/service.go | 4 +- .../types/{function.go => apigw_function.go} | 34 +- system/types/{route.go => apigw_route.go} | 10 +- system/types/parsers.go | 4 +- system/types/rbac.gen.go | 111 ++++-- system/types/type_set.gen.go | 244 ++++++------ system/types/type_set.gen_test.go | 360 +++++++++--------- system/types/types.yaml | 4 +- 64 files changed, 3960 insertions(+), 1952 deletions(-) create mode 100644 def/system.apigw-function.yaml rename def/{system.route.yaml => system.apigw-route.yaml} (100%) create mode 100644 pkg/apigw/auth.go create mode 100644 pkg/apigw/auth_test.go create mode 100644 pkg/apigw/error.go create mode 100644 pkg/apigw/expediter_test.go create mode 100644 pkg/apigw/helpers.go delete mode 100644 pkg/apigw/matcher_test.go create mode 100644 pkg/apigw/pipeline_test.go create mode 100644 pkg/apigw/processer_test.go create mode 100644 pkg/apigw/registry_test.go create mode 100644 pkg/apigw/route_test.go create mode 100644 pkg/apigw/test.go create mode 100644 pkg/apigw/validator.go create mode 100644 pkg/apigw/verifier_test.go create mode 100644 pkg/options/apigw.gen.go create mode 100644 pkg/options/apigw.yaml rename system/rest/{function.go => apigw_function.go} (52%) rename system/rest/{route.go => apigw_route.go} (55%) rename system/rest/handlers/{function.go => apigwFunction.go} (75%) rename system/rest/handlers/{route.go => apigwRoute.go} (76%) rename system/rest/request/{function.go => apigwFunction.go} (73%) rename system/rest/request/{route.go => apigwRoute.go} (78%) create mode 100644 system/service/apigw_function.go rename system/service/{function_actions.gen.go => apigw_function_actions.gen.go} (50%) rename system/service/{function_actions.yaml => apigw_function_actions.yaml} (56%) create mode 100644 system/service/apigw_route.go rename system/service/{route_actions.gen.go => apigw_route_actions.gen.go} (65%) rename system/service/{route_actions.yaml => apigw_route_actions.yaml} (93%) delete mode 100644 system/service/function.go delete mode 100644 system/service/route.go rename system/types/{function.go => apigw_function.go} (56%) rename system/types/{route.go => apigw_route.go} (87%) diff --git a/app/servers.go b/app/servers.go index c482029a7..b0d186912 100644 --- a/app/servers.go +++ b/app/servers.go @@ -108,8 +108,8 @@ func (app *CortezaApp) mountHttpRoutes(r chi.Router) { // temp api gateway support { - apigw.Setup(struct{}{}, app.Log, service.DefaultStore) - r.Route("/gateway", apigw.Service().Router(context.Background())) + apigw.Setup(options.Apigw(), service.DefaultLogger, service.DefaultStore) + r.Route("/gateway", apigw.Service().Router) } var fullpathDocs = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "docs") diff --git a/def/system.apigw-function.yaml b/def/system.apigw-function.yaml new file mode 100644 index 000000000..f59b8cebb --- /dev/null +++ b/def/system.apigw-function.yaml @@ -0,0 +1,8 @@ +rbac: + operations: + read: + description: Read API Gateway function + update: + description: Update API Gateway function + delete: + description: Delete API Gateway function diff --git a/def/system.route.yaml b/def/system.apigw-route.yaml similarity index 100% rename from def/system.route.yaml rename to def/system.apigw-route.yaml diff --git a/def/system.yaml b/def/system.yaml index 9222d17cf..5f44193c7 100644 --- a/def/system.yaml +++ b/def/system.yaml @@ -52,3 +52,10 @@ rbac: api-gw-route.create: description: Create API gateway route + api-gw-routes.search: + description: List search or filter API gateway routes + + api-gw-function.create: + description: Add API gateway function to route + api-gw-functions.search: + description: List, search or filter functions diff --git a/pkg/apigw/apigw.go b/pkg/apigw/apigw.go index d4095162e..5f5314946 100644 --- a/pkg/apigw/apigw.go +++ b/pkg/apigw/apigw.go @@ -2,7 +2,11 @@ package apigw import ( "context" + "encoding/json" + as "github.com/cortezaproject/corteza-server/automation/service" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/system/types" "github.com/go-chi/chi" "go.uber.org/zap" @@ -10,11 +14,12 @@ import ( type ( storer interface { - SearchApigwRoutes(ctx context.Context, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) - SearchApigwFunctions(ctx context.Context, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) + SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) + SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) } apigw struct { + opts *options.ApigwOpt log *zap.Logger reg *registry routes []*route @@ -37,7 +42,7 @@ func Set(a *apigw) { } // Setup handles the singleton service -func Setup(opts interface{}, log *zap.Logger, storer storer) { +func Setup(opts *options.ApigwOpt, log *zap.Logger, storer storer) { if apiGw != nil { return } @@ -45,11 +50,12 @@ func Setup(opts interface{}, log *zap.Logger, storer storer) { apiGw = New(opts, log, storer) } -func New(opts interface{}, logger *zap.Logger, storer storer) *apigw { +func New(opts *options.ApigwOpt, logger *zap.Logger, storer storer) *apigw { reg := NewRegistry() reg.Preload() return &apigw{ + opts: opts, log: logger, storer: storer, reload: make(chan bool), @@ -64,7 +70,7 @@ func (s *apigw) Reload(ctx context.Context) { } func (s *apigw) loadRoutes(ctx context.Context) (rr []*route, err error) { - routes, _, err := s.storer.SearchApigwRoutes(ctx, types.RouteFilter{Enabled: true}) + routes, _, err := s.storer.SearchApigwRoutes(ctx, types.ApigwRouteFilter{Enabled: true, Deleted: filter.StateExcluded}) if err != nil { return @@ -83,66 +89,76 @@ func (s *apigw) loadRoutes(ctx context.Context) (rr []*route, err error) { return } -func (s *apigw) loadFunctions(ctx context.Context, route uint64) (ff []*types.Function, err error) { - ff, _, err = s.storer.SearchApigwFunctions(ctx, types.FunctionFilter{}) +func (s *apigw) loadFunctions(ctx context.Context, route uint64) (ff []*types.ApigwFunction, err error) { + ff, _, err = s.storer.SearchApigwFunctions(ctx, types.ApigwFunctionFilter{RouteID: route}) return } -func (s *apigw) Router(ctx context.Context) func(r chi.Router) { - return func(r chi.Router) { - routes, err := s.loadRoutes(ctx) +func (s *apigw) Router(r chi.Router) { + var ( + ctx = context.Background() + ) - if err != nil { - s.log.Error("could not load routes", zap.Error(err)) - return - } + r.HandleFunc("/", helperDefaultResponse(s.opts)) - s.Init(ctx, routes...) + routes, err := s.loadRoutes(ctx) - for _, route := range s.routes { - r.Handle(route.endpoint, route) - } + if err != nil { + s.log.Error("could not load routes", zap.Error(err)) + return + } - go func() { - for { - select { - case <-s.reload: - s.log.Debug("got reload signal") + s.Init(ctx, routes...) - routes, err := s.loadRoutes(ctx) + for _, route := range s.routes { + r.Handle(route.endpoint, route) + } - if err != nil { - s.log.Error("could not reload routes", zap.Error(err)) - return - } + go func() { + for { + select { + case <-s.reload: + routes, err := s.loadRoutes(ctx) - s.Init(ctx, routes...) - - for _, route := range s.routes { - r.Handle(route.endpoint, route) - } - - case <-ctx.Done(): - s.log.Debug("done! getting out") + if err != nil { + s.log.Error("could not reload API Gateway routes", zap.Error(err)) return } + + s.log.Debug("reloading API Gateway routes and functions", zap.Int("count", len(routes))) + + s.Init(ctx, routes...) + + for _, route := range s.routes { + r.Handle(route.endpoint, route) + } + + case <-ctx.Done(): + s.log.Debug("shutting down API Gateway service") + return } - }() - } + } + }() } // init all the routes func (s *apigw) Init(ctx context.Context, route ...*route) { s.routes = route - s.log.Debug("initializing routes\n", zap.Int("num", len(s.routes))) + s.log.Debug("registering routes", zap.Int("count", len(s.routes))) for _, r := range s.routes { - r.pipe = &pl{} + log := s.log.With(zap.String("route", r.String())) + + r.pipe = NewPipeline(log) + + r.opts = s.opts + r.log = log + regFuncs, err := s.loadFunctions(ctx, r.ID) if err != nil { - s.log.Error("could not load functions for route", zap.String("route", r.endpoint), zap.Error(err)) + log.Error("could not load functions for route", zap.Error(err)) continue } @@ -154,20 +170,31 @@ func (s *apigw) Init(ctx context.Context, route ...*route) { }) for _, f := range regFuncs { - fc := functionHandler{} - h, err := s.reg.Get(f.Ref) if err != nil { - s.log.Error("could not register function for route", zap.String("route", r.endpoint), zap.Error(err)) + log.Error("could not register function for route", zap.Error(err)) continue } - fc.Merge(ctx, h.Meta(f)) - fc.SetHandler(h.Handler()) + enc, err := json.Marshal(f.Params) - r.pipe.Add(fc, f.Params) + if err != nil { + log.Error("could not load params for function", zap.String("ref", f.Ref), zap.Error(err)) + continue + } + + h, err = s.reg.Merge(h, enc) + + if err != nil { + log.Error("could not merge params to handler", zap.String("ref", f.Ref), zap.Error(err)) + continue + } + + r.pipe.Add(h) } + + log.Debug("successfuly registered route") } } @@ -176,10 +203,13 @@ func (s *apigw) Funcs(kind string) (list functionMetaList) { if kind != "" { list, _ = list.Filter(func(fm *functionMeta) (bool, error) { - // return fm. - return fm.Kind == kind, nil + return string(fm.Kind) == kind, nil }) } return } + +func NewWorkflow() (wf WfExecer) { + return as.Workflow(&zap.Logger{}, options.CorredorOpt{}) +} diff --git a/pkg/apigw/apigw_test.go b/pkg/apigw/apigw_test.go index debc459bf..059d99eda 100644 --- a/pkg/apigw/apigw_test.go +++ b/pkg/apigw/apigw_test.go @@ -2,39 +2,187 @@ package apigw import ( "context" - "net/http" - "net/http/httptest" + "errors" "testing" - "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/cortezaproject/corteza-server/pkg/logger" - "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" "github.com/stretchr/testify/require" + "go.uber.org/zap" ) -func execFn(t *testing.T, r *http.Request, fn wfHandler) error { +type ( + // overriding mockHandler with only + // the merge function + mockExistingHandler struct { + *mockHandler + merge func(params []byte) (Handler, error) + } +) + +func Test_serviceLoadRoutes(t *testing.T) { var ( - req = require.New(t) - ctx = context.Background() - scope = &expr.Vars{} - graph = wfexec.NewGraph() - recorder = httptest.NewRecorder() + ctx = context.Background() + req = require.New(t) ) - scope.Set("envelope", envelope{ - Request: r, - Writer: recorder, - }) + mockStorer := &mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + {ID: 2, Endpoint: "/endpoint2", Method: "POST", Debug: false, Enabled: true, Group: 0}, + } + return + }, + } - step := wfexec.NewGenericStep(fn.self()) + service := &apigw{ + storer: mockStorer, + } - graph.AddStep(step) - - sess := wfexec.NewSession(ctx, graph, wfexec.SetLogger(logger.Default())) - - err := sess.Exec(ctx, step, scope) + r, err := service.loadRoutes(ctx) req.NoError(err) - - return sess.Wait(ctx) + req.Len(r, 2) +} + +func Test_serviceLoadFunctions(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + ) + + mockStorer := &mockStorer{ + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1}, + {ID: 2, Route: 2}, + } + return + }, + } + + service := &apigw{ + + storer: mockStorer, + } + + r, err := service.loadFunctions(ctx, 2) + + req.NoError(err) + req.Len(r, 2) +} + +func Test_serviceInit(t *testing.T) { + type ( + tf struct { + name string + expLen int + st mockStorer + reg *registry + } + ) + + var ( + tcc = []tf{ + { + name: "could not register 1 function for route", + st: mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + } + return + }, + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1, Ref: "testExistingFunction"}, + {ID: 2, Route: 1, Ref: "testNotExistingFunction"}, + } + return + }, + }, + reg: ®istry{ + h: map[string]Handler{"testExistingFunction": &mockHandler{}}, + }, + expLen: 1, + }, + { + name: "successful register of 2 functions for route", + st: mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + } + return + }, + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1, Ref: "testExistingFunction"}, + {ID: 2, Route: 1, Ref: "testExistingFunction"}, + } + return + }, + }, + reg: ®istry{ + h: map[string]Handler{"testExistingFunction": &mockHandler{}}, + }, + expLen: 2, + }, + { + name: "could not merge params for function", + st: mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + } + return + }, + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1, Ref: "testExistingFunction", Params: types.ApigwFuncParams{}}, + } + return + }, + }, + reg: ®istry{ + h: map[string]Handler{ + "testExistingFunction": &mockExistingHandler{ + merge: func(params []byte) (Handler, error) { + return nil, errors.New("testttt") + }, + }, + }, + }, + expLen: 0, + }, + } + ) + + for _, tc := range tcc { + t.Run(tc.name, func(t *testing.T) { + var ( + req = require.New(t) + ctx = context.Background() + ) + + service := &apigw{ + log: zap.NewNop(), + storer: tc.st, + reg: tc.reg, + } + + rr, err := service.loadRoutes(ctx) + req.NoError(err) + + service.Init(ctx, rr...) + + req.NotEmpty(service.routes) + req.Len(service.routes[0].pipe.w, tc.expLen) + }) + } + +} + +func (h mockExistingHandler) Merge(params []byte) (Handler, error) { + return h.merge(params) } diff --git a/pkg/apigw/auth.go b/pkg/apigw/auth.go new file mode 100644 index 000000000..e776ba68a --- /dev/null +++ b/pkg/apigw/auth.go @@ -0,0 +1,128 @@ +package apigw + +import ( + "encoding/base64" + "fmt" + "net/http" +) + +const ( + authTypeOauth2 authType = "oauth2" + authTypeHeader authType = "header" + authTypeQuery authType = "query" + authTypeBasic authType = "basic" + authTypeJwt authType = "jwt" + authTypeNoop authType = "noop" +) + +type ( + AuthServicer interface { + Do(*http.Request) error + } + + authServicerNoop struct{} + + authServicerHeader struct { + params map[string]interface{} + } + + authServicerQuery struct { + params map[string]interface{} + } + + authServicerBasic struct { + user string + pass string + } + + authType string + + authParams struct { + Type authType `json:"type"` + Params map[string]interface{} `json:"params"` + } +) + +func NewAuthHeader(p authParams) (s authServicerHeader, err error) { + s = authServicerHeader{ + params: p.Params, + } + + return +} + +func NewAuthQuery(p authParams) (s authServicerQuery, err error) { + s = authServicerQuery{ + params: p.Params, + } + + return +} + +func NewAuthBasic(p authParams) (s authServicerBasic, err error) { + var ( + ok bool + user, pass string + ) + + if user, ok = p.Params["username"].(string); !ok { + err = fmt.Errorf("invalid param username") + return + } + + if pass, ok = p.Params["password"].(string); !ok { + err = fmt.Errorf("invalid param password") + return + } + + s = authServicerBasic{user: user, pass: pass} + + return +} + +func NewAuthServicer(c *http.Client, p authParams) (AuthServicer, error) { + switch p.Type { + case authTypeHeader: + return NewAuthHeader(p) + case authTypeQuery: + return NewAuthQuery(p) + case authTypeBasic: + return NewAuthBasic(p) + default: + return authServicerNoop{}, nil + } +} + +func (s authServicerHeader) Do(r *http.Request) error { + for k, v := range s.params { + r.Header.Add(k, v.(string)) + } + return nil +} + +func (s authServicerQuery) Do(r *http.Request) error { + if len(s.params) == 0 { + return nil + } + + q := r.URL.Query() + + for k, v := range s.params { + q.Set(k, v.(string)) + } + + r.URL.RawQuery = q.Encode() + + return nil +} + +func (s authServicerBasic) Do(r *http.Request) error { + bs := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", s.user, s.pass))) + + r.Header.Set("Authorization", fmt.Sprintf("Basic %s", bs)) + return nil +} + +func (s authServicerNoop) Do(r *http.Request) error { + return nil +} diff --git a/pkg/apigw/auth_test.go b/pkg/apigw/auth_test.go new file mode 100644 index 000000000..2c6313e7f --- /dev/null +++ b/pkg/apigw/auth_test.go @@ -0,0 +1,121 @@ +package apigw + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +type ( + mockRoundTripper func(*http.Request) (*http.Response, error) +) + +func (mrt mockRoundTripper) RoundTrip(rq *http.Request) (r *http.Response, err error) { + return mrt(rq) +} + +func Test_authDo(t *testing.T) { + type ( + tf struct { + name string + err string + errv string + params authParams + exp http.Header + } + ) + + var ( + tcc = []tf{ + { + name: "auth header match headers", + params: authParams{ + Type: authTypeHeader, + Params: map[string]interface{}{ + "Client-Id": "123455", + "Client_credentials": "pass1234", + }, + }, + exp: http.Header{ + "Client-Id": []string{"123455"}, + "Client_credentials": []string{"pass1234"}, + }, + }, + { + name: "auth header match canonicalized headers", + params: authParams{ + Type: authTypeHeader, + Params: map[string]interface{}{ + "camelCaseHeader": "123455", + }, + }, + exp: http.Header{ + "Camelcaseheader": []string{"123455"}, + }, + }, + { + name: "auth basic match headers", + params: authParams{ + Type: authTypeBasic, + Params: map[string]interface{}{ + "username": "user", + "password": "pass1234", + }, + }, + exp: http.Header{"Authorization": []string{"Basic dXNlcjpwYXNzMTIzNA=="}}, + }, + { + name: "auth basic match headers fail user validation", + params: authParams{ + Type: authTypeBasic, + Params: map[string]interface{}{"password": "pass1234"}, + }, + exp: http.Header{}, + errv: "invalid param username", + }, + { + name: "auth basic match headers fail pass validation", + params: authParams{ + Type: authTypeBasic, + Params: map[string]interface{}{"username": "user"}, + }, + exp: http.Header{}, + errv: "invalid param password", + }, + { + name: "noop default fallback", + params: authParams{}, + exp: http.Header{}, + }, + } + ) + + for _, tc := range tcc { + t.Run(tc.name, func(t *testing.T) { + var ( + req = require.New(t) + c = http.DefaultClient + ) + + c.Transport = mockRoundTripper(func(r *http.Request) (rs *http.Response, err error) { return }) + + rq, _ := http.NewRequest("POST", "/foo", http.NoBody) + + authServicer, err := NewAuthServicer(c, tc.params) + + if tc.errv != "" { + req.EqualError(err, tc.errv) + return + } + + err = authServicer.Do(rq) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.Equal(tc.exp, rq.Header) + } + }) + } +} diff --git a/pkg/apigw/error.go b/pkg/apigw/error.go new file mode 100644 index 000000000..689b05458 --- /dev/null +++ b/pkg/apigw/error.go @@ -0,0 +1,18 @@ +package apigw + +import ( + "context" + "net/http" +) + +type ( + defaultErrorHandler struct{} +) + +func (h defaultErrorHandler) Exec(ctx context.Context, scope *scp, err error) { + // set http status code + scope.Writer().WriteHeader(http.StatusInternalServerError) + + // set body + scope.Writer().Write([]byte(err.Error())) +} diff --git a/pkg/apigw/expediter.go b/pkg/apigw/expediter.go index 9b9dfeb25..981aeab74 100644 --- a/pkg/apigw/expediter.go +++ b/pkg/apigw/expediter.go @@ -1,17 +1,22 @@ package apigw import ( + "bytes" "context" "encoding/json" "fmt" "net/http" - - "github.com/cortezaproject/corteza-server/system/types" - "github.com/davecgh/go-spew/spew" + "net/url" ) type ( - expediterRedirection struct{} + expediterRedirection struct { + functionMeta + params struct { + HTTPStatus int `json:"status,string"` + Location string `json:"location"` + } + } errorHandler struct { name string @@ -21,41 +26,91 @@ type ( } ) -func NewExpediterRedirection() expediterRedirection { - return expediterRedirection{} +func NewExpediterRedirection() (e *expediterRedirection) { + e = &expediterRedirection{} + + e.Step = 3 + e.Name = "expediterRedirection" + e.Label = "Redirection expediter" + e.Kind = FunctionKindExpediter + + e.Args = []*functionMetaArg{ + { + Type: "status", + Label: "status", + Options: map[string]interface{}{}, + }, + { + Type: "text", + Label: "location", + Options: map[string]interface{}{}, + }, + } + + return } -func (h expediterRedirection) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 3, - Name: "expediterRedirection", - Label: "Redirection expediter", - Kind: "expediter", - Weight: int(f.Weight), - Params: f.Params, - } +func (h expediterRedirection) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) } -func (h expediterRedirection) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - scope.Writer().Header().Add(fmt.Sprintf("step_%d", ff.step), ff.name) - http.Redirect(scope.Writer(), scope.Request(), params["location"].(string), http.StatusFound) +func (h expediterRedirection) Meta() functionMeta { + return h.functionMeta +} - return nil +func (f *expediterRedirection) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) + return f, err +} + +func (h expediterRedirection) Exec(ctx context.Context, scope *scp) error { + loc, err := url.ParseRequestURI(h.params.Location) + + if err != nil { + return fmt.Errorf("could not redirect: %s", err) } + + status := h.params.HTTPStatus + + if !checkStatus("redirect", status) { + return fmt.Errorf("could not redirect: wrong status %d", status) + } + + http.Redirect(scope.Writer(), scope.Request(), loc.String(), status) + + return nil } func (pp errorHandler) Exec(ctx context.Context, scope *scp, err error) { type ( responseHelper struct { - Msg string `json:"msg"` + ErrResponse struct { + Msg string `json:"msg"` + } `json:"error"` } ) resp := responseHelper{ - Msg: err.Error(), + ErrResponse: struct { + Msg string "json:\"msg\"" + }{ + Msg: err.Error(), + }, } - spew.Dump("ERR in expediter", err, resp) + // set http status code + scope.Writer().WriteHeader(http.StatusInternalServerError) + + // set body json.NewEncoder(scope.Writer()).Encode(resp) + +} + +func checkStatus(typ string, status int) bool { + switch typ { + case "redirect": + return status >= 300 && status <= 399 + default: + return true + } } diff --git a/pkg/apigw/expediter_test.go b/pkg/apigw/expediter_test.go new file mode 100644 index 000000000..14661d858 --- /dev/null +++ b/pkg/apigw/expediter_test.go @@ -0,0 +1,74 @@ +package apigw + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_expediterRedirection(t *testing.T) { + type ( + tf struct { + name string + expr string + err string + } + ) + + var ( + tcc = []tf{ + { + name: "simple redirection", + expr: `{"status":"302", "location": "http://redire.ct/to"}`, + }, + { + name: "permanent redirection", + expr: `{"status":"301", "location": "http://redire.ct/to"}`, + }, + { + name: "url validation", + expr: `{"status":"301", "location": "invalid url"}`, + err: `could not redirect: parse "invalid url": invalid URI for request`, + }, + { + name: "invalid redirection status", + expr: `{"status":"400", "location": "http://redire.ct/to"}`, + err: "could not redirect: wrong status 400", + }, + } + ) + + for _, tc := range tcc { + var ( + ctx = context.Background() + ) + + t.Run(tc.name, func(t *testing.T) { + req := require.New(t) + + r, err := http.NewRequest(http.MethodGet, "/foo", http.NoBody) + + req.NoError(err) + + rc := httptest.NewRecorder() + scope := &scp{"request": r, "writer": rc} + + h := NewExpediterRedirection() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + return + } + + req.NoError(err) + req.Equal(h.params.Location, rc.Header().Get("Location")) + req.Equal(h.params.HTTPStatus, rc.Code) + }) + } +} diff --git a/pkg/apigw/function.go b/pkg/apigw/function.go index 586b7d0be..7b9082a23 100644 --- a/pkg/apigw/function.go +++ b/pkg/apigw/function.go @@ -1,75 +1,52 @@ package apigw -import ( - "context" - - "github.com/cortezaproject/corteza-server/system/types" +const ( + FunctionKindVerifier FunctionKind = "verifier" + FunctionKindValidator FunctionKind = "validator" + FunctionKindProcesser FunctionKind = "processer" + FunctionKindExpediter FunctionKind = "expediter" ) type ( - functionMetaList []*functionMeta + FunctionKind string Handler interface { - Handler() handlerFunc - Meta(f *types.Function) functionMeta - } + Execer + Stringer - handlerFunc func(context.Context, *scp, map[string]interface{}, functionHandler) error + Merge([]byte) (Handler, error) + Meta() functionMeta + } functionMeta struct { - Step int `json:"step"` - Weight int `json:"-"` - Name string `json:"name"` - Label string `json:"label"` - Kind string `json:"kind"` - Params map[string]interface{} `json:"-"` - Args []*functionMetaArg `json:"params,omitempty"` + Step int `json:"step"` + Weight int `json:"-"` + Name string `json:"name"` + Label string `json:"label"` + Kind FunctionKind `json:"kind"` + Args []*functionMetaArg `json:"params,omitempty"` } + functionMetaList []*functionMeta + functionMetaArg struct { Label string `json:"label"` Type string `json:"type"` Example string `json:"example"` Options map[string]interface{} `json:"options"` } - - functionHandler struct { - step int - weight int - name string - label string - kind string - handler handlerFunc - params map[string]interface{} - } ) -func (ff functionHandler) Exec(ctx context.Context, scope *scp, params map[string]interface{}) error { - return ff.handler(ctx, scope, params, ff) -} - -func (ff *functionHandler) SetHandler(h handlerFunc) { - ff.handler = h -} - -func (ff *functionHandler) Merge(ctx context.Context, p functionMeta) { - ff.step = p.Step - ff.kind = p.Kind - ff.label = p.Label - ff.name = p.Name - ff.weight = p.Weight - ff.params = p.Params -} - -func (ff functionHandler) Weight() int { - // if there's gonna be more than 1000 funcs - // per step, we're doing something wrong - return ff.step*1000 + ff.weight -} +// func (ff functionHandler) Weight() int { +// // if there's gonna be more than 1000 funcs +// // per step, we're doing something wrong +// return ff.step*1000 + ff.weight +// } func (fm functionMetaList) Filter(f func(*functionMeta) (bool, error)) (out functionMetaList, err error) { var ok bool out = functionMetaList{} + for i := range fm { if ok, err = f(fm[i]); err != nil { return diff --git a/pkg/apigw/helpers.go b/pkg/apigw/helpers.go new file mode 100644 index 000000000..62367ff72 --- /dev/null +++ b/pkg/apigw/helpers.go @@ -0,0 +1,21 @@ +package apigw + +import ( + "net/http" + + "github.com/cortezaproject/corteza-server/pkg/options" +) + +const ( + devHelperResponseBody string = `Hey developer!` +) + +func helperDefaultResponse(opt *options.ApigwOpt) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if opt.LogEnabled { + http.Error(w, devHelperResponseBody, http.StatusTeapot) + } else { + http.Error(w, ``, http.StatusFound) + } + } +} diff --git a/pkg/apigw/matcher_test.go b/pkg/apigw/matcher_test.go deleted file mode 100644 index 5a29e46e9..000000000 --- a/pkg/apigw/matcher_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package apigw - -import ( - "context" - "net/http" - "testing" - - "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/stretchr/testify/require" -) - -func TestAuthenticationOriginMatcher(t *testing.T) { - type ( - tf struct { - name string - origin string - exp string - req *http.Request - } - ) - - var ( - ctx = context.Background() - - tcc = []tf{ - { - name: "fail on origin", - origin: "http://fail.ed", - exp: "workflow 0 step 0 execution failed: origin fail", - - req: &http.Request{ - Header: http.Header{ - "Origin": []string{ - "http://localhost", - }, - }, - }, - }, - { - name: "success on origin", - origin: "http://localhost", - exp: "", - - req: &http.Request{ - Header: http.Header{ - "Origin": []string{ - "http://localhost", - }, - }, - }, - }, - } - ) - - for _, tc := range tcc { - t.Run(tc.name, func(t *testing.T) { - var ( - req = require.New(t) - input = &expr.Vars{} - ) - - input.Set("origin", tc.origin) - - err := execFn(t, tc.req, authenticationOriginMatcher(ctx, input)) - - if tc.exp != "" { - req.EqualError(err, tc.exp) - } else { - req.NoError(err) - } - }) - } - -} diff --git a/pkg/apigw/pipeline.go b/pkg/apigw/pipeline.go index c2f347d31..b6712c93d 100644 --- a/pkg/apigw/pipeline.go +++ b/pkg/apigw/pipeline.go @@ -2,13 +2,16 @@ package apigw import ( "context" + "fmt" "net/http" - "sort" + + "github.com/cortezaproject/corteza-server/pkg/options" + "go.uber.org/zap" ) type ( Execer interface { - Exec(context.Context, *scp, map[string]interface{}) error + Exec(context.Context, *scp) error } Sorter interface { @@ -19,26 +22,35 @@ type ( Exec(context.Context, *scp, error) } - Payload struct { - params map[string]interface{} - worker Worker - } - Worker interface { Execer - Sorter + // Sorter + Stringer } - workers []Payload + Stringer interface { + String() string + } + + workers []Worker pl struct { w workers err ErrorHandler + log *zap.Logger } scp map[string]interface{} ) +func NewPipeline(log *zap.Logger, w ...Worker) *pl { + return &pl{ + w: w, + log: log, + err: defaultErrorHandler{}, + } +} + func (s scp) Request() *http.Request { if _, ok := s["request"]; ok { return s["request"].(*http.Request) @@ -55,19 +67,39 @@ func (s scp) Writer() http.ResponseWriter { return nil } +func (s scp) Opts() *options.ApigwOpt { + if _, ok := s["opts"]; ok { + return s["opts"].(*options.ApigwOpt) + } + + return nil +} + func (s scp) Set(k string, v interface{}) { s[k] = v } +func (s scp) Get(k string) (v interface{}, err error) { + var ok bool + + if v, ok = s[k]; !ok { + err = fmt.Errorf("could not get key on index: %s", k) + return + } + + return +} + // Exec takes care of error handling and main // functionality that takes place in worker func (pp *pl) Exec(ctx context.Context, scope *scp) (err error) { for _, w := range pp.w { - err = w.worker.Exec(ctx, scope, w.params) + + pp.log.Debug("executing worker", zap.Any("worker", w.String())) + err = w.Exec(ctx, scope) if err != nil { - // call the error handler - pp.err.Exec(ctx, scope, err) + pp.log.Debug("could not execute worker", zap.Error(err)) return } } @@ -76,10 +108,12 @@ func (pp *pl) Exec(ctx context.Context, scope *scp) (err error) { } // Add registers a new worker with parameters -// fethed from store -func (pp *pl) Add(ff Worker, p map[string]interface{}) { - pp.w = append(pp.w, Payload{worker: ff, params: p}) - sort.Sort(pp.w) +// fetched from store +func (pp *pl) Add(w Worker) { + pp.w = append(pp.w, w) + // sort.Sort(pp.w) + + pp.log.Debug("registered worker", zap.Any("worker", w.String())) } // add error handler @@ -87,8 +121,8 @@ func (pp *pl) ErrorHandler(ff ErrorHandler) { pp.err = ff } -func (a workers) Len() int { return len(a) } -func (a workers) Less(i, j int) bool { - return a[i].worker.Weight() < a[j].worker.Weight() -} -func (a workers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +// func (a workers) Len() int { return len(a) } +// func (a workers) Less(i, j int) bool { +// return a[i].worker.Weight() < a[j].worker.Weight() +// } +// func (a workers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/pkg/apigw/pipeline_test.go b/pkg/apigw/pipeline_test.go new file mode 100644 index 000000000..fd4520afc --- /dev/null +++ b/pkg/apigw/pipeline_test.go @@ -0,0 +1,69 @@ +package apigw + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func NewPl() *pl { + return NewPipeline(zap.NewNop()) +} + +func Test_pipelineAdd(t *testing.T) { + var ( + req = require.New(t) + ) + + p := NewPl() + p.Add(mockExecer{}) + + req.Len(p.w, 1) +} + +func Test_pipelineExec(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + scope = &scp{"foo": 1} + ) + + p := NewPl() + p.Add(mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Set("foo", 2) + return nil + }, + }) + + err := p.Exec(ctx, scope) + + req.NoError(err) + + foo, err := scope.Get("foo") + + req.NoError(err) + req.Equal(2, foo) +} + +func Test_pipelineExecErr(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + scope = &scp{"foo": 1} + ) + + p := NewPl() + p.Add(mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + return fmt.Errorf("error returned") + }, + }) + + err := p.Exec(ctx, scope) + + req.Error(err, "error returned") +} diff --git a/pkg/apigw/processer.go b/pkg/apigw/processer.go index 56fd11c11..3da66d3ef 100644 --- a/pkg/apigw/processer.go +++ b/pkg/apigw/processer.go @@ -1,12 +1,32 @@ package apigw import ( + "bytes" "context" + "encoding/json" "fmt" + "io" + "net/http" + "net/http/httputil" + "net/url" + "time" atypes "github.com/cortezaproject/corteza-server/automation/types" "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/cortezaproject/corteza-server/system/types" + "go.uber.org/zap" +) + +var ( + hopHeaders = []string{ + "Connection", + "Keep-Alive", + "Proxy-Authenticate", + "Proxy-Authorization", + "Te", + "Trailers", + "Transfer-Encoding", + "Upgrade", + } ) type ( @@ -15,93 +35,252 @@ type ( } processerWorkflow struct { + functionMeta d WfExecer + + params struct { + Workflow uint64 `json:"workflow"` + } + } + + processerProxy struct { + functionMeta + a AuthServicer + c *http.Client + log *zap.Logger + + params struct { + Location string `json:"location"` + Auth authParams `json:"auth"` + } } ) -func NewProcesserWorkflow(wf WfExecer) processerWorkflow { - return processerWorkflow{ - d: wf, - } -} +func NewProcesserWorkflow(wf WfExecer) (p *processerWorkflow) { + p = &processerWorkflow{} -func (h processerWorkflow) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 2, - Name: "processerWorkflow", - Label: "Workflow processer", - Kind: "processer", - Weight: int(f.Weight), - Params: f.Params, - Args: []*functionMetaArg{ - { - Type: "workflow", - Label: "workflow", - Options: map[string]interface{}{}, - }, + p.d = wf + + p.Step = 2 + p.Name = "processerWorkflow" + p.Label = "Workflow processer" + p.Kind = FunctionKindProcesser + + p.Args = []*functionMetaArg{ + { + Type: "workflow", + Label: "workflow", + Options: map[string]interface{}{}, }, } + + return } -func (h processerWorkflow) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - var ( - wfID int64 - ok bool - err error - ) +func (h processerWorkflow) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} - // validate workflow param - if _, ok = params["workflow"]; !ok { - return fmt.Errorf("invalid param workflow") - } +func (h processerWorkflow) Meta() functionMeta { + return h.functionMeta +} - wfID, err = expr.CastToInteger(params["workflow"]) +func (f *processerWorkflow) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) - if err != nil { - return err - } + return f, err +} - // setup scope for workflow - vv := map[string]interface{}{ - "request": scope.Request(), - } +func (h processerWorkflow) Exec(ctx context.Context, scope *scp) error { + var ( + err error + ) - // get the request data and put it into vars - in, err := expr.NewVars(vv) + // setup scope for workflow + vv := map[string]interface{}{ + "request": scope.Request(), + } - if err != nil { - return err - } - - wp := atypes.WorkflowExecParams{ - Trace: false, - // todo depending on settings per-route - Async: false, - // todo depending on settings per-route - Wait: true, - Input: in, - } - - out, _, err := h.d.Exec(ctx, uint64(wfID), wp) - - if err != nil { - return err - } - - // merge out with scope - merged, err := in.Merge(out) - - if err != nil { - return err - } - - mm, err := expr.CastToVars(merged) - - for k, v := range mm { - scope.Set(k, v) - } + // get the request data and put it into vars + in, err := expr.NewVars(vv) + if err != nil { return err } + + wp := atypes.WorkflowExecParams{ + Trace: false, + // todo depending on settings per-route + Async: false, + // todo depending on settings per-route + Wait: true, + Input: in, + } + + out, _, err := h.d.Exec(ctx, uint64(h.params.Workflow), wp) + + if err != nil { + return err + } + + // merge out with scope + merged, err := in.Merge(out) + + if err != nil { + return err + } + + mm, err := expr.CastToVars(merged) + + for k, v := range mm { + scope.Set(k, v) + } + + return err +} + +func NewProcesserProxy(l *zap.Logger, c *http.Client) (p *processerProxy) { + p = &processerProxy{} + + p.c = c + p.log = l + + p.Step = 2 + p.Name = "processerProxy" + p.Label = "Proxy processer" + p.Kind = FunctionKindProcesser + + p.Args = []*functionMetaArg{ + { + Type: "text", + Label: "location", + Options: map[string]interface{}{}, + }, + } + + return +} + +func (h processerProxy) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} + +func (h processerProxy) Meta() functionMeta { + return h.functionMeta +} + +func (f *processerProxy) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) + + if err != nil { + return nil, err + } + + // get the auth mechanism + f.a, err = NewAuthServicer(f.c, f.params.Auth) + + if err != nil { + return nil, fmt.Errorf("could not load auth servicer for proxying: %s", err) + } + + return f, err +} + +func (h processerProxy) Exec(ctx context.Context, scope *scp) (err error) { + ctx, cancel := context.WithTimeout(ctx, scope.Opts().ProxyOutboundTimeout) + defer cancel() + + req := scope.Request() + log := h.log.With(zap.String("ref", h.Name)) + + outreq := req.Clone(ctx) + + l, err := url.ParseRequestURI(h.params.Location) + + if err != nil { + return fmt.Errorf("could not parse destination location for proxying: %s", err) + } + + // should we preserve query params? headers? post data? + outreq.URL = l + outreq.RequestURI = "" + outreq.Method = req.Method + outreq.Host = l.Hostname() + + // use authservicer, set any additional headers + err = h.a.Do(outreq) + + if err != nil { + return fmt.Errorf("errors setting auth for proxying: %s", err) + } + + // merge the old query params to the new request + // do not overwrite old ones + // do it after the authServicer, since we also may add them there + mergeQueryParams(req, outreq) + + if scope.Opts().ProxyEnableDebugLog { + o, _ := httputil.DumpRequestOut(outreq, false) + log.Debug("proxy outbound request", zap.Any("request", string(o))) + } + + // temporary metrics before the proper functionality + startTime := time.Now() + + // todo - disable / enable follow redirects, already + // added to options + resp, err := h.c.Do(outreq) + + if err != nil { + return fmt.Errorf("could not proxy request: %s", err) + } + + if scope.Opts().ProxyEnableDebugLog { + o, _ := httputil.DumpResponse(resp, false) + log.Debug("proxy outbound response", zap.Any("request", string(o)), zap.Duration("duration", time.Since(startTime))) + } + + b, err := io.ReadAll(resp.Body) + + if err != nil { + return fmt.Errorf("could not read get body on proxy request: %s", err) + } + + mergeHeaders(resp.Header, scope.Writer().Header()) + + // add to writer + scope.Writer().Write(b) + + return nil +} + +func mergeHeaders(orig, dest http.Header) { +OUTER: + for name, values := range orig { + // skip headers that need to be omitted + // when proxying + for _, v := range hopHeaders { + if v == name { + continue OUTER + } + } + dest[name] = values + } +} + +func mergeQueryParams(orig, dest *http.Request) { + origValues := dest.URL.Query() + + for k, qp := range orig.URL.Query() { + // skip existing + if dest.URL.Query().Get(k) != "" { + continue + } + + for _, v := range qp { + origValues.Add(k, v) + } + } + + dest.URL.RawQuery = origValues.Encode() } diff --git a/pkg/apigw/processer_test.go b/pkg/apigw/processer_test.go new file mode 100644 index 000000000..94476bf2c --- /dev/null +++ b/pkg/apigw/processer_test.go @@ -0,0 +1,195 @@ +package apigw + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/cortezaproject/corteza-server/pkg/options" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func Test_processerProxy(t *testing.T) { + type ( + exp struct { + Status int + Header http.Header + Body *bytes.Buffer + } + + tf struct { + name string + err string + params string + exp exp + rq *http.Request + fn func(*require.Assertions) mockRoundTripper + } + ) + + var ( + tcc = []tf{ + { + name: "proxy processer with auth headers", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("default response")), + } + + return + } + }, + params: `{"location": "/foo", "auth": {"type": "header", "params": {"access-token": "123", "client": "456"}}}`, + exp: exp{ + Status: http.StatusOK, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBufferString("default response"), + }, + }, + { + name: "proxy processer with auth query params", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{} + req.Equal("access-param=123%2B456", r.URL.RawQuery) + return + } + }, + params: `{"location": "/foo", "auth": {"type": "query", "params": {"access-param": "123+456"}}}`, + exp: exp{ + Status: http.StatusOK, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBuffer(nil), + }, + }, + { + name: "proxy processer with auth headers unauthorized", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{ + StatusCode: http.StatusUnauthorized, + Body: io.NopCloser(strings.NewReader("unauthorized response")), + } + + return + } + }, + params: `{"location": "/foo", "auth": {"type": "header", "params": {"access-token": "123", "client": "456"}}}`, + exp: exp{ + Status: http.StatusUnauthorized, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBufferString("unauthorized response"), + }, + }, + { + name: "proxy processer params parse error", + params: `{"location": "invalid url", "auth": {"type": "header", "params": {}}}`, + err: `could not parse destination location for proxying: parse "invalid url": invalid URI for request`, + }, + { + name: "proxy processer params request error", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + err = fmt.Errorf("error on client.Do") + return + } + }, + params: `{"location": "https://example.com", "auth": {"type": "header", "params": {}}}`, + err: `could not proxy request: Post "https://example.com": error on client.Do`, + }, + { + name: "proxy processer hop headers removed", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{ + Header: http.Header{ + "Proxy-Authenticate": []string{`Basic realm="Access to the internal site"`}, + "Content-Type": []string{"application/json; charset=utf-8"}, + }, + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("default response")), + } + + return + } + }, + params: `{"location": "https://example.com", "auth": {"type": "header", "params": {}}}`, + exp: exp{ + Status: http.StatusUnauthorized, + Header: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}}, + Body: bytes.NewBufferString("default response"), + }, + }, + { + name: "proxy processer query parameters merged", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{} + req.Equal("access-param=123%2B456&addedCustomQueryParam=true", r.URL.RawQuery) + return + } + }, + params: `{"location": "https://example.com", "auth": {"type": "query", "params": {"access-param": "123+456"}}}`, + exp: exp{ + Status: http.StatusUnauthorized, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBuffer(nil), + }, + rq: &http.Request{ + Header: http.Header{}, + URL: &url.URL{Path: "/foo", RawQuery: "addedCustomQueryParam=true"}, + Body: http.NoBody, + Method: "POST", + }, + }, + } + ) + + for _, tc := range tcc { + + t.Run(tc.name, func(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + c = http.DefaultClient + rq = tc.rq + ) + + if tc.fn != nil { + c.Transport = mockRoundTripper(tc.fn(req)) + } + + if rq == nil { + rq, _ = http.NewRequest("POST", "/foo", strings.NewReader(`custom request body`)) + } + + proxy := NewProcesserProxy(zap.NewNop(), c) + proxy.Merge([]byte(tc.params)) + + scope := &scp{ + "request": rq, + "writer": httptest.NewRecorder(), + "opts": options.Apigw(), + } + + err := proxy.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.NoError(err) + req.Equal(tc.exp.Header, scope.Writer().(*httptest.ResponseRecorder).Header()) + req.Equal(tc.exp.Body, scope.Writer().(*httptest.ResponseRecorder).Body) + } + }) + } +} diff --git a/pkg/apigw/registry.go b/pkg/apigw/registry.go index 0f7e6362b..1497c72f4 100644 --- a/pkg/apigw/registry.go +++ b/pkg/apigw/registry.go @@ -2,11 +2,9 @@ package apigw import ( "fmt" + "net/http" "github.com/cortezaproject/corteza-server/automation/service" - as "github.com/cortezaproject/corteza-server/automation/service" - "github.com/cortezaproject/corteza-server/pkg/options" - "github.com/cortezaproject/corteza-server/system/types" ) type ( @@ -25,6 +23,11 @@ func (r *registry) Add(n string, h Handler) { r.h[n] = h } +func (r *registry) Merge(h Handler, b []byte) (hh Handler, err error) { + hh, err = h.Merge(b) + return +} + func (r *registry) Get(identifier string) (Handler, error) { var ( ok bool @@ -40,8 +43,8 @@ func (r *registry) Get(identifier string) (Handler, error) { func (r *registry) All() (list functionMetaList) { for _, handler := range r.h { - m := handler.Meta(&types.Function{}) - list = append(list, &m) + meta := handler.Meta() + list = append(list, &meta) } return @@ -50,10 +53,8 @@ func (r *registry) All() (list functionMetaList) { func (r *registry) Preload() { r.Add("verifierQueryParam", NewVerifierQueryParam()) r.Add("verifierOrigin", NewVerifierOrigin()) + r.Add("validatorHeader", NewValidatorHeader()) r.Add("expediterRedirection", NewExpediterRedirection()) r.Add("processerWorkflow", NewProcesserWorkflow(NewWorkflow())) -} - -func NewWorkflow() WfExecer { - return as.Workflow(service.DefaultLogger, options.CorredorOpt{}) + r.Add("processerProxy", NewProcesserProxy(service.DefaultLogger, http.DefaultClient)) } diff --git a/pkg/apigw/registry_test.go b/pkg/apigw/registry_test.go new file mode 100644 index 000000000..a6f690a6c --- /dev/null +++ b/pkg/apigw/registry_test.go @@ -0,0 +1,100 @@ +package apigw + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_registryAddGet(t *testing.T) { + var ( + req = require.New(t) + r = NewRegistry() + ) + + r.Add("mockHandler", mockHandler{}) + + h, err := r.Get("mockHandler") + + req.NoError(err) + req.Len(r.h, 1) + req.IsType(mockHandler{}, h) +} + +func Test_registryAddGetErr(t *testing.T) { + var ( + req = require.New(t) + r = NewRegistry() + ) + + r.Add("mockHandler", mockHandler{}) + + h, err := r.Get("foo") + + req.EqualError(err, "could not get element from registry: foo") + req.Len(r.h, 1) + req.Nil(h) +} + +func Test_registryMerge(t *testing.T) { + type ( + tf struct { + name string + err string + params string + exp string + } + ) + + var ( + tcc = []tf{ + { + name: "set params", + params: `{"foo":"bar"}`, + exp: "bar", + }, + { + name: "set invalid params", + params: `{"foo1":"bar"}`, + exp: "", + }, + { + name: "set invalid params err", + params: `{"foo1":"bar"`, + exp: "", + err: "unexpected EOF", + }, + } + ) + + for _, tc := range tcc { + var ( + req = require.New(t) + r = NewRegistry() + ) + + m, err := r.Merge(mockHandler{}, []byte(tc.params)) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.Equal(m.(mockHandler).Foo, tc.exp) + req.NoError(err) + } + } + +} + +func Test_registryAll(t *testing.T) { + var ( + req = require.New(t) + r = NewRegistry() + ) + + r.Add("mockHandler", mockHandler{}) + + list := r.All() + + req.Len(list, 1) + req.NotEmpty(list[0].Name) +} diff --git a/pkg/apigw/route.go b/pkg/apigw/route.go index 0ee70493a..19f3e5f9e 100644 --- a/pkg/apigw/route.go +++ b/pkg/apigw/route.go @@ -1,8 +1,12 @@ package apigw import ( - "context" + "fmt" "net/http" + "net/http/httputil" + + "github.com/cortezaproject/corteza-server/pkg/options" + "go.uber.org/zap" ) type ( @@ -11,22 +15,50 @@ type ( endpoint string method string + opts *options.ApigwOpt + log *zap.Logger pipe *pl } ) func (r route) ServeHTTP(w http.ResponseWriter, req *http.Request) { var ( - ctx = context.Background() + ctx = req.Context() scope = scp{} ) - scope["request"] = req - scope["writer"] = w + scope.Set("request", req) + scope.Set("writer", w) + scope.Set("opts", r.opts) + + if err := r.validate(req); err != nil { + r.log.Debug("error validating request on route", zap.Error(err)) + r.pipe.err.Exec(ctx, &scope, fmt.Errorf("could not validate request: %s", err)) + return + } + + if r.opts.LogEnabled { + o, _ := httputil.DumpRequest(req, false) + r.log.Debug("incoming request", zap.Any("request", string(o))) + } err := r.pipe.Exec(ctx, &scope) if err != nil { - // log error + // call the error handler + r.log.Debug("calling default error handler on error") + r.pipe.err.Exec(ctx, &scope, err) } } + +func (r route) validate(req *http.Request) (err error) { + if req.Method != r.method { + err = fmt.Errorf("invalid method %s", req.Method) + } + + return +} + +func (r route) String() string { + return fmt.Sprintf("%s %s", r.method, r.endpoint) +} diff --git a/pkg/apigw/route_test.go b/pkg/apigw/route_test.go new file mode 100644 index 000000000..627417deb --- /dev/null +++ b/pkg/apigw/route_test.go @@ -0,0 +1,117 @@ +package apigw + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cortezaproject/corteza-server/pkg/options" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func Test_pl(t *testing.T) { + type ( + tf struct { + name string + handler Worker + method string + errHandler ErrorHandler + expStatus int + expError error + } + ) + + var ( + tcc = []tf{ + { + name: "successful exec", + handler: mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Writer().WriteHeader(http.StatusTemporaryRedirect) + return + }, + }, + errHandler: mockErrorExecer{ + exec: func(c context.Context, s *scp, e error) { + s.Writer().Write([]byte(e.Error())) + }, + }, + method: "POST", + expStatus: http.StatusTemporaryRedirect, + expError: nil, + }, + { + name: "unsuccessful exec", + handler: mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Writer().WriteHeader(http.StatusTemporaryRedirect) + return errors.New("test error") + }, + }, + errHandler: mockErrorExecer{ + exec: func(c context.Context, s *scp, e error) { + s.Writer().WriteHeader(http.StatusInternalServerError) + s.Writer().Write([]byte(e.Error())) + }, + }, + method: "POST", + expStatus: http.StatusTemporaryRedirect, + expError: errors.New("test error"), + }, + { + name: "request method validation fail", + handler: mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Writer().WriteHeader(http.StatusTemporaryRedirect) + return errors.New("test error") + }, + }, + errHandler: mockErrorExecer{ + exec: func(c context.Context, s *scp, e error) { + s.Writer().WriteHeader(http.StatusInternalServerError) + s.Writer().Write([]byte(e.Error())) + }, + }, + method: "GET", + expStatus: http.StatusInternalServerError, + expError: errors.New("could not validate request: invalid method POST"), + }, + } + ) + + for _, tc := range tcc { + t.Run(tc.name, func(t *testing.T) { + var ( + req = require.New(t) + rr = httptest.NewRecorder() + pipe = NewPl() + ) + + r, err := http.NewRequest("POST", "/foo", http.NoBody) + req.NoError(err) + + pipe.Add(tc.handler) + pipe.ErrorHandler(tc.errHandler) + + route := &route{ + method: tc.method, + pipe: pipe, + log: zap.NewNop(), + opts: options.Apigw(), + } + + route.ServeHTTP(rr, r) + + expError := "" + if tc.expError != nil { + expError = tc.expError.Error() + } + + req.Equal(tc.expStatus, rr.Result().StatusCode) + req.Equal(expError, rr.Body.String()) + }) + } +} diff --git a/pkg/apigw/test.go b/pkg/apigw/test.go new file mode 100644 index 000000000..21bb6a7b6 --- /dev/null +++ b/pkg/apigw/test.go @@ -0,0 +1,67 @@ +package apigw + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + mockExecer struct { + exec func(context.Context, *scp) (err error) + } + + mockErrorExecer struct { + exec func(context.Context, *scp, error) + } + + mockHandler struct { + Foo string `json:"foo"` + } + + mockStorer struct { + f func(context.Context, types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) + r func(context.Context, types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) + } +) + +func (h mockHandler) String() string { + return "mockHandler" +} + +func (h mockHandler) Exec(_ context.Context, _ *scp) error { + panic("not implemented") // TODO: Implement +} + +func (h mockHandler) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h) + return h, err +} + +func (h mockHandler) Meta() functionMeta { + return functionMeta{ + Name: "return mocked function metadata", + } +} + +func (td mockStorer) SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (s types.ApigwRouteSet, ff types.ApigwRouteFilter, err error) { + return td.r(ctx, f) +} + +func (td mockStorer) SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (s types.ApigwFunctionSet, ff types.ApigwFunctionFilter, err error) { + return td.f(ctx, f) +} + +func (me mockExecer) String() string { + return "mockExecer" +} + +func (me mockExecer) Exec(ctx context.Context, s *scp) (err error) { + return me.exec(ctx, s) +} + +func (me mockErrorExecer) Exec(ctx context.Context, s *scp, e error) { + me.exec(ctx, s, e) +} diff --git a/pkg/apigw/validator.go b/pkg/apigw/validator.go new file mode 100644 index 000000000..611c5662c --- /dev/null +++ b/pkg/apigw/validator.go @@ -0,0 +1,87 @@ +package apigw + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/cortezaproject/corteza-server/pkg/expr" +) + +type ( + validatorHeader struct { + functionMeta + params struct { + Expr string `json:"expr"` + } + } +) + +func NewValidatorHeader() (v *validatorHeader) { + v = &validatorHeader{} + + v.Step = 3 + v.Name = "validatorHeader" + v.Label = "Header validator" + v.Kind = FunctionKindValidator + + v.Args = []*functionMetaArg{ + { + Type: "expr", + Label: "expr", + Options: map[string]interface{}{}, + }, + } + + return +} + +func (h validatorHeader) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} + +func (h validatorHeader) Meta() functionMeta { + return h.functionMeta +} + +func (v *validatorHeader) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) + return v, err +} + +func (h validatorHeader) Exec(ctx context.Context, scope *scp) error { + vv := map[string]interface{}{} + headers := scope.Request().Header + + for k, v := range headers { + // sanitize header keys? + vv[k] = v[0] + } + + // get the request data and put it into vars + out, err := expr.NewVars(vv) + + if err != nil { + return err + } + + pp := expr.NewParser() + tt, err := pp.Parse(h.params.Expr) + + if err != nil { + return fmt.Errorf("could not parse matching expression: %s", err) + } + + b, err := tt.Test(ctx, out) + + if err != nil { + return fmt.Errorf("could not validate headers: %s", err) + } + + if !b { + return fmt.Errorf("could not validate headers") + } + + return nil +} diff --git a/pkg/apigw/validator_test.go b/pkg/apigw/validator_test.go index d74b4bb47..36b63cf59 100644 --- a/pkg/apigw/validator_test.go +++ b/pkg/apigw/validator_test.go @@ -3,58 +3,75 @@ package apigw import ( "context" "net/http" - "net/http/httptest" - "strings" "testing" - "github.com/cortezaproject/corteza-server/pkg/expr" "github.com/stretchr/testify/require" ) -func TestContentLengthValidator(t *testing.T) { +func Test_validatorHeader(t *testing.T) { type ( tf struct { - name string - limit int - exp string - body string + name string + expr string + err string + headers http.Header } ) var ( - ctx = context.Background() - tcc = []tf{ { - name: "fail on content length > limit", - limit: 10, - exp: "workflow 0 step 0 execution failed: content length overriden", - body: "A message that is 31 bytes long", + name: "matching simple", + expr: `{"expr":"foo == \"bar\""}`, + headers: map[string][]string{"foo": {"bar"}}, }, { - name: "success on content length < limit", - limit: 10, - exp: "", - body: "Below 10", + name: "matching case", + expr: `{"expr":"Foo == \"bar\""}`, + headers: map[string][]string{"Foo": {"bar"}}, + }, + { + name: "non matching value", + expr: `{"expr":"Foo == \"bar1\""}`, + headers: map[string][]string{"Foo": {"bar"}}, + err: "could not validate headers", + }, + { + name: "non matching key", + expr: `{"expr":"Foo1 == \"bar\""}`, + headers: map[string][]string{"Foo": {"bar"}}, + err: "could not validate headers: failed to select 'Foo1' on *expr.Vars: no such key 'Foo1'", + }, + { + name: "matching header with hyphen - TODO", + expr: `{"expr":"Content-type == \"application/json\""}`, + headers: map[string][]string{"Content-type": {"application/json"}}, }, } ) for _, tc := range tcc { + var ( + ctx = context.Background() + ) + t.Run(tc.name, func(t *testing.T) { - var ( - req = require.New(t) - input = &expr.Vars{} - ) + req := require.New(t) - input.Set("length", tc.limit) + r, err := http.NewRequest(http.MethodGet, "/foo", http.NoBody) + r.Header = tc.headers - r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tc.body)) + req.NoError(err) - err := execFn(t, r, contentLengthValidator(ctx, input)) + scope := &scp{"request": r} - if tc.exp != "" { - req.EqualError(err, tc.exp) + h := NewValidatorHeader() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) } else { req.NoError(err) } diff --git a/pkg/apigw/verifier.go b/pkg/apigw/verifier.go index c20a65bcb..c3132b7c5 100644 --- a/pkg/apigw/verifier.go +++ b/pkg/apigw/verifier.go @@ -1,165 +1,161 @@ package apigw import ( + "bytes" "context" + "encoding/json" "fmt" "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/cortezaproject/corteza-server/system/types" - "github.com/davecgh/go-spew/spew" ) type ( - verifierQueryParam struct{} - verifierOrigin struct{} + verifierQueryParam struct { + functionMeta + params struct { + Expr string `json:"expr"` + } + } + + verifierOrigin struct { + functionMeta + params struct { + Expr string `json:"expr"` + } + } ) -func NewVerifierOrigin() verifierOrigin { - return verifierOrigin{} -} +func NewVerifierOrigin() (v *verifierOrigin) { + v = &verifierOrigin{} -func NewVerifierQueryParam() verifierQueryParam { - return verifierQueryParam{} -} + v.Step = 0 + v.Name = "verifierOrigin" + v.Label = "Origin verifier" + v.Kind = FunctionKindVerifier -func (h verifierQueryParam) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 0, - Name: "verifierQueryParam", - Label: "Query parameters verifier", - Kind: "verifier", - Weight: int(f.Weight), - Params: f.Params, - Args: []*functionMetaArg{ - { - Type: "expr", - Label: "expr", - Options: map[string]interface{}{}, - }, + v.Args = []*functionMetaArg{ + { + Type: "expr", + Label: "expr", + Options: map[string]interface{}{}, }, } + + return } -func (h verifierOrigin) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 0, - Name: "verifierOrigin", - Label: "Origin verifier", - Kind: "verifier", - Weight: int(f.Weight), - Params: f.Params, - Args: []*functionMetaArg{ - { - Type: "expr", - Label: "expr", - Options: map[string]interface{}{}, - }, +func (h verifierOrigin) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} + +func (h verifierOrigin) Meta() functionMeta { + return h.functionMeta +} + +func (v *verifierOrigin) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) + return v, err +} + +func (h verifierOrigin) Exec(ctx context.Context, scope *scp) error { + vv := map[string]interface{}{ + "origin": scope.Request().Header.Get("Origin"), + } + + // get the request data and put it into vars + out, err := expr.NewVars(vv) + + if err != nil { + return err + } + // spew.Dump("OUT", out) + pp := expr.NewParser() + tt, err := pp.Parse(h.params.Expr) + + if err != nil { + return fmt.Errorf("could not parse matching expression: %s", err) + } + + b, err := tt.Test(ctx, out) + + if err != nil { + return fmt.Errorf("could not validate origin: %s", err) + } + + if !b { + return fmt.Errorf("could not validate origin") + } + + return nil +} + +func NewVerifierQueryParam() (v *verifierQueryParam) { + v = &verifierQueryParam{} + + v.Step = 0 + v.Name = "verifierQueryParam" + v.Label = "Query parameters verifier" + v.Kind = "verifier" + + v.Args = []*functionMetaArg{ + { + Type: "expr", + Label: "expr", + Options: map[string]interface{}{}, }, } + + return } -func (h verifierQueryParam) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - for k := range ff.params { +func (h verifierQueryParam) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} - v, ok := params[k] +func (h verifierQueryParam) Meta() functionMeta { + return h.functionMeta +} - if !ok { - spew.Dump("not in params", k) - continue - } +func (v *verifierQueryParam) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) + return v, err +} - vv := map[string]interface{}{} - vals := scope.Request().URL.Query() +func (h verifierQueryParam) Exec(ctx context.Context, scope *scp) error { + vv := map[string]interface{}{} + vals := scope.Request().URL.Query() - for k, v := range vals { - vv[k] = v[0] - } - - // get the request data and put it into vars - out, err := expr.NewVars(vv) - - if err != nil { - // spew.Dump("ERR!", err) - return err - } - - pp := expr.NewParser() - tt, err := pp.Parse(v.(string)) - - if err != nil { - // spew.Dump("ERR!", err) - return err - } - - b, err := tt.Test(ctx, out) - - if err != nil { - // spew.Dump("ERR!", err) - return err - } - - spew.Dump("BBBB", b) - - if !b { - return fmt.Errorf("failed on step %d, function %s", ff.step, ff.name) - } - } - - // testing - scope.Request().Header.Add(fmt.Sprintf("step_%d", ff.step), ff.name) - - return nil + for k, v := range vals { + vv[k] = v[0] } -} -func (h verifierOrigin) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - for k := range ff.params { - v, ok := params[k] + // get the request data and put it into vars + out, err := expr.NewVars(vv) - if !ok { - spew.Dump("not in params", k) - continue - } - - vv := map[string]interface{}{ - "origin": scope.Request().Header.Get("Origin"), - } - - // get the request data and put it into vars - out, err := expr.NewVars(vv) - - if err != nil { - spew.Dump("ERR!", err) - return err - } - - pp := expr.NewParser() - tt, err := pp.Parse(v.(string)) - - if err != nil { - spew.Dump("ERR!", err) - return err - } - - b, err := tt.Test(ctx, out) - - if err != nil { - spew.Dump("ERR!", err) - return err - } - - spew.Dump("BBBB", b) - - if !b { - return fmt.Errorf("failed on step %d, function %s", ff.step, ff.name) - } - } - - // testing - scope.Request().Header.Add(fmt.Sprintf("step_%d", ff.step), ff.name) - - return nil + if err != nil { + return err } + + pp := expr.NewParser() + tt, err := pp.Parse(h.params.Expr) + + if err != nil { + return fmt.Errorf("could not parse matching expression: %s", err) + } + + b, err := tt.Test(ctx, out) + + if err != nil { + return fmt.Errorf("could not validate query params: %s", err) + } + + if !b { + return fmt.Errorf("could not validate query params") + } + // } + + // testing + scope.Request().Header.Add(fmt.Sprintf("step_%d", h.Step), h.Name) + + return nil } diff --git a/pkg/apigw/verifier_test.go b/pkg/apigw/verifier_test.go new file mode 100644 index 000000000..3e396d32b --- /dev/null +++ b/pkg/apigw/verifier_test.go @@ -0,0 +1,141 @@ +package apigw + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_verifierQueryParam(t *testing.T) { + type ( + tf struct { + name string + expr string + err string + url string + } + ) + + var ( + tcc = []tf{ + { + name: "matching simple query parameter", + expr: `{"expr":"foo == \"bar\""}`, + url: "https://examp.le?foo=bar", + }, + { + name: "matching simple query parameter - invalid expression key", + expr: `{"expr1":"foo == \"bar\""}`, + url: "https://examp.le?foo=bar", + err: "could not parse matching expression: parsing error: - 1:1 unexpected EOF while scanning extensions", + }, + { + name: "matching simple query parameter - missing value", + expr: `{"expr":"foo == \"bar\""}`, + url: "https://examp.le?foo=bar1", + err: "could not validate query params", + }, + { + name: "matching simple query parameter - missing value", + expr: `{"expr":"foo == \"bar-baz\""}`, + url: "https://examp.le?foo=bar-baz", + }, + } + ) + + for _, tc := range tcc { + var ( + ctx = context.Background() + ) + + t.Run(tc.name, func(t *testing.T) { + req := require.New(t) + + r, err := http.NewRequest(http.MethodGet, tc.url, http.NoBody) + + req.NoError(err) + + scope := &scp{"request": r} + + h := NewVerifierQueryParam() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.NoError(err) + } + }) + } +} + +func Test_verifierOrigin(t *testing.T) { + type ( + tf struct { + name string + expr string + err string + o string + } + ) + + var ( + tcc = []tf{ + { + name: "matching simple origin value", + expr: `{"expr":"origin == \"https://www.google.com\""}`, + o: "https://www.google.com", + }, + { + name: "matching simple nonexistent origin value", + expr: `{"expr":"origin == \"https://www.google.com\""}`, + o: "", + err: "could not validate origin", + }, + { + name: "matching simple origin value - invalid expression key", + expr: `{"expr1":"origin == \"https://www.google.com\""}`, + o: "", + err: "could not parse matching expression: parsing error: \t - 1:1 unexpected EOF while scanning extensions", + }, + { + name: "matching simple origin value - invalid expression key", + expr: `{"expr1":"origin == \"https"}`, + o: "", + err: "could not parse matching expression: parsing error: \t - 1:1 unexpected EOF while scanning extensions", + }, + } + ) + + for _, tc := range tcc { + var ( + ctx = context.Background() + ) + + t.Run(tc.name, func(t *testing.T) { + req := require.New(t) + + r, err := http.NewRequest(http.MethodGet, "/foo", http.NoBody) + r.Header.Set("Origin", tc.o) + + req.NoError(err) + + scope := &scp{"request": r} + + h := NewVerifierOrigin() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.NoError(err) + } + }) + } +} diff --git a/pkg/envoy/resource/rbac_references_system.gen.go b/pkg/envoy/resource/rbac_references_system.gen.go index 865d8aea4..352b0025c 100644 --- a/pkg/envoy/resource/rbac_references_system.gen.go +++ b/pkg/envoy/resource/rbac_references_system.gen.go @@ -7,10 +7,11 @@ package resource // // Definitions file that controls how this file is generated: +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -19,6 +20,32 @@ import ( "github.com/cortezaproject/corteza-server/system/types" ) +// SystemApigwFunctionRbacReferences generates RBAC references +// +// Resources with "envoy: false" are skipped +// +// This function is auto-generated +func SystemApigwFunctionRbacReferences(apigwFunction string) (res *Ref, pp []*Ref, err error) { + if apigwFunction != "*" { + res = &Ref{ResourceType: types.ApigwFunctionResourceType, Identifiers: MakeIdentifiers(apigwFunction)} + } + + return +} + +// SystemApigwRouteRbacReferences generates RBAC references +// +// Resources with "envoy: false" are skipped +// +// This function is auto-generated +func SystemApigwRouteRbacReferences(apigwRoute string) (res *Ref, pp []*Ref, err error) { + if apigwRoute != "*" { + res = &Ref{ResourceType: types.ApigwRouteResourceType, Identifiers: MakeIdentifiers(apigwRoute)} + } + + return +} + // SystemApplicationRbacReferences generates RBAC references // // Resources with "envoy: false" are skipped @@ -58,19 +85,6 @@ func SystemRoleRbacReferences(role string) (res *Ref, pp []*Ref, err error) { return } -// SystemRouteRbacReferences generates RBAC references -// -// Resources with "envoy: false" are skipped -// -// This function is auto-generated -func SystemRouteRbacReferences(route string) (res *Ref, pp []*Ref, err error) { - if route != "*" { - res = &Ref{ResourceType: types.RouteResourceType, Identifiers: MakeIdentifiers(route)} - } - - return -} - // SystemTemplateRbacReferences generates RBAC references // // Resources with "envoy: false" are skipped diff --git a/pkg/envoy/resource/rbac_rules_parse.gen.go b/pkg/envoy/resource/rbac_rules_parse.gen.go index 8960d891d..153cbe3e5 100644 --- a/pkg/envoy/resource/rbac_rules_parse.gen.go +++ b/pkg/envoy/resource/rbac_rules_parse.gen.go @@ -16,10 +16,11 @@ package resource // - compose.page.yaml // - compose.record.yaml // - compose.yaml +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -169,6 +170,26 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) { // Component resource, no path return composeTypes.ComponentResourceType, nil, nil, nil + case systemTypes.ApigwFunctionResourceType: + if len(path) != 1 { + return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) + } + ref, pp, err := SystemApigwFunctionRbacReferences( + // apigwFunction + path[0], + ) + return systemTypes.ApigwFunctionResourceType, ref, pp, err + + case systemTypes.ApigwRouteResourceType: + if len(path) != 1 { + return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) + } + ref, pp, err := SystemApigwRouteRbacReferences( + // apigwRoute + path[0], + ) + return systemTypes.ApigwRouteResourceType, ref, pp, err + case systemTypes.ApplicationResourceType: if len(path) != 1 { return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) @@ -199,16 +220,6 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) { ) return systemTypes.RoleResourceType, ref, pp, err - case systemTypes.RouteResourceType: - if len(path) != 1 { - return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) - } - ref, pp, err := SystemRouteRbacReferences( - // route - path[0], - ) - return systemTypes.RouteResourceType, ref, pp, err - case systemTypes.TemplateResourceType: if len(path) != 1 { return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) diff --git a/pkg/options/apigw.gen.go b/pkg/options/apigw.gen.go new file mode 100644 index 000000000..5be616bec --- /dev/null +++ b/pkg/options/apigw.gen.go @@ -0,0 +1,47 @@ +package options + +// 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: +// pkg/options/apigw.yaml + +import ( + "time" +) + +type ( + ApigwOpt struct { + Enabled bool `env:"APIGW_ENABLED"` + LogEnabled bool `env:"APIGW_LOG_ENABLED"` + ProxyEnableDebugLog bool `env:"APIGW_PROXY_ENABLE_DEBUG_LOG"` + ProxyFollowRedirects bool `env:"APIGW_PROXY_FOLLOW_REDIRECTS"` + ProxyOutboundTimeout time.Duration `env:"APIGW_PROXY_OUTBOUND_TIMEOUT"` + } +) + +// Apigw initializes and returns a ApigwOpt with default values +func Apigw() (o *ApigwOpt) { + o = &ApigwOpt{ + Enabled: true, + LogEnabled: false, + ProxyEnableDebugLog: false, + ProxyFollowRedirects: true, + ProxyOutboundTimeout: time.Second * 30, + } + + fill(o) + + // Function that allows access to custom logic inside the parent function. + // The custom logic in the other file should be like: + // func (o *Apigw) Defaults() {...} + func(o interface{}) { + if def, ok := o.(interface{ Defaults() }); ok { + def.Defaults() + } + }(o) + + return +} diff --git a/pkg/options/apigw.yaml b/pkg/options/apigw.yaml new file mode 100644 index 000000000..6d047e0d1 --- /dev/null +++ b/pkg/options/apigw.yaml @@ -0,0 +1,37 @@ +docs: + title: API Gateway + +imports: + - time + +props: + - name: Enabled + type: bool + default: true + description: |- + Enable API Gateway + + - name: logEnabled + type: bool + default: false + description: |- + Enable extra logging + + - name: proxyEnableDebugLog + type: bool + default: false + description: |- + Enable full debug log on requests / responses - warning, includes sensitive data + + - name: proxyFollowRedirects + type: bool + default: true + description: |- + Follow redirects on proxy requests + + - name: proxyOutboundTimeout + type: time.Duration + default: time.Second * 30 + description: |- + Outbound request timeout + diff --git a/store/apigw_function.gen.go b/store/apigw_function.gen.go index 04bedd6fc..49b4164fe 100644 --- a/store/apigw_function.gen.go +++ b/store/apigw_function.gen.go @@ -15,51 +15,51 @@ import ( type ( ApigwFunctions interface { - SearchApigwFunctions(ctx context.Context, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) - LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.Function, error) - LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.Function, error) + SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) + LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.ApigwFunction, error) + LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.ApigwFunction, error) - CreateApigwFunction(ctx context.Context, rr ...*types.Function) error + CreateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error - UpdateApigwFunction(ctx context.Context, rr ...*types.Function) error + UpdateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error - DeleteApigwFunction(ctx context.Context, rr ...*types.Function) error + DeleteApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error DeleteApigwFunctionByID(ctx context.Context, ID uint64) error TruncateApigwFunctions(ctx context.Context) error } ) -var _ *types.Function +var _ *types.ApigwFunction var _ context.Context // SearchApigwFunctions returns all matching ApigwFunctions from store -func SearchApigwFunctions(ctx context.Context, s ApigwFunctions, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) { +func SearchApigwFunctions(ctx context.Context, s ApigwFunctions, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) { return s.SearchApigwFunctions(ctx, f) } // LookupApigwFunctionByID searches for function by ID -func LookupApigwFunctionByID(ctx context.Context, s ApigwFunctions, id uint64) (*types.Function, error) { +func LookupApigwFunctionByID(ctx context.Context, s ApigwFunctions, id uint64) (*types.ApigwFunction, error) { return s.LookupApigwFunctionByID(ctx, id) } // LookupApigwFunctionByRoute searches for function by route -func LookupApigwFunctionByRoute(ctx context.Context, s ApigwFunctions, route string) (*types.Function, error) { +func LookupApigwFunctionByRoute(ctx context.Context, s ApigwFunctions, route string) (*types.ApigwFunction, error) { return s.LookupApigwFunctionByRoute(ctx, route) } // CreateApigwFunction creates one or more ApigwFunctions in store -func CreateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.Function) error { +func CreateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.ApigwFunction) error { return s.CreateApigwFunction(ctx, rr...) } // UpdateApigwFunction updates one or more (existing) ApigwFunctions in store -func UpdateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.Function) error { +func UpdateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.ApigwFunction) error { return s.UpdateApigwFunction(ctx, rr...) } // DeleteApigwFunction Deletes one or more ApigwFunctions from store -func DeleteApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.Function) error { +func DeleteApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.ApigwFunction) error { return s.DeleteApigwFunction(ctx, rr...) } diff --git a/store/apigw_function.yaml b/store/apigw_function.yaml index 80ae1bcb8..6845b0af2 100644 --- a/store/apigw_function.yaml +++ b/store/apigw_function.yaml @@ -3,8 +3,8 @@ import: types: package: types - type: types.Function - filterType: types.FunctionFilter + type: types.ApigwFunction + filterType: types.ApigwFunctionFilter fields: - { field: ID, sortable: false } diff --git a/store/apigw_route.gen.go b/store/apigw_route.gen.go index fec22452c..2af69fdff 100644 --- a/store/apigw_route.gen.go +++ b/store/apigw_route.gen.go @@ -15,51 +15,51 @@ import ( type ( ApigwRoutes interface { - SearchApigwRoutes(ctx context.Context, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) - LookupApigwRouteByID(ctx context.Context, id uint64) (*types.Route, error) - LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.Route, error) + SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) + LookupApigwRouteByID(ctx context.Context, id uint64) (*types.ApigwRoute, error) + LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.ApigwRoute, error) - CreateApigwRoute(ctx context.Context, rr ...*types.Route) error + CreateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error - UpdateApigwRoute(ctx context.Context, rr ...*types.Route) error + UpdateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error - DeleteApigwRoute(ctx context.Context, rr ...*types.Route) error + DeleteApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error DeleteApigwRouteByID(ctx context.Context, ID uint64) error TruncateApigwRoutes(ctx context.Context) error } ) -var _ *types.Route +var _ *types.ApigwRoute var _ context.Context // SearchApigwRoutes returns all matching ApigwRoutes from store -func SearchApigwRoutes(ctx context.Context, s ApigwRoutes, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) { +func SearchApigwRoutes(ctx context.Context, s ApigwRoutes, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) { return s.SearchApigwRoutes(ctx, f) } // LookupApigwRouteByID searches for route by ID -func LookupApigwRouteByID(ctx context.Context, s ApigwRoutes, id uint64) (*types.Route, error) { +func LookupApigwRouteByID(ctx context.Context, s ApigwRoutes, id uint64) (*types.ApigwRoute, error) { return s.LookupApigwRouteByID(ctx, id) } // LookupApigwRouteByEndpoint searches for route by endpoint -func LookupApigwRouteByEndpoint(ctx context.Context, s ApigwRoutes, endpoint string) (*types.Route, error) { +func LookupApigwRouteByEndpoint(ctx context.Context, s ApigwRoutes, endpoint string) (*types.ApigwRoute, error) { return s.LookupApigwRouteByEndpoint(ctx, endpoint) } // CreateApigwRoute creates one or more ApigwRoutes in store -func CreateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.Route) error { +func CreateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.ApigwRoute) error { return s.CreateApigwRoute(ctx, rr...) } // UpdateApigwRoute updates one or more (existing) ApigwRoutes in store -func UpdateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.Route) error { +func UpdateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.ApigwRoute) error { return s.UpdateApigwRoute(ctx, rr...) } // DeleteApigwRoute Deletes one or more ApigwRoutes from store -func DeleteApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.Route) error { +func DeleteApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.ApigwRoute) error { return s.DeleteApigwRoute(ctx, rr...) } diff --git a/store/apigw_route.yaml b/store/apigw_route.yaml index 5cf9d9f54..3b40d2353 100644 --- a/store/apigw_route.yaml +++ b/store/apigw_route.yaml @@ -3,8 +3,8 @@ import: types: package: types - type: types.Route - filterType: types.RouteFilter + type: types.ApigwRoute + filterType: types.ApigwRouteFilter fields: - { field: ID, sortable: false } diff --git a/store/rdbms/apigw_function.gen.go b/store/rdbms/apigw_function.gen.go index 298ea4504..0f7dadb64 100644 --- a/store/rdbms/apigw_function.gen.go +++ b/store/rdbms/apigw_function.gen.go @@ -24,11 +24,11 @@ var _ = errors.Is // SearchApigwFunctions returns all matching rows // // This function calls convertApigwFunctionFilter with the given -// types.FunctionFilter and expects to receive a working squirrel.SelectBuilder -func (s Store) SearchApigwFunctions(ctx context.Context, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) { +// types.ApigwFunctionFilter and expects to receive a working squirrel.SelectBuilder +func (s Store) SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) { var ( err error - set []*types.Function + set []*types.ApigwFunction q squirrel.SelectBuilder ) @@ -112,11 +112,11 @@ func (s Store) fetchFullPageOfApigwFunctions( sort filter.SortExprSet, cursor *filter.PagingCursor, reqItems uint, - check func(*types.Function) (bool, error), + check func(*types.ApigwFunction) (bool, error), cursorCond func(*filter.PagingCursor) squirrel.Sqlizer, -) (set []*types.Function, prev, next *filter.PagingCursor, err error) { +) (set []*types.ApigwFunction, prev, next *filter.PagingCursor, err error) { var ( - aux []*types.Function + aux []*types.ApigwFunction // When cursor for a previous page is used it's marked as reversed // This tells us to flip the descending flag on all used sort keys @@ -137,7 +137,7 @@ func (s Store) fetchFullPageOfApigwFunctions( hasNext bool ) - set = make([]*types.Function, 0, DefaultSliceCapacity) + set = make([]*types.ApigwFunction, 0, DefaultSliceCapacity) for try := 0; try < MaxRefetches; try++ { if cursor != nil { @@ -235,12 +235,12 @@ func (s Store) fetchFullPageOfApigwFunctions( func (s Store) QueryApigwFunctions( ctx context.Context, q squirrel.Sqlizer, - check func(*types.Function) (bool, error), -) ([]*types.Function, error) { + check func(*types.ApigwFunction) (bool, error), +) ([]*types.ApigwFunction, error) { var ( - tmp = make([]*types.Function, 0, DefaultSliceCapacity) - set = make([]*types.Function, 0, DefaultSliceCapacity) - res *types.Function + tmp = make([]*types.ApigwFunction, 0, DefaultSliceCapacity) + set = make([]*types.ApigwFunction, 0, DefaultSliceCapacity) + res *types.ApigwFunction // Query rows with rows, err = s.Query(ctx, q) @@ -272,21 +272,21 @@ func (s Store) QueryApigwFunctions( } // LookupApigwFunctionByID searches for function by ID -func (s Store) LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.Function, error) { +func (s Store) LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.ApigwFunction, error) { return s.execLookupApigwFunction(ctx, squirrel.Eq{ s.preprocessColumn("af.id", ""): store.PreprocessValue(id, ""), }) } // LookupApigwFunctionByRoute searches for function by route -func (s Store) LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.Function, error) { +func (s Store) LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.ApigwFunction, error) { return s.execLookupApigwFunction(ctx, squirrel.Eq{ s.preprocessColumn("af.rel_route", ""): store.PreprocessValue(route, ""), }) } // CreateApigwFunction creates one or more rows in apigw_functions table -func (s Store) CreateApigwFunction(ctx context.Context, rr ...*types.Function) (err error) { +func (s Store) CreateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) (err error) { for _, res := range rr { err = s.checkApigwFunctionConstraints(ctx, res) if err != nil { @@ -303,12 +303,12 @@ func (s Store) CreateApigwFunction(ctx context.Context, rr ...*types.Function) ( } // UpdateApigwFunction updates one or more existing rows in apigw_functions -func (s Store) UpdateApigwFunction(ctx context.Context, rr ...*types.Function) error { +func (s Store) UpdateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error { return s.partialApigwFunctionUpdate(ctx, nil, rr...) } // partialApigwFunctionUpdate updates one or more existing rows in apigw_functions -func (s Store) partialApigwFunctionUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Function) (err error) { +func (s Store) partialApigwFunctionUpdate(ctx context.Context, onlyColumns []string, rr ...*types.ApigwFunction) (err error) { for _, res := range rr { err = s.checkApigwFunctionConstraints(ctx, res) if err != nil { @@ -330,7 +330,7 @@ func (s Store) partialApigwFunctionUpdate(ctx context.Context, onlyColumns []str } // DeleteApigwFunction Deletes one or more rows from apigw_functions table -func (s Store) DeleteApigwFunction(ctx context.Context, rr ...*types.Function) (err error) { +func (s Store) DeleteApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) (err error) { for _, res := range rr { err = s.execDeleteApigwFunctions(ctx, squirrel.Eq{ @@ -357,8 +357,8 @@ func (s Store) TruncateApigwFunctions(ctx context.Context) error { } // execLookupApigwFunction prepares ApigwFunction query and executes it, -// returning types.Function (or error) -func (s Store) execLookupApigwFunction(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Function, err error) { +// returning types.ApigwFunction (or error) +func (s Store) execLookupApigwFunction(ctx context.Context, cnd squirrel.Sqlizer) (res *types.ApigwFunction, err error) { var ( row rowScanner ) @@ -391,11 +391,11 @@ func (s Store) execDeleteApigwFunctions(ctx context.Context, cnd squirrel.Sqlize return s.Exec(ctx, s.DeleteBuilder(s.apigwFunctionTable("af")).Where(cnd)) } -func (s Store) internalApigwFunctionRowScanner(row rowScanner) (res *types.Function, err error) { - res = &types.Function{} +func (s Store) internalApigwFunctionRowScanner(row rowScanner) (res *types.ApigwFunction, err error) { + res = &types.ApigwFunction{} if _, has := s.config.RowScanners["apigwFunction"]; has { - scanner := s.config.RowScanners["apigwFunction"].(func(_ rowScanner, _ *types.Function) error) + scanner := s.config.RowScanners["apigwFunction"].(func(_ rowScanner, _ *types.ApigwFunction) error) err = scanner(row, res) } else { err = row.Scan( @@ -476,11 +476,11 @@ func (Store) sortableApigwFunctionColumns() map[string]string { } } -// internalApigwFunctionEncoder encodes fields from types.Function to store.Payload (map) +// internalApigwFunctionEncoder encodes fields from types.ApigwFunction to store.Payload (map) // // Encoding is done by using generic approach or by calling encodeApigwFunction // func when rdbms.customEncoder=true -func (s Store) internalApigwFunctionEncoder(res *types.Function) store.Payload { +func (s Store) internalApigwFunctionEncoder(res *types.ApigwFunction) store.Payload { return store.Payload{ "id": res.ID, "rel_route": res.Route, @@ -506,7 +506,7 @@ func (s Store) internalApigwFunctionEncoder(res *types.Function) store.Payload { // Known issue: // when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on // undeleted items) -func (s Store) collectApigwFunctionCursorValues(res *types.Function, cc ...*filter.SortExpr) *filter.PagingCursor { +func (s Store) collectApigwFunctionCursorValues(res *types.ApigwFunction, cc ...*filter.SortExpr) *filter.PagingCursor { var ( cursor = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()} @@ -542,7 +542,7 @@ func (s Store) collectApigwFunctionCursorValues(res *types.Function, cc ...*filt // // Using built-in constraint checking would be more performant but unfortunately we cannot rely // on the full support (MySQL does not support conditional indexes) -func (s *Store) checkApigwFunctionConstraints(ctx context.Context, res *types.Function) error { +func (s *Store) checkApigwFunctionConstraints(ctx context.Context, res *types.ApigwFunction) error { // Consider resource valid when all fields in unique constraint check lookups // have valid (non-empty) value // diff --git a/store/rdbms/apigw_function.go b/store/rdbms/apigw_function.go index 80e2299c8..14e255023 100644 --- a/store/rdbms/apigw_function.go +++ b/store/rdbms/apigw_function.go @@ -6,8 +6,14 @@ import ( "github.com/cortezaproject/corteza-server/system/types" ) -func (s Store) convertApigwFunctionFilter(f types.FunctionFilter) (query squirrel.SelectBuilder, err error) { +func (s Store) convertApigwFunctionFilter(f types.ApigwFunctionFilter) (query squirrel.SelectBuilder, err error) { query = s.apigwFunctionsSelectBuilder() + query = filter.StateCondition(query, "af.deleted_at", f.Deleted) + + if f.RouteID > 0 { + query = query.Where(squirrel.Eq{"af.rel_route": f.RouteID}) + } + return } diff --git a/store/rdbms/apigw_route.gen.go b/store/rdbms/apigw_route.gen.go index 3fc10724e..44e2a82ec 100644 --- a/store/rdbms/apigw_route.gen.go +++ b/store/rdbms/apigw_route.gen.go @@ -24,11 +24,11 @@ var _ = errors.Is // SearchApigwRoutes returns all matching rows // // This function calls convertApigwRouteFilter with the given -// types.RouteFilter and expects to receive a working squirrel.SelectBuilder -func (s Store) SearchApigwRoutes(ctx context.Context, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) { +// types.ApigwRouteFilter and expects to receive a working squirrel.SelectBuilder +func (s Store) SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) { var ( err error - set []*types.Route + set []*types.ApigwRoute q squirrel.SelectBuilder ) @@ -112,11 +112,11 @@ func (s Store) fetchFullPageOfApigwRoutes( sort filter.SortExprSet, cursor *filter.PagingCursor, reqItems uint, - check func(*types.Route) (bool, error), + check func(*types.ApigwRoute) (bool, error), cursorCond func(*filter.PagingCursor) squirrel.Sqlizer, -) (set []*types.Route, prev, next *filter.PagingCursor, err error) { +) (set []*types.ApigwRoute, prev, next *filter.PagingCursor, err error) { var ( - aux []*types.Route + aux []*types.ApigwRoute // When cursor for a previous page is used it's marked as reversed // This tells us to flip the descending flag on all used sort keys @@ -137,7 +137,7 @@ func (s Store) fetchFullPageOfApigwRoutes( hasNext bool ) - set = make([]*types.Route, 0, DefaultSliceCapacity) + set = make([]*types.ApigwRoute, 0, DefaultSliceCapacity) for try := 0; try < MaxRefetches; try++ { if cursor != nil { @@ -235,12 +235,12 @@ func (s Store) fetchFullPageOfApigwRoutes( func (s Store) QueryApigwRoutes( ctx context.Context, q squirrel.Sqlizer, - check func(*types.Route) (bool, error), -) ([]*types.Route, error) { + check func(*types.ApigwRoute) (bool, error), +) ([]*types.ApigwRoute, error) { var ( - tmp = make([]*types.Route, 0, DefaultSliceCapacity) - set = make([]*types.Route, 0, DefaultSliceCapacity) - res *types.Route + tmp = make([]*types.ApigwRoute, 0, DefaultSliceCapacity) + set = make([]*types.ApigwRoute, 0, DefaultSliceCapacity) + res *types.ApigwRoute // Query rows with rows, err = s.Query(ctx, q) @@ -272,21 +272,21 @@ func (s Store) QueryApigwRoutes( } // LookupApigwRouteByID searches for route by ID -func (s Store) LookupApigwRouteByID(ctx context.Context, id uint64) (*types.Route, error) { +func (s Store) LookupApigwRouteByID(ctx context.Context, id uint64) (*types.ApigwRoute, error) { return s.execLookupApigwRoute(ctx, squirrel.Eq{ s.preprocessColumn("ar.id", ""): store.PreprocessValue(id, ""), }) } // LookupApigwRouteByEndpoint searches for route by endpoint -func (s Store) LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.Route, error) { +func (s Store) LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.ApigwRoute, error) { return s.execLookupApigwRoute(ctx, squirrel.Eq{ s.preprocessColumn("ar.endpoint", ""): store.PreprocessValue(endpoint, ""), }) } // CreateApigwRoute creates one or more rows in apigw_routes table -func (s Store) CreateApigwRoute(ctx context.Context, rr ...*types.Route) (err error) { +func (s Store) CreateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) (err error) { for _, res := range rr { err = s.checkApigwRouteConstraints(ctx, res) if err != nil { @@ -303,12 +303,12 @@ func (s Store) CreateApigwRoute(ctx context.Context, rr ...*types.Route) (err er } // UpdateApigwRoute updates one or more existing rows in apigw_routes -func (s Store) UpdateApigwRoute(ctx context.Context, rr ...*types.Route) error { +func (s Store) UpdateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error { return s.partialApigwRouteUpdate(ctx, nil, rr...) } // partialApigwRouteUpdate updates one or more existing rows in apigw_routes -func (s Store) partialApigwRouteUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Route) (err error) { +func (s Store) partialApigwRouteUpdate(ctx context.Context, onlyColumns []string, rr ...*types.ApigwRoute) (err error) { for _, res := range rr { err = s.checkApigwRouteConstraints(ctx, res) if err != nil { @@ -330,7 +330,7 @@ func (s Store) partialApigwRouteUpdate(ctx context.Context, onlyColumns []string } // DeleteApigwRoute Deletes one or more rows from apigw_routes table -func (s Store) DeleteApigwRoute(ctx context.Context, rr ...*types.Route) (err error) { +func (s Store) DeleteApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) (err error) { for _, res := range rr { err = s.execDeleteApigwRoutes(ctx, squirrel.Eq{ @@ -357,8 +357,8 @@ func (s Store) TruncateApigwRoutes(ctx context.Context) error { } // execLookupApigwRoute prepares ApigwRoute query and executes it, -// returning types.Route (or error) -func (s Store) execLookupApigwRoute(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Route, err error) { +// returning types.ApigwRoute (or error) +func (s Store) execLookupApigwRoute(ctx context.Context, cnd squirrel.Sqlizer) (res *types.ApigwRoute, err error) { var ( row rowScanner ) @@ -391,11 +391,11 @@ func (s Store) execDeleteApigwRoutes(ctx context.Context, cnd squirrel.Sqlizer) return s.Exec(ctx, s.DeleteBuilder(s.apigwRouteTable("ar")).Where(cnd)) } -func (s Store) internalApigwRouteRowScanner(row rowScanner) (res *types.Route, err error) { - res = &types.Route{} +func (s Store) internalApigwRouteRowScanner(row rowScanner) (res *types.ApigwRoute, err error) { + res = &types.ApigwRoute{} if _, has := s.config.RowScanners["apigwRoute"]; has { - scanner := s.config.RowScanners["apigwRoute"].(func(_ rowScanner, _ *types.Route) error) + scanner := s.config.RowScanners["apigwRoute"].(func(_ rowScanner, _ *types.ApigwRoute) error) err = scanner(row, res) } else { err = row.Scan( @@ -476,11 +476,11 @@ func (Store) sortableApigwRouteColumns() map[string]string { } } -// internalApigwRouteEncoder encodes fields from types.Route to store.Payload (map) +// internalApigwRouteEncoder encodes fields from types.ApigwRoute to store.Payload (map) // // Encoding is done by using generic approach or by calling encodeApigwRoute // func when rdbms.customEncoder=true -func (s Store) internalApigwRouteEncoder(res *types.Route) store.Payload { +func (s Store) internalApigwRouteEncoder(res *types.ApigwRoute) store.Payload { return store.Payload{ "id": res.ID, "endpoint": res.Endpoint, @@ -506,7 +506,7 @@ func (s Store) internalApigwRouteEncoder(res *types.Route) store.Payload { // Known issue: // when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on // undeleted items) -func (s Store) collectApigwRouteCursorValues(res *types.Route, cc ...*filter.SortExpr) *filter.PagingCursor { +func (s Store) collectApigwRouteCursorValues(res *types.ApigwRoute, cc ...*filter.SortExpr) *filter.PagingCursor { var ( cursor = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()} @@ -542,7 +542,7 @@ func (s Store) collectApigwRouteCursorValues(res *types.Route, cc ...*filter.Sor // // Using built-in constraint checking would be more performant but unfortunately we cannot rely // on the full support (MySQL does not support conditional indexes) -func (s *Store) checkApigwRouteConstraints(ctx context.Context, res *types.Route) error { +func (s *Store) checkApigwRouteConstraints(ctx context.Context, res *types.ApigwRoute) error { // Consider resource valid when all fields in unique constraint check lookups // have valid (non-empty) value // diff --git a/store/rdbms/apigw_route.go b/store/rdbms/apigw_route.go index 2de3533a7..5086b15ec 100644 --- a/store/rdbms/apigw_route.go +++ b/store/rdbms/apigw_route.go @@ -6,7 +6,7 @@ import ( "github.com/cortezaproject/corteza-server/system/types" ) -func (s Store) convertApigwRouteFilter(f types.RouteFilter) (query squirrel.SelectBuilder, err error) { +func (s Store) convertApigwRouteFilter(f types.ApigwRouteFilter) (query squirrel.SelectBuilder, err error) { query = s.apigwRoutesSelectBuilder() query = filter.StateCondition(query, "ar.deleted_at", f.Deleted) return diff --git a/system/rest.yaml b/system/rest.yaml index ee0b94405..fb6bd5b09 100644 --- a/system/rest.yaml +++ b/system/rest.yaml @@ -1507,7 +1507,7 @@ endpoints: - title: API Gateway routes path: "/apigw/route" - entrypoint: route + entrypoint: apigwRoute authentication: [] imports: - github.com/cortezaproject/corteza-server/pkg/label @@ -1518,7 +1518,7 @@ endpoints: path: "/" parameters: get: - - { name: routeID, type: "[]string", title: "Filter by route ID" } + - { name: routeID, type: "[]uint64", title: "Filter by route ID" } - { name: query, type: "string", title: "Filter routes" } - { name: deleted, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) deleted routes" } - { name: disabled, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) disabled routes" } @@ -1579,7 +1579,7 @@ endpoints: - title: API Gateway functions path: "/apigw/function" - entrypoint: function + entrypoint: apigwFunction authentication: [] imports: - github.com/cortezaproject/corteza-server/system/types @@ -1590,8 +1590,7 @@ endpoints: path: "/" parameters: get: - - { name: functionID, type: "[]string", title: "Filter by function ID" } - - { name: routeID, type: "string", title: "Filter by route ID" } + - { name: routeID, type: "uint64", title: "Filter by route ID" } - { name: query, type: "string", title: "Filter functions" } - { name: deleted, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) deleted functions" } - { name: disabled, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) disabled functions" } @@ -1604,11 +1603,11 @@ endpoints: path: "" parameters: post: - - { name: routeID, type: uint64, title: "Route", required: true } - - { name: weight, type: uint64, title: "Function priority" } - - { name: kind, type: "types.ApigwFunctionKind", title: "Function kind" } - - { name: ref, type: string, title: "Function ref" } - - { name: params, type: "types.FuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } + - { name: routeID, type: uint64, title: "Route", required: true } + - { name: weight, type: uint64, title: "Function priority" } + - { name: kind, type: string, title: "Function kind" } + - { name: ref, type: string, title: "Function ref" } + - { name: params, type: "types.ApigwFuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } - name: update method: POST title: Update route details @@ -1616,11 +1615,11 @@ endpoints: parameters: path: [ { name: functionID, type: uint64, required: true, title: "Function ID" } ] post: - - { name: routeID, type: uint64, title: "Route", required: true } - - { name: weight, type: uint64, title: "Function priority" } - - { name: kind, type: "types.ApigwFunctionKind", title: "Function kind" } - - { name: ref, type: string, title: "Function ref" } - - { name: params, type: "types.FuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } + - { name: routeID, type: uint64, title: "Route", required: true } + - { name: weight, type: uint64, title: "Function priority" } + - { name: kind, type: string, title: "Function kind" } + - { name: ref, type: string, title: "Function ref" } + - { name: params, type: "types.ApigwFuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } - name: read method: GET title: Read function details diff --git a/system/rest/function.go b/system/rest/apigw_function.go similarity index 52% rename from system/rest/function.go rename to system/rest/apigw_function.go index 5fd0d24c1..a3a4946dc 100644 --- a/system/rest/function.go +++ b/system/rest/apigw_function.go @@ -11,42 +11,43 @@ import ( ) type ( - Function struct { + ApigwFunction struct { svc functionService ac templateAccessController } functionPayload struct { - *types.Function + *types.ApigwFunction } functionSetPayload struct { - Filter types.FunctionFilter `json:"filter"` - Set []*functionPayload `json:"set"` + Filter types.ApigwFunctionFilter `json:"filter"` + Set []*functionPayload `json:"set"` } functionService interface { - FindByID(ctx context.Context, ID uint64) (*types.Function, error) - Search(ctx context.Context, filter types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) - Create(ctx context.Context, new *types.Function) (*types.Function, error) - Update(ctx context.Context, upd *types.Function) (*types.Function, error) + FindByID(ctx context.Context, ID uint64) (*types.ApigwFunction, error) + Search(ctx context.Context, filter types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) + Create(ctx context.Context, new *types.ApigwFunction) (*types.ApigwFunction, error) + Update(ctx context.Context, upd *types.ApigwFunction) (*types.ApigwFunction, error) DeleteByID(ctx context.Context, ID uint64) error UndeleteByID(ctx context.Context, ID uint64) error Definitions(context.Context, string) (interface{}, error) } ) -func (Function) New() *Function { - return &Function{ +func (ApigwFunction) New() *ApigwFunction { + return &ApigwFunction{ svc: service.DefaultFunction, ac: service.DefaultAccessControl, } } -func (ctrl *Function) List(ctx context.Context, r *request.FunctionList) (interface{}, error) { +func (ctrl *ApigwFunction) List(ctx context.Context, r *request.ApigwFunctionList) (interface{}, error) { var ( err error - f = types.FunctionFilter{ + f = types.ApigwFunctionFilter{ + RouteID: r.RouteID, Deleted: filter.State(r.Deleted), } ) @@ -64,13 +65,13 @@ func (ctrl *Function) List(ctx context.Context, r *request.FunctionList) (interf return ctrl.makeFilterPayload(ctx, set, filter, err) } -func (ctrl *Function) Create(ctx context.Context, r *request.FunctionCreate) (interface{}, error) { +func (ctrl *ApigwFunction) Create(ctx context.Context, r *request.ApigwFunctionCreate) (interface{}, error) { var ( err error - q = &types.Function{ + q = &types.ApigwFunction{ Route: r.RouteID, Weight: r.Weight, - Kind: string(r.Kind), + Kind: r.Kind, Ref: r.Ref, Params: r.Params, } @@ -81,18 +82,18 @@ func (ctrl *Function) Create(ctx context.Context, r *request.FunctionCreate) (in return ctrl.makePayload(ctx, q, err) } -func (ctrl *Function) Read(ctx context.Context, r *request.FunctionRead) (interface{}, error) { +func (ctrl *ApigwFunction) Read(ctx context.Context, r *request.ApigwFunctionRead) (interface{}, error) { return ctrl.svc.FindByID(ctx, r.FunctionID) } -func (ctrl *Function) Update(ctx context.Context, r *request.FunctionUpdate) (interface{}, error) { +func (ctrl *ApigwFunction) Update(ctx context.Context, r *request.ApigwFunctionUpdate) (interface{}, error) { var ( err error - q = &types.Function{ + q = &types.ApigwFunction{ ID: r.FunctionID, Route: r.RouteID, Weight: r.Weight, - Kind: string(r.Kind), + Kind: r.Kind, Ref: r.Ref, Params: r.Params, } @@ -103,31 +104,31 @@ func (ctrl *Function) Update(ctx context.Context, r *request.FunctionUpdate) (in return ctrl.makePayload(ctx, q, err) } -func (ctrl *Function) Delete(ctx context.Context, r *request.FunctionDelete) (interface{}, error) { +func (ctrl *ApigwFunction) Delete(ctx context.Context, r *request.ApigwFunctionDelete) (interface{}, error) { return api.OK(), ctrl.svc.DeleteByID(ctx, r.FunctionID) } -func (ctrl *Function) Definitions(ctx context.Context, r *request.FunctionDefinitions) (interface{}, error) { +func (ctrl *ApigwFunction) Definitions(ctx context.Context, r *request.ApigwFunctionDefinitions) (interface{}, error) { return ctrl.svc.Definitions(ctx, r.Kind) } -func (ctrl *Function) Undelete(ctx context.Context, r *request.FunctionUndelete) (interface{}, error) { +func (ctrl *ApigwFunction) Undelete(ctx context.Context, r *request.ApigwFunctionUndelete) (interface{}, error) { return api.OK(), ctrl.svc.UndeleteByID(ctx, r.FunctionID) } -func (ctrl *Function) makePayload(ctx context.Context, q *types.Function, err error) (*functionPayload, error) { +func (ctrl *ApigwFunction) makePayload(ctx context.Context, q *types.ApigwFunction, err error) (*functionPayload, error) { if err != nil || q == nil { return nil, err } qq := &functionPayload{ - Function: q, + ApigwFunction: q, } return qq, nil } -func (ctrl *Function) makeFilterPayload(ctx context.Context, nn types.FunctionSet, f types.FunctionFilter, err error) (*functionSetPayload, error) { +func (ctrl *ApigwFunction) makeFilterPayload(ctx context.Context, nn types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) (*functionSetPayload, error) { if err != nil { return nil, err } diff --git a/system/rest/route.go b/system/rest/apigw_route.go similarity index 55% rename from system/rest/route.go rename to system/rest/apigw_route.go index fb88c22bf..d688cc986 100644 --- a/system/rest/route.go +++ b/system/rest/apigw_route.go @@ -11,41 +11,41 @@ import ( ) type ( - Route struct { + ApigwRoute struct { svc routeService ac templateAccessController } routePayload struct { - *types.Route + *types.ApigwRoute } routeSetPayload struct { - Filter types.RouteFilter `json:"filter"` - Set []*routePayload `json:"set"` + Filter types.ApigwRouteFilter `json:"filter"` + Set []*routePayload `json:"set"` } routeService interface { - FindByID(ctx context.Context, ID uint64) (*types.Route, error) - Create(ctx context.Context, new *types.Route) (*types.Route, error) - Update(ctx context.Context, upd *types.Route) (*types.Route, error) + FindByID(ctx context.Context, ID uint64) (*types.ApigwRoute, error) + Create(ctx context.Context, new *types.ApigwRoute) (*types.ApigwRoute, error) + Update(ctx context.Context, upd *types.ApigwRoute) (*types.ApigwRoute, error) DeleteByID(ctx context.Context, ID uint64) error UndeleteByID(ctx context.Context, ID uint64) error - Search(ctx context.Context, filter types.RouteFilter) (types.RouteSet, types.RouteFilter, error) + Search(ctx context.Context, filter types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) } ) -func (Route) New() *Route { - return &Route{ +func (ApigwRoute) New() *ApigwRoute { + return &ApigwRoute{ svc: service.DefaultRoute, ac: service.DefaultAccessControl, } } -func (ctrl *Route) List(ctx context.Context, r *request.RouteList) (interface{}, error) { +func (ctrl *ApigwRoute) List(ctx context.Context, r *request.ApigwRouteList) (interface{}, error) { var ( err error - f = types.RouteFilter{ + f = types.ApigwRouteFilter{ Deleted: filter.State(r.Deleted), } ) @@ -63,10 +63,10 @@ func (ctrl *Route) List(ctx context.Context, r *request.RouteList) (interface{}, return ctrl.makeFilterPayload(ctx, set, filter, err) } -func (ctrl *Route) Create(ctx context.Context, r *request.RouteCreate) (interface{}, error) { +func (ctrl *ApigwRoute) Create(ctx context.Context, r *request.ApigwRouteCreate) (interface{}, error) { var ( err error - q = &types.Route{ + q = &types.ApigwRoute{ Endpoint: r.Endpoint, Method: r.Method, Debug: r.Debug, @@ -79,14 +79,14 @@ func (ctrl *Route) Create(ctx context.Context, r *request.RouteCreate) (interfac return ctrl.makePayload(ctx, q, err) } -func (ctrl *Route) Read(ctx context.Context, r *request.RouteRead) (interface{}, error) { +func (ctrl *ApigwRoute) Read(ctx context.Context, r *request.ApigwRouteRead) (interface{}, error) { return ctrl.svc.FindByID(ctx, r.RouteID) } -func (ctrl *Route) Update(ctx context.Context, r *request.RouteUpdate) (interface{}, error) { +func (ctrl *ApigwRoute) Update(ctx context.Context, r *request.ApigwRouteUpdate) (interface{}, error) { var ( err error - q = &types.Route{ + q = &types.ApigwRoute{ ID: r.RouteID, Endpoint: r.Endpoint, Method: r.Method, @@ -101,27 +101,27 @@ func (ctrl *Route) Update(ctx context.Context, r *request.RouteUpdate) (interfac return ctrl.makePayload(ctx, q, err) } -func (ctrl *Route) Delete(ctx context.Context, r *request.RouteDelete) (interface{}, error) { +func (ctrl *ApigwRoute) Delete(ctx context.Context, r *request.ApigwRouteDelete) (interface{}, error) { return api.OK(), ctrl.svc.DeleteByID(ctx, r.RouteID) } -func (ctrl *Route) Undelete(ctx context.Context, r *request.RouteUndelete) (interface{}, error) { +func (ctrl *ApigwRoute) Undelete(ctx context.Context, r *request.ApigwRouteUndelete) (interface{}, error) { return api.OK(), ctrl.svc.UndeleteByID(ctx, r.RouteID) } -func (ctrl *Route) makePayload(ctx context.Context, q *types.Route, err error) (*routePayload, error) { +func (ctrl *ApigwRoute) makePayload(ctx context.Context, q *types.ApigwRoute, err error) (*routePayload, error) { if err != nil || q == nil { return nil, err } qq := &routePayload{ - Route: q, + ApigwRoute: q, } return qq, nil } -func (ctrl *Route) makeFilterPayload(ctx context.Context, nn types.RouteSet, f types.RouteFilter, err error) (*routeSetPayload, error) { +func (ctrl *ApigwRoute) makeFilterPayload(ctx context.Context, nn types.ApigwRouteSet, f types.ApigwRouteFilter, err error) (*routeSetPayload, error) { if err != nil { return nil, err } diff --git a/system/rest/handlers/function.go b/system/rest/handlers/apigwFunction.go similarity index 75% rename from system/rest/handlers/function.go rename to system/rest/handlers/apigwFunction.go index 464a76e45..fc583c45c 100644 --- a/system/rest/handlers/function.go +++ b/system/rest/handlers/apigwFunction.go @@ -18,18 +18,18 @@ import ( type ( // Internal API interface - FunctionAPI interface { - List(context.Context, *request.FunctionList) (interface{}, error) - Create(context.Context, *request.FunctionCreate) (interface{}, error) - Update(context.Context, *request.FunctionUpdate) (interface{}, error) - Read(context.Context, *request.FunctionRead) (interface{}, error) - Delete(context.Context, *request.FunctionDelete) (interface{}, error) - Undelete(context.Context, *request.FunctionUndelete) (interface{}, error) - Definitions(context.Context, *request.FunctionDefinitions) (interface{}, error) + ApigwFunctionAPI interface { + List(context.Context, *request.ApigwFunctionList) (interface{}, error) + Create(context.Context, *request.ApigwFunctionCreate) (interface{}, error) + Update(context.Context, *request.ApigwFunctionUpdate) (interface{}, error) + Read(context.Context, *request.ApigwFunctionRead) (interface{}, error) + Delete(context.Context, *request.ApigwFunctionDelete) (interface{}, error) + Undelete(context.Context, *request.ApigwFunctionUndelete) (interface{}, error) + Definitions(context.Context, *request.ApigwFunctionDefinitions) (interface{}, error) } // HTTP API interface - Function struct { + ApigwFunction struct { List func(http.ResponseWriter, *http.Request) Create func(http.ResponseWriter, *http.Request) Update func(http.ResponseWriter, *http.Request) @@ -40,11 +40,11 @@ type ( } ) -func NewFunction(h FunctionAPI) *Function { - return &Function{ +func NewApigwFunction(h ApigwFunctionAPI) *ApigwFunction { + return &ApigwFunction{ List: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionList() + params := request.NewApigwFunctionList() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -60,7 +60,7 @@ func NewFunction(h FunctionAPI) *Function { }, Create: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionCreate() + params := request.NewApigwFunctionCreate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -76,7 +76,7 @@ func NewFunction(h FunctionAPI) *Function { }, Update: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionUpdate() + params := request.NewApigwFunctionUpdate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -92,7 +92,7 @@ func NewFunction(h FunctionAPI) *Function { }, Read: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionRead() + params := request.NewApigwFunctionRead() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -108,7 +108,7 @@ func NewFunction(h FunctionAPI) *Function { }, Delete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionDelete() + params := request.NewApigwFunctionDelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -124,7 +124,7 @@ func NewFunction(h FunctionAPI) *Function { }, Undelete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionUndelete() + params := request.NewApigwFunctionUndelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -140,7 +140,7 @@ func NewFunction(h FunctionAPI) *Function { }, Definitions: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionDefinitions() + params := request.NewApigwFunctionDefinitions() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -157,7 +157,7 @@ func NewFunction(h FunctionAPI) *Function { } } -func (h Function) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { +func (h ApigwFunction) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) r.Get("/apigw/function/", h.List) diff --git a/system/rest/handlers/route.go b/system/rest/handlers/apigwRoute.go similarity index 76% rename from system/rest/handlers/route.go rename to system/rest/handlers/apigwRoute.go index da7dcab82..d34587a4d 100644 --- a/system/rest/handlers/route.go +++ b/system/rest/handlers/apigwRoute.go @@ -18,17 +18,17 @@ import ( type ( // Internal API interface - RouteAPI interface { - List(context.Context, *request.RouteList) (interface{}, error) - Create(context.Context, *request.RouteCreate) (interface{}, error) - Update(context.Context, *request.RouteUpdate) (interface{}, error) - Read(context.Context, *request.RouteRead) (interface{}, error) - Delete(context.Context, *request.RouteDelete) (interface{}, error) - Undelete(context.Context, *request.RouteUndelete) (interface{}, error) + ApigwRouteAPI interface { + List(context.Context, *request.ApigwRouteList) (interface{}, error) + Create(context.Context, *request.ApigwRouteCreate) (interface{}, error) + Update(context.Context, *request.ApigwRouteUpdate) (interface{}, error) + Read(context.Context, *request.ApigwRouteRead) (interface{}, error) + Delete(context.Context, *request.ApigwRouteDelete) (interface{}, error) + Undelete(context.Context, *request.ApigwRouteUndelete) (interface{}, error) } // HTTP API interface - Route struct { + ApigwRoute struct { List func(http.ResponseWriter, *http.Request) Create func(http.ResponseWriter, *http.Request) Update func(http.ResponseWriter, *http.Request) @@ -38,11 +38,11 @@ type ( } ) -func NewRoute(h RouteAPI) *Route { - return &Route{ +func NewApigwRoute(h ApigwRouteAPI) *ApigwRoute { + return &ApigwRoute{ List: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteList() + params := request.NewApigwRouteList() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -58,7 +58,7 @@ func NewRoute(h RouteAPI) *Route { }, Create: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteCreate() + params := request.NewApigwRouteCreate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -74,7 +74,7 @@ func NewRoute(h RouteAPI) *Route { }, Update: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteUpdate() + params := request.NewApigwRouteUpdate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -90,7 +90,7 @@ func NewRoute(h RouteAPI) *Route { }, Read: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteRead() + params := request.NewApigwRouteRead() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -106,7 +106,7 @@ func NewRoute(h RouteAPI) *Route { }, Delete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteDelete() + params := request.NewApigwRouteDelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -122,7 +122,7 @@ func NewRoute(h RouteAPI) *Route { }, Undelete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteUndelete() + params := request.NewApigwRouteUndelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -139,7 +139,7 @@ func NewRoute(h RouteAPI) *Route { } } -func (h Route) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { +func (h ApigwRoute) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) r.Get("/apigw/route/", h.List) diff --git a/system/rest/request/function.go b/system/rest/request/apigwFunction.go similarity index 73% rename from system/rest/request/function.go rename to system/rest/request/apigwFunction.go index 20c4a4ea4..40d697951 100644 --- a/system/rest/request/function.go +++ b/system/rest/request/apigwFunction.go @@ -34,16 +34,11 @@ var ( type ( // Internal API interface - FunctionList struct { - // FunctionID GET parameter - // - // Filter by function ID - FunctionID []string - + ApigwFunctionList struct { // RouteID GET parameter // // Filter by route ID - RouteID string + RouteID uint64 `json:",string"` // Query GET parameter // @@ -76,7 +71,7 @@ type ( Sort string } - FunctionCreate struct { + ApigwFunctionCreate struct { // RouteID POST parameter // // Route @@ -90,7 +85,7 @@ type ( // Kind POST parameter // // Function kind - Kind types.ApigwFunctionKind + Kind string // Ref POST parameter // @@ -100,10 +95,10 @@ type ( // Params POST parameter // // Function parameters - Params types.FuncParams + Params types.ApigwFuncParams } - FunctionUpdate struct { + ApigwFunctionUpdate struct { // FunctionID PATH parameter // // Function ID @@ -122,7 +117,7 @@ type ( // Kind POST parameter // // Function kind - Kind types.ApigwFunctionKind + Kind string // Ref POST parameter // @@ -132,31 +127,31 @@ type ( // Params POST parameter // // Function parameters - Params types.FuncParams + Params types.ApigwFuncParams } - FunctionRead struct { + ApigwFunctionRead struct { // FunctionID PATH parameter // // Function ID FunctionID uint64 `json:",string"` } - FunctionDelete struct { + ApigwFunctionDelete struct { // FunctionID PATH parameter // // Function ID FunctionID uint64 `json:",string"` } - FunctionUndelete struct { + ApigwFunctionUndelete struct { // FunctionID PATH parameter // // Function ID FunctionID uint64 `json:",string"` } - FunctionDefinitions struct { + ApigwFunctionDefinitions struct { // Kind GET parameter // // Filter functions by kind @@ -164,15 +159,14 @@ type ( } ) -// NewFunctionList request -func NewFunctionList() *FunctionList { - return &FunctionList{} +// NewApigwFunctionList request +func NewApigwFunctionList() *ApigwFunctionList { + return &ApigwFunctionList{} } // Auditable returns all auditable/loggable parameters -func (r FunctionList) Auditable() map[string]interface{} { +func (r ApigwFunctionList) Auditable() map[string]interface{} { return map[string]interface{}{ - "functionID": r.FunctionID, "routeID": r.RouteID, "query": r.Query, "deleted": r.Deleted, @@ -184,65 +178,49 @@ func (r FunctionList) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetFunctionID() []string { - return r.FunctionID -} - -// Auditable returns all auditable/loggable parameters -func (r FunctionList) GetRouteID() string { +func (r ApigwFunctionList) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetQuery() string { +func (r ApigwFunctionList) GetQuery() string { return r.Query } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetDeleted() uint64 { +func (r ApigwFunctionList) GetDeleted() uint64 { return r.Deleted } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetDisabled() uint64 { +func (r ApigwFunctionList) GetDisabled() uint64 { return r.Disabled } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetLimit() uint { +func (r ApigwFunctionList) GetLimit() uint { return r.Limit } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetPageCursor() string { +func (r ApigwFunctionList) GetPageCursor() string { return r.PageCursor } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetSort() string { +func (r ApigwFunctionList) GetSort() string { return r.Sort } // Fill processes request and fills internal variables -func (r *FunctionList) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionList) Fill(req *http.Request) (err error) { { // GET params tmp := req.URL.Query() - if val, ok := tmp["functionID[]"]; ok { - r.FunctionID, err = val, nil - if err != nil { - return err - } - } else if val, ok := tmp["functionID"]; ok { - r.FunctionID, err = val, nil - if err != nil { - return err - } - } if val, ok := tmp["routeID"]; ok && len(val) > 0 { - r.RouteID, err = val[0], nil + r.RouteID, err = payload.ParseUint64(val[0]), nil if err != nil { return err } @@ -288,13 +266,13 @@ func (r *FunctionList) Fill(req *http.Request) (err error) { return err } -// NewFunctionCreate request -func NewFunctionCreate() *FunctionCreate { - return &FunctionCreate{} +// NewApigwFunctionCreate request +func NewApigwFunctionCreate() *ApigwFunctionCreate { + return &ApigwFunctionCreate{} } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) Auditable() map[string]interface{} { +func (r ApigwFunctionCreate) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, "weight": r.Weight, @@ -305,32 +283,32 @@ func (r FunctionCreate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetRouteID() uint64 { +func (r ApigwFunctionCreate) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetWeight() uint64 { +func (r ApigwFunctionCreate) GetWeight() uint64 { return r.Weight } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetKind() types.ApigwFunctionKind { +func (r ApigwFunctionCreate) GetKind() string { return r.Kind } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetRef() string { +func (r ApigwFunctionCreate) GetRef() string { return r.Ref } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetParams() types.FuncParams { +func (r ApigwFunctionCreate) GetParams() types.ApigwFuncParams { return r.Params } // Fill processes request and fills internal variables -func (r *FunctionCreate) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionCreate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -365,7 +343,7 @@ func (r *FunctionCreate) Fill(req *http.Request) (err error) { } if val, ok := req.Form["kind"]; ok && len(val) > 0 { - r.Kind, err = types.ApigwFunctionKind(val[0]), nil + r.Kind, err = val[0], nil if err != nil { return err } @@ -394,13 +372,13 @@ func (r *FunctionCreate) Fill(req *http.Request) (err error) { return err } -// NewFunctionUpdate request -func NewFunctionUpdate() *FunctionUpdate { - return &FunctionUpdate{} +// NewApigwFunctionUpdate request +func NewApigwFunctionUpdate() *ApigwFunctionUpdate { + return &ApigwFunctionUpdate{} } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) Auditable() map[string]interface{} { +func (r ApigwFunctionUpdate) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, "routeID": r.RouteID, @@ -412,37 +390,37 @@ func (r FunctionUpdate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetFunctionID() uint64 { +func (r ApigwFunctionUpdate) GetFunctionID() uint64 { return r.FunctionID } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetRouteID() uint64 { +func (r ApigwFunctionUpdate) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetWeight() uint64 { +func (r ApigwFunctionUpdate) GetWeight() uint64 { return r.Weight } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetKind() types.ApigwFunctionKind { +func (r ApigwFunctionUpdate) GetKind() string { return r.Kind } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetRef() string { +func (r ApigwFunctionUpdate) GetRef() string { return r.Ref } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetParams() types.FuncParams { +func (r ApigwFunctionUpdate) GetParams() types.ApigwFuncParams { return r.Params } // Fill processes request and fills internal variables -func (r *FunctionUpdate) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionUpdate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -477,7 +455,7 @@ func (r *FunctionUpdate) Fill(req *http.Request) (err error) { } if val, ok := req.Form["kind"]; ok && len(val) > 0 { - r.Kind, err = types.ApigwFunctionKind(val[0]), nil + r.Kind, err = val[0], nil if err != nil { return err } @@ -518,25 +496,25 @@ func (r *FunctionUpdate) Fill(req *http.Request) (err error) { return err } -// NewFunctionRead request -func NewFunctionRead() *FunctionRead { - return &FunctionRead{} +// NewApigwFunctionRead request +func NewApigwFunctionRead() *ApigwFunctionRead { + return &ApigwFunctionRead{} } // Auditable returns all auditable/loggable parameters -func (r FunctionRead) Auditable() map[string]interface{} { +func (r ApigwFunctionRead) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, } } // Auditable returns all auditable/loggable parameters -func (r FunctionRead) GetFunctionID() uint64 { +func (r ApigwFunctionRead) GetFunctionID() uint64 { return r.FunctionID } // Fill processes request and fills internal variables -func (r *FunctionRead) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionRead) Fill(req *http.Request) (err error) { { var val string @@ -553,25 +531,25 @@ func (r *FunctionRead) Fill(req *http.Request) (err error) { return err } -// NewFunctionDelete request -func NewFunctionDelete() *FunctionDelete { - return &FunctionDelete{} +// NewApigwFunctionDelete request +func NewApigwFunctionDelete() *ApigwFunctionDelete { + return &ApigwFunctionDelete{} } // Auditable returns all auditable/loggable parameters -func (r FunctionDelete) Auditable() map[string]interface{} { +func (r ApigwFunctionDelete) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, } } // Auditable returns all auditable/loggable parameters -func (r FunctionDelete) GetFunctionID() uint64 { +func (r ApigwFunctionDelete) GetFunctionID() uint64 { return r.FunctionID } // Fill processes request and fills internal variables -func (r *FunctionDelete) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionDelete) Fill(req *http.Request) (err error) { { var val string @@ -588,25 +566,25 @@ func (r *FunctionDelete) Fill(req *http.Request) (err error) { return err } -// NewFunctionUndelete request -func NewFunctionUndelete() *FunctionUndelete { - return &FunctionUndelete{} +// NewApigwFunctionUndelete request +func NewApigwFunctionUndelete() *ApigwFunctionUndelete { + return &ApigwFunctionUndelete{} } // Auditable returns all auditable/loggable parameters -func (r FunctionUndelete) Auditable() map[string]interface{} { +func (r ApigwFunctionUndelete) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, } } // Auditable returns all auditable/loggable parameters -func (r FunctionUndelete) GetFunctionID() uint64 { +func (r ApigwFunctionUndelete) GetFunctionID() uint64 { return r.FunctionID } // Fill processes request and fills internal variables -func (r *FunctionUndelete) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionUndelete) Fill(req *http.Request) (err error) { { var val string @@ -623,25 +601,25 @@ func (r *FunctionUndelete) Fill(req *http.Request) (err error) { return err } -// NewFunctionDefinitions request -func NewFunctionDefinitions() *FunctionDefinitions { - return &FunctionDefinitions{} +// NewApigwFunctionDefinitions request +func NewApigwFunctionDefinitions() *ApigwFunctionDefinitions { + return &ApigwFunctionDefinitions{} } // Auditable returns all auditable/loggable parameters -func (r FunctionDefinitions) Auditable() map[string]interface{} { +func (r ApigwFunctionDefinitions) Auditable() map[string]interface{} { return map[string]interface{}{ "kind": r.Kind, } } // Auditable returns all auditable/loggable parameters -func (r FunctionDefinitions) GetKind() string { +func (r ApigwFunctionDefinitions) GetKind() string { return r.Kind } // Fill processes request and fills internal variables -func (r *FunctionDefinitions) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionDefinitions) Fill(req *http.Request) (err error) { { // GET params diff --git a/system/rest/request/route.go b/system/rest/request/apigwRoute.go similarity index 78% rename from system/rest/request/route.go rename to system/rest/request/apigwRoute.go index 270f6bdb8..b2624db59 100644 --- a/system/rest/request/route.go +++ b/system/rest/request/apigwRoute.go @@ -34,11 +34,11 @@ var ( type ( // Internal API interface - RouteList struct { + ApigwRouteList struct { // RouteID GET parameter // // Filter by route ID - RouteID []string + RouteID []uint64 // Query GET parameter // @@ -76,7 +76,7 @@ type ( Sort string } - RouteCreate struct { + ApigwRouteCreate struct { // Endpoint POST parameter // // Route endpoint @@ -103,7 +103,7 @@ type ( Group uint64 `json:",string"` } - RouteUpdate struct { + ApigwRouteUpdate struct { // RouteID PATH parameter // // Route ID @@ -135,21 +135,21 @@ type ( Group uint64 `json:",string"` } - RouteRead struct { + ApigwRouteRead struct { // RouteID PATH parameter // // Route ID RouteID uint64 `json:",string"` } - RouteDelete struct { + ApigwRouteDelete struct { // RouteID PATH parameter // // Route ID RouteID uint64 `json:",string"` } - RouteUndelete struct { + ApigwRouteUndelete struct { // RouteID PATH parameter // // Route ID @@ -157,13 +157,13 @@ type ( } ) -// NewRouteList request -func NewRouteList() *RouteList { - return &RouteList{} +// NewApigwRouteList request +func NewApigwRouteList() *ApigwRouteList { + return &ApigwRouteList{} } // Auditable returns all auditable/loggable parameters -func (r RouteList) Auditable() map[string]interface{} { +func (r ApigwRouteList) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, "query": r.Query, @@ -177,59 +177,59 @@ func (r RouteList) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetRouteID() []string { +func (r ApigwRouteList) GetRouteID() []uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetQuery() string { +func (r ApigwRouteList) GetQuery() string { return r.Query } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetDeleted() uint64 { +func (r ApigwRouteList) GetDeleted() uint64 { return r.Deleted } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetDisabled() uint64 { +func (r ApigwRouteList) GetDisabled() uint64 { return r.Disabled } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetLabels() map[string]string { +func (r ApigwRouteList) GetLabels() map[string]string { return r.Labels } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetLimit() uint { +func (r ApigwRouteList) GetLimit() uint { return r.Limit } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetPageCursor() string { +func (r ApigwRouteList) GetPageCursor() string { return r.PageCursor } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetSort() string { +func (r ApigwRouteList) GetSort() string { return r.Sort } // Fill processes request and fills internal variables -func (r *RouteList) Fill(req *http.Request) (err error) { +func (r *ApigwRouteList) Fill(req *http.Request) (err error) { { // GET params tmp := req.URL.Query() if val, ok := tmp["routeID[]"]; ok { - r.RouteID, err = val, nil + r.RouteID, err = payload.ParseUint64s(val), nil if err != nil { return err } } else if val, ok := tmp["routeID"]; ok { - r.RouteID, err = val, nil + r.RouteID, err = payload.ParseUint64s(val), nil if err != nil { return err } @@ -286,13 +286,13 @@ func (r *RouteList) Fill(req *http.Request) (err error) { return err } -// NewRouteCreate request -func NewRouteCreate() *RouteCreate { - return &RouteCreate{} +// NewApigwRouteCreate request +func NewApigwRouteCreate() *ApigwRouteCreate { + return &ApigwRouteCreate{} } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) Auditable() map[string]interface{} { +func (r ApigwRouteCreate) Auditable() map[string]interface{} { return map[string]interface{}{ "endpoint": r.Endpoint, "method": r.Method, @@ -303,32 +303,32 @@ func (r RouteCreate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetEndpoint() string { +func (r ApigwRouteCreate) GetEndpoint() string { return r.Endpoint } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetMethod() string { +func (r ApigwRouteCreate) GetMethod() string { return r.Method } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetDebug() bool { +func (r ApigwRouteCreate) GetDebug() bool { return r.Debug } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetEnabled() bool { +func (r ApigwRouteCreate) GetEnabled() bool { return r.Enabled } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetGroup() uint64 { +func (r ApigwRouteCreate) GetGroup() uint64 { return r.Group } // Fill processes request and fills internal variables -func (r *RouteCreate) Fill(req *http.Request) (err error) { +func (r *ApigwRouteCreate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -387,13 +387,13 @@ func (r *RouteCreate) Fill(req *http.Request) (err error) { return err } -// NewRouteUpdate request -func NewRouteUpdate() *RouteUpdate { - return &RouteUpdate{} +// NewApigwRouteUpdate request +func NewApigwRouteUpdate() *ApigwRouteUpdate { + return &ApigwRouteUpdate{} } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) Auditable() map[string]interface{} { +func (r ApigwRouteUpdate) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, "endpoint": r.Endpoint, @@ -405,37 +405,37 @@ func (r RouteUpdate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetRouteID() uint64 { +func (r ApigwRouteUpdate) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetEndpoint() string { +func (r ApigwRouteUpdate) GetEndpoint() string { return r.Endpoint } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetMethod() string { +func (r ApigwRouteUpdate) GetMethod() string { return r.Method } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetDebug() bool { +func (r ApigwRouteUpdate) GetDebug() bool { return r.Debug } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetEnabled() bool { +func (r ApigwRouteUpdate) GetEnabled() bool { return r.Enabled } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetGroup() uint64 { +func (r ApigwRouteUpdate) GetGroup() uint64 { return r.Group } // Fill processes request and fills internal variables -func (r *RouteUpdate) Fill(req *http.Request) (err error) { +func (r *ApigwRouteUpdate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -506,25 +506,25 @@ func (r *RouteUpdate) Fill(req *http.Request) (err error) { return err } -// NewRouteRead request -func NewRouteRead() *RouteRead { - return &RouteRead{} +// NewApigwRouteRead request +func NewApigwRouteRead() *ApigwRouteRead { + return &ApigwRouteRead{} } // Auditable returns all auditable/loggable parameters -func (r RouteRead) Auditable() map[string]interface{} { +func (r ApigwRouteRead) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, } } // Auditable returns all auditable/loggable parameters -func (r RouteRead) GetRouteID() uint64 { +func (r ApigwRouteRead) GetRouteID() uint64 { return r.RouteID } // Fill processes request and fills internal variables -func (r *RouteRead) Fill(req *http.Request) (err error) { +func (r *ApigwRouteRead) Fill(req *http.Request) (err error) { { var val string @@ -541,25 +541,25 @@ func (r *RouteRead) Fill(req *http.Request) (err error) { return err } -// NewRouteDelete request -func NewRouteDelete() *RouteDelete { - return &RouteDelete{} +// NewApigwRouteDelete request +func NewApigwRouteDelete() *ApigwRouteDelete { + return &ApigwRouteDelete{} } // Auditable returns all auditable/loggable parameters -func (r RouteDelete) Auditable() map[string]interface{} { +func (r ApigwRouteDelete) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, } } // Auditable returns all auditable/loggable parameters -func (r RouteDelete) GetRouteID() uint64 { +func (r ApigwRouteDelete) GetRouteID() uint64 { return r.RouteID } // Fill processes request and fills internal variables -func (r *RouteDelete) Fill(req *http.Request) (err error) { +func (r *ApigwRouteDelete) Fill(req *http.Request) (err error) { { var val string @@ -576,25 +576,25 @@ func (r *RouteDelete) Fill(req *http.Request) (err error) { return err } -// NewRouteUndelete request -func NewRouteUndelete() *RouteUndelete { - return &RouteUndelete{} +// NewApigwRouteUndelete request +func NewApigwRouteUndelete() *ApigwRouteUndelete { + return &ApigwRouteUndelete{} } // Auditable returns all auditable/loggable parameters -func (r RouteUndelete) Auditable() map[string]interface{} { +func (r ApigwRouteUndelete) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, } } // Auditable returns all auditable/loggable parameters -func (r RouteUndelete) GetRouteID() uint64 { +func (r ApigwRouteUndelete) GetRouteID() uint64 { return r.RouteID } // Fill processes request and fills internal variables -func (r *RouteUndelete) Fill(req *http.Request) (err error) { +func (r *ApigwRouteUndelete) Fill(req *http.Request) (err error) { { var val string diff --git a/system/rest/router.go b/system/rest/router.go index 9fb27b3dc..90bb2963e 100644 --- a/system/rest/router.go +++ b/system/rest/router.go @@ -37,7 +37,7 @@ func MountRoutes(r chi.Router) { handlers.NewReminder(Reminder{}.New()).MountRoutes(r) handlers.NewActionlog(Actionlog{}.New()).MountRoutes(r) handlers.NewQueues(Queue{}.New()).MountRoutes(r) - handlers.NewRoute(Route{}.New()).MountRoutes(r) - handlers.NewFunction(Function{}.New()).MountRoutes(r) + handlers.NewApigwRoute(ApigwRoute{}.New()).MountRoutes(r) + handlers.NewApigwFunction(ApigwFunction{}.New()).MountRoutes(r) }) } diff --git a/system/service/access_control.gen.go b/system/service/access_control.gen.go index 16063b93a..fe03d1eaa 100644 --- a/system/service/access_control.gen.go +++ b/system/service/access_control.gen.go @@ -7,10 +7,11 @@ package service // // Definitions file that controls how this file is generated: +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -63,6 +64,36 @@ func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee func (svc accessControl) List() (out []map[string]string) { def := []map[string]string{ + { + "type": types.ApigwFunctionResourceType, + "any": types.ApigwFunctionRbacResource(0), + "op": "read", + }, + { + "type": types.ApigwFunctionResourceType, + "any": types.ApigwFunctionRbacResource(0), + "op": "update", + }, + { + "type": types.ApigwFunctionResourceType, + "any": types.ApigwFunctionRbacResource(0), + "op": "delete", + }, + { + "type": types.ApigwRouteResourceType, + "any": types.ApigwRouteRbacResource(0), + "op": "read", + }, + { + "type": types.ApigwRouteResourceType, + "any": types.ApigwRouteRbacResource(0), + "op": "update", + }, + { + "type": types.ApigwRouteResourceType, + "any": types.ApigwRouteRbacResource(0), + "op": "delete", + }, { "type": types.ApplicationResourceType, "any": types.ApplicationRbacResource(0), @@ -118,21 +149,6 @@ func (svc accessControl) List() (out []map[string]string) { "any": types.RoleRbacResource(0), "op": "members.manage", }, - { - "type": types.RouteResourceType, - "any": types.RouteRbacResource(0), - "op": "read", - }, - { - "type": types.RouteResourceType, - "any": types.RouteRbacResource(0), - "op": "update", - }, - { - "type": types.RouteResourceType, - "any": types.RouteRbacResource(0), - "op": "delete", - }, { "type": types.TemplateResourceType, "any": types.TemplateRbacResource(0), @@ -293,6 +309,21 @@ func (svc accessControl) List() (out []map[string]string) { "any": types.ComponentRbacResource(), "op": "api-gw-route.create", }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "api-gw-routes.search", + }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "api-gw-function.create", + }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "api-gw-functions.search", + }, } func(svc interface{}) { @@ -355,6 +386,48 @@ func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) ( return svc.rbac.FindRulesByRoleID(roleID), nil } +// CanReadApigwFunction checks if current user can read api gateway function +// +// This function is auto-generated +func (svc accessControl) CanReadApigwFunction(ctx context.Context, r *types.ApigwFunction) bool { + return svc.can(ctx, "read", r) +} + +// CanUpdateApigwFunction checks if current user can update api gateway function +// +// This function is auto-generated +func (svc accessControl) CanUpdateApigwFunction(ctx context.Context, r *types.ApigwFunction) bool { + return svc.can(ctx, "update", r) +} + +// CanDeleteApigwFunction checks if current user can delete api gateway function +// +// This function is auto-generated +func (svc accessControl) CanDeleteApigwFunction(ctx context.Context, r *types.ApigwFunction) bool { + return svc.can(ctx, "delete", r) +} + +// CanReadApigwRoute checks if current user can read api gateway route +// +// This function is auto-generated +func (svc accessControl) CanReadApigwRoute(ctx context.Context, r *types.ApigwRoute) bool { + return svc.can(ctx, "read", r) +} + +// CanUpdateApigwRoute checks if current user can update api gateway route +// +// This function is auto-generated +func (svc accessControl) CanUpdateApigwRoute(ctx context.Context, r *types.ApigwRoute) bool { + return svc.can(ctx, "update", r) +} + +// CanDeleteApigwRoute checks if current user can delete api gateway route +// +// This function is auto-generated +func (svc accessControl) CanDeleteApigwRoute(ctx context.Context, r *types.ApigwRoute) bool { + return svc.can(ctx, "delete", r) +} + // CanReadApplication checks if current user can read application // // This function is auto-generated @@ -432,27 +505,6 @@ func (svc accessControl) CanManageMembersOnRole(ctx context.Context, r *types.Ro return svc.can(ctx, "members.manage", r) } -// CanReadRoute checks if current user can read api gateway route -// -// This function is auto-generated -func (svc accessControl) CanReadRoute(ctx context.Context, r *types.Route) bool { - return svc.can(ctx, "read", r) -} - -// CanUpdateRoute checks if current user can update api gateway route -// -// This function is auto-generated -func (svc accessControl) CanUpdateRoute(ctx context.Context, r *types.Route) bool { - return svc.can(ctx, "update", r) -} - -// CanDeleteRoute checks if current user can delete api gateway route -// -// This function is auto-generated -func (svc accessControl) CanDeleteRoute(ctx context.Context, r *types.Route) bool { - return svc.can(ctx, "delete", r) -} - // CanReadTemplate checks if current user can read template // // This function is auto-generated @@ -677,19 +729,42 @@ func (svc accessControl) CanCreateApiGwRoute(ctx context.Context) bool { return svc.can(ctx, "api-gw-route.create", &types.Component{}) } +// CanSearchApiGwRoutes checks if current user can list search or filter api gateway routes +// +// This function is auto-generated +func (svc accessControl) CanSearchApiGwRoutes(ctx context.Context) bool { + return svc.can(ctx, "api-gw-routes.search", &types.Component{}) +} + +// CanCreateApiGwFunction checks if current user can add api gateway function to route +// +// This function is auto-generated +func (svc accessControl) CanCreateApiGwFunction(ctx context.Context) bool { + return svc.can(ctx, "api-gw-function.create", &types.Component{}) +} + +// CanSearchApiGwFunctions checks if current user can list, search or filter functions +// +// This function is auto-generated +func (svc accessControl) CanSearchApiGwFunctions(ctx context.Context) bool { + return svc.can(ctx, "api-gw-functions.search", &types.Component{}) +} + // rbacResourceValidator validates known component's resource by routing it to the appropriate validator // // This function is auto-generated func rbacResourceValidator(r string, oo ...string) error { switch rbac.ResourceType(r) { + case types.ApigwFunctionResourceType: + return rbacApigwFunctionResourceValidator(r, oo...) + case types.ApigwRouteResourceType: + return rbacApigwRouteResourceValidator(r, oo...) case types.ApplicationResourceType: return rbacApplicationResourceValidator(r, oo...) case types.AuthClientResourceType: return rbacAuthClientResourceValidator(r, oo...) case types.RoleResourceType: return rbacRoleResourceValidator(r, oo...) - case types.RouteResourceType: - return rbacRouteResourceValidator(r, oo...) case types.TemplateResourceType: return rbacTemplateResourceValidator(r, oo...) case types.UserResourceType: @@ -706,6 +781,18 @@ func rbacResourceValidator(r string, oo ...string) error { // This function is auto-generated func rbacResourceOperations(r string) map[string]bool { switch rbac.ResourceType(r) { + case types.ApigwFunctionResourceType: + return map[string]bool{ + "read": true, + "update": true, + "delete": true, + } + case types.ApigwRouteResourceType: + return map[string]bool{ + "read": true, + "update": true, + "delete": true, + } case types.ApplicationResourceType: return map[string]bool{ "read": true, @@ -726,12 +813,6 @@ func rbacResourceOperations(r string) map[string]bool { "delete": true, "members.manage": true, } - case types.RouteResourceType: - return map[string]bool{ - "read": true, - "update": true, - "delete": true, - } case types.TemplateResourceType: return map[string]bool{ "read": true, @@ -772,6 +853,111 @@ func rbacResourceOperations(r string) map[string]bool { "queue.create": true, "queues.search": true, "api-gw-route.create": true, + "api-gw-routes.search": true, + "api-gw-function.create": true, + "api-gw-functions.search": true, + } + } + + return nil +} + +// rbacApigwFunctionResourceValidator checks validity of rbac resource and operations +// +// Can be called without operations to check for validity of resource string only +// +// This function is auto-generated +func rbacApigwFunctionResourceValidator(r string, oo ...string) error { + defOps := rbacResourceOperations(r) + for _, o := range oo { + if !defOps[o] { + return fmt.Errorf("invalid operation '%s' for system ApigwFunction resource", o) + } + } + + if !strings.HasPrefix(r, types.ApigwFunctionResourceType) { + // expecting resource to always include path + return fmt.Errorf("invalid resource type") + } + + const sep = "/" + var ( + specIdUsed = true + + pp = strings.Split(strings.Trim(r[len(types.ApigwFunctionResourceType):], sep), sep) + prc = []string{ + "ID", + } + ) + + if len(pp) != len(prc) { + return fmt.Errorf("invalid resource path structure") + } + + for i, p := range pp { + if p == "*" { + if !specIdUsed { + return fmt.Errorf("invalid resource path wildcard level (%d) for ApigwFunction", i) + } + + specIdUsed = false + continue + } + + specIdUsed = true + if _, err := cast.ToUint64E(p); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p) + } + } + + return nil +} + +// rbacApigwRouteResourceValidator checks validity of rbac resource and operations +// +// Can be called without operations to check for validity of resource string only +// +// This function is auto-generated +func rbacApigwRouteResourceValidator(r string, oo ...string) error { + defOps := rbacResourceOperations(r) + for _, o := range oo { + if !defOps[o] { + return fmt.Errorf("invalid operation '%s' for system ApigwRoute resource", o) + } + } + + if !strings.HasPrefix(r, types.ApigwRouteResourceType) { + // expecting resource to always include path + return fmt.Errorf("invalid resource type") + } + + const sep = "/" + var ( + specIdUsed = true + + pp = strings.Split(strings.Trim(r[len(types.ApigwRouteResourceType):], sep), sep) + prc = []string{ + "ID", + } + ) + + if len(pp) != len(prc) { + return fmt.Errorf("invalid resource path structure") + } + + for i, p := range pp { + if p == "*" { + if !specIdUsed { + return fmt.Errorf("invalid resource path wildcard level (%d) for ApigwRoute", i) + } + + specIdUsed = false + continue + } + + specIdUsed = true + if _, err := cast.ToUint64E(p); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p) } } @@ -816,16 +1002,11 @@ func rbacApplicationResourceValidator(r string, oo ...string) error { return fmt.Errorf("invalid resource path wildcard level (%d) for Application", i) } - specIdUsed = false - continue - } - - specIdUsed = true - if _, err := cast.ToUint64E(p); err != nil { - return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p) + if _, err := cast.ToUint64E(pp[i]); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i]) + } } } - return nil } @@ -921,52 +1102,6 @@ func rbacRoleResourceValidator(r string, oo ...string) error { return nil } -// rbacRouteResourceValidator checks validity of rbac resource and operations -// -// Can be called without operations to check for validity of resource string only -// -// This function is auto-generated -func rbacRouteResourceValidator(r string, oo ...string) error { - defOps := rbacResourceOperations(r) - for _, o := range oo { - if !defOps[o] { - return fmt.Errorf("invalid operation '%s' for system Route resource", o) - } - } - - if !strings.HasPrefix(r, types.RouteResourceType) { - // expecting resource to always include path - return fmt.Errorf("invalid resource type") - } - - const sep = "/" - var ( - specIdUsed = true - - pp = strings.Split(strings.Trim(r[len(types.RouteResourceType):], sep), sep) - prc = []string{ - "ID", - } - ) - - if len(pp) != len(prc) { - return fmt.Errorf("invalid resource path structure") - } - - for i, p := range pp { - if p == "*" { - if !specIdUsed { - return fmt.Errorf("invalid resource path wildcard level (%d) for Route", i) - } - - if _, err := cast.ToUint64E(pp[i]); err != nil { - return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i]) - } - } - } - return nil -} - // rbacTemplateResourceValidator checks validity of rbac resource and operations // // Can be called without operations to check for validity of resource string only diff --git a/system/service/apigw_function.go b/system/service/apigw_function.go new file mode 100644 index 000000000..b50195394 --- /dev/null +++ b/system/service/apigw_function.go @@ -0,0 +1,249 @@ +package service + +import ( + "context" + + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/apigw" + a "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + apigwFunction struct { + actionlog actionlog.Recorder + store store.Storer + ac functionAccessController + } + + functionAccessController interface { + CanSearchApiGwFunctions(context.Context) bool + + CanCreateApiGwFunction(context.Context) bool + CanReadApigwFunction(context.Context, *types.ApigwFunction) bool + CanUpdateApigwFunction(context.Context, *types.ApigwFunction) bool + CanDeleteApigwFunction(context.Context, *types.ApigwFunction) bool + } +) + +func Function() *apigwFunction { + return (&apigwFunction{ + ac: DefaultAccessControl, + actionlog: DefaultActionlog, + store: DefaultStore, + }) +} + +func (svc *apigwFunction) FindByID(ctx context.Context, ID uint64) (q *types.ApigwFunction, err error) { + var ( + rProps = &apigwFunctionActionProps{} + ) + + err = func() error { + if ID == 0 { + return ApigwFunctionErrInvalidID() + } + + if !svc.ac.CanSearchApiGwFunctions(ctx) { + return ApigwFunctionErrNotAllowedToRead(rProps) + } + + if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { + return TemplateErrInvalidID().Wrap(err) + } + + rProps.setFunction(q) + + return nil + }() + + return q, svc.recordAction(ctx, rProps, ApigwFunctionActionLookup, err) +} + +func (svc *apigwFunction) Create(ctx context.Context, new *types.ApigwFunction) (q *types.ApigwFunction, err error) { + var ( + qProps = &apigwFunctionActionProps{function: new} + ) + + err = func() (err error) { + if !svc.ac.CanCreateApiGwFunction(ctx) { + return ApigwFunctionErrNotAllowedToCreate(qProps) + } + + // Set new values after beforeCreate events are emitted + new.ID = nextID() + new.CreatedAt = *now() + new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() + + if _, err = DefaultRoute.FindByID(ctx, new.Route); err != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if err = store.CreateApigwFunction(ctx, svc.store, new); err != nil { + return err + } + + q = new + // send the signal to reload all functions + apigw.Service().Reload(ctx) + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwFunctionActionCreate, err) +} + +func (svc *apigwFunction) Update(ctx context.Context, upd *types.ApigwFunction) (q *types.ApigwFunction, err error) { + var ( + qProps = &apigwFunctionActionProps{function: upd} + qq *types.ApigwFunction + e error + ) + + err = func() (err error) { + if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if !svc.ac.CanUpdateApigwFunction(ctx, qq) { + return ApigwFunctionErrNotAllowedToUpdate(qProps) + } + + if _, err = DefaultRoute.FindByID(ctx, upd.Route); err != nil { + return err + } + + if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e == nil && qq == nil { + return ApigwFunctionErrNotFound(qProps) + } + + upd.UpdatedAt = now() + upd.CreatedAt = qq.CreatedAt + upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwFunction(ctx, svc.store, upd); err != nil { + return + } + + q = upd + + // send the signal to reload all function + apigw.Service().Reload(ctx) + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwFunctionActionUpdate, err) +} + +func (svc *apigwFunction) DeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwFunctionActionProps{} + q *types.ApigwFunction + ) + + err = func() (err error) { + if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if !svc.ac.CanDeleteApigwFunction(ctx, q) { + return ApigwFunctionErrNotAllowedToDelete(qProps) + } + + qProps.setFunction(q) + + q.DeletedAt = now() + q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwFunctionActionDelete, err) +} + +func (svc *apigwFunction) UndeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwFunctionActionProps{} + q *types.ApigwFunction + ) + + err = func() (err error) { + if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if !svc.ac.CanDeleteApigwFunction(ctx, q) { + return ApigwFunctionErrNotAllowedToDelete(qProps) + } + + qProps.setFunction(q) + + q.DeletedAt = nil + q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwFunctionActionDelete, err) +} + +func (svc *apigwFunction) Search(ctx context.Context, filter types.ApigwFunctionFilter) (r types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + var ( + aProps = &apigwFunctionActionProps{search: &filter} + ) + + // For each fetched item, store backend will check if it is valid or not + filter.Check = func(res *types.ApigwFunction) (bool, error) { + if !svc.ac.CanReadApigwFunction(ctx, res) { + return false, nil + } + + return true, nil + } + + err = func() error { + if r, f, err = store.SearchApigwFunctions(ctx, svc.store, filter); err != nil { + return err + } + + return nil + }() + + return r, f, svc.recordAction(ctx, aProps, ApigwFunctionActionSearch, err) +} + +func (svc *apigwFunction) Definitions(ctx context.Context, kind string) (l interface{}, err error) { + var ( + qProps = &apigwFunctionActionProps{} + ) + + err = func() error { + if !svc.ac.CanSearchApiGwFunctions(ctx) { + return ApigwFunctionErrNotAllowedToRead(qProps) + } + + // get the definitions from registry + l = apigw.Service().Funcs(kind) + + return nil + }() + + return l, svc.recordAction(ctx, qProps, ApigwFunctionActionSearch, err) + +} diff --git a/system/service/function_actions.gen.go b/system/service/apigw_function_actions.gen.go similarity index 50% rename from system/service/function_actions.gen.go rename to system/service/apigw_function_actions.gen.go index de483497f..0a08fa64b 100644 --- a/system/service/function_actions.gen.go +++ b/system/service/apigw_function_actions.gen.go @@ -6,7 +6,7 @@ package service // the code is regenerated. // // Definitions file that controls how this file is generated: -// system/service/function_actions.yaml +// system/service/apigw_function_actions.yaml import ( "context" @@ -19,12 +19,12 @@ import ( ) type ( - functionActionProps struct { - function *types.Function - search *types.FunctionFilter + apigwFunctionActionProps struct { + function *types.ApigwFunction + search *types.ApigwFunctionFilter } - functionAction struct { + apigwFunctionAction struct { timestamp time.Time resource string action string @@ -34,11 +34,11 @@ type ( // prefix for error when action fails errorMessage string - props *functionActionProps + props *apigwFunctionActionProps } - functionLogMetaKey struct{} - functionPropsMetaKey struct{} + apigwFunctionLogMetaKey struct{} + apigwFunctionPropsMetaKey struct{} ) var ( @@ -49,33 +49,33 @@ var ( // ********************************************************************************************************************* // ********************************************************************************************************************* // Props methods -// setFunction updates functionActionProps's function +// setFunction updates apigwFunctionActionProps's function // // Allows method chaining // // This function is auto-generated. // -func (p *functionActionProps) setFunction(function *types.Function) *functionActionProps { +func (p *apigwFunctionActionProps) setFunction(function *types.ApigwFunction) *apigwFunctionActionProps { p.function = function return p } -// setSearch updates functionActionProps's search +// setSearch updates apigwFunctionActionProps's search // // Allows method chaining // // This function is auto-generated. // -func (p *functionActionProps) setSearch(search *types.FunctionFilter) *functionActionProps { +func (p *apigwFunctionActionProps) setSearch(search *types.ApigwFunctionFilter) *apigwFunctionActionProps { p.search = search return p } -// Serialize converts functionActionProps to actionlog.Meta +// Serialize converts apigwFunctionActionProps to actionlog.Meta // // This function is auto-generated. // -func (p functionActionProps) Serialize() actionlog.Meta { +func (p apigwFunctionActionProps) Serialize() actionlog.Meta { var ( m = make(actionlog.Meta) ) @@ -94,7 +94,7 @@ func (p functionActionProps) Serialize() actionlog.Meta { // // This function is auto-generated. // -func (p functionActionProps) Format(in string, err error) string { +func (p apigwFunctionActionProps) Format(in string, err error) string { var ( pairs = []string{"{err}"} // first non-empty string @@ -148,8 +148,8 @@ func (p functionActionProps) Format(in string, err error) string { // // This function is auto-generated. // -func (a *functionAction) String() string { - var props = &functionActionProps{} +func (a *apigwFunctionAction) String() string { + var props = &apigwFunctionActionProps{} if a.props != nil { props = a.props @@ -158,7 +158,7 @@ func (a *functionAction) String() string { return props.Format(a.log, nil) } -func (e *functionAction) ToAction() *actionlog.Action { +func (e *apigwFunctionAction) ToAction() *actionlog.Action { return &actionlog.Action{ Resource: e.resource, Action: e.action, @@ -172,12 +172,12 @@ func (e *functionAction) ToAction() *actionlog.Action { // ********************************************************************************************************************* // Action constructors -// FunctionActionSearch returns "system:function.search" action +// ApigwFunctionActionSearch returns "system:function.search" action // // This function is auto-generated. // -func FunctionActionSearch(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionSearch(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "search", @@ -192,12 +192,12 @@ func FunctionActionSearch(props ...*functionActionProps) *functionAction { return a } -// FunctionActionLookup returns "system:function.lookup" action +// ApigwFunctionActionLookup returns "system:function.lookup" action // // This function is auto-generated. // -func FunctionActionLookup(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionLookup(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "lookup", @@ -212,12 +212,12 @@ func FunctionActionLookup(props ...*functionActionProps) *functionAction { return a } -// FunctionActionCreate returns "system:function.create" action +// ApigwFunctionActionCreate returns "system:function.create" action // // This function is auto-generated. // -func FunctionActionCreate(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionCreate(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "create", @@ -232,12 +232,12 @@ func FunctionActionCreate(props ...*functionActionProps) *functionAction { return a } -// FunctionActionUpdate returns "system:function.update" action +// ApigwFunctionActionUpdate returns "system:function.update" action // // This function is auto-generated. // -func FunctionActionUpdate(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionUpdate(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "update", @@ -252,12 +252,12 @@ func FunctionActionUpdate(props ...*functionActionProps) *functionAction { return a } -// FunctionActionDelete returns "system:function.delete" action +// ApigwFunctionActionDelete returns "system:function.delete" action // // This function is auto-generated. // -func FunctionActionDelete(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionDelete(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "delete", @@ -272,12 +272,12 @@ func FunctionActionDelete(props ...*functionActionProps) *functionAction { return a } -// FunctionActionUndelete returns "system:function.undelete" action +// ApigwFunctionActionUndelete returns "system:function.undelete" action // // This function is auto-generated. // -func FunctionActionUndelete(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionUndelete(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "undelete", @@ -296,13 +296,13 @@ func FunctionActionUndelete(props ...*functionActionProps) *functionAction { // ********************************************************************************************************************* // Error constructors -// FunctionErrGeneric returns "system:function.generic" as *errors.Error +// ApigwFunctionErrGeneric returns "system:function.generic" as *errors.Error // // // This function is auto-generated. // -func FunctionErrGeneric(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrGeneric(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -316,8 +316,8 @@ func FunctionErrGeneric(mm ...*functionActionProps) *errors.Error { errors.Meta("resource", "system:function"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(functionLogMetaKey{}, "{err}"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionLogMetaKey{}, "{err}"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -328,13 +328,13 @@ func FunctionErrGeneric(mm ...*functionActionProps) *errors.Error { return e } -// FunctionErrNotFound returns "system:function.notFound" as *errors.Error +// ApigwFunctionErrNotFound returns "system:function.notFound" as *errors.Error // // // This function is auto-generated. // -func FunctionErrNotFound(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrNotFound(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -347,7 +347,7 @@ func FunctionErrNotFound(mm ...*functionActionProps) *errors.Error { errors.Meta("type", "notFound"), errors.Meta("resource", "system:function"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -358,13 +358,13 @@ func FunctionErrNotFound(mm ...*functionActionProps) *errors.Error { return e } -// FunctionErrInvalidID returns "system:function.invalidID" as *errors.Error +// ApigwFunctionErrInvalidID returns "system:function.invalidID" as *errors.Error // // // This function is auto-generated. // -func FunctionErrInvalidID(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrInvalidID(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -377,7 +377,7 @@ func FunctionErrInvalidID(mm ...*functionActionProps) *errors.Error { errors.Meta("type", "invalidID"), errors.Meta("resource", "system:function"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -388,13 +388,13 @@ func FunctionErrInvalidID(mm ...*functionActionProps) *errors.Error { return e } -// FunctionErrInvalidRoute returns "system:function.invalidRoute" as *errors.Error +// ApigwFunctionErrInvalidRoute returns "system:function.invalidRoute" as *errors.Error // // // This function is auto-generated. // -func FunctionErrInvalidRoute(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrInvalidRoute(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -407,7 +407,167 @@ func FunctionErrInvalidRoute(mm ...*functionActionProps) *errors.Error { errors.Meta("type", "invalidRoute"), errors.Meta("resource", "system:function"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToCreate returns "system:function.notAllowedToCreate" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToCreate(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to create a function", nil), + + errors.Meta("type", "notAllowedToCreate"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to create a route; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToRead returns "system:function.notAllowedToRead" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToRead(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to read this function", nil), + + errors.Meta("type", "notAllowedToRead"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to read {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToUpdate returns "system:function.notAllowedToUpdate" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToUpdate(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to update this function", nil), + + errors.Meta("type", "notAllowedToUpdate"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to update {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToDelete returns "system:function.notAllowedToDelete" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToDelete(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to delete this function", nil), + + errors.Meta("type", "notAllowedToDelete"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to delete {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToUndelete returns "system:function.notAllowedToUndelete" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToUndelete(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to undelete this function", nil), + + errors.Meta("type", "notAllowedToUndelete"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to undelete {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -427,7 +587,7 @@ func FunctionErrInvalidRoute(mm ...*functionActionProps) *errors.Error { // // This function is auto-generated. // -func (svc function) recordAction(ctx context.Context, props *functionActionProps, actionFn func(...*functionActionProps) *functionAction, err error) error { +func (svc apigwFunction) recordAction(ctx context.Context, props *apigwFunctionActionProps, actionFn func(...*apigwFunctionActionProps) *apigwFunctionAction, err error) error { if svc.actionlog == nil || actionFn == nil { // action log disabled or no action fn passed, return error as-is return err @@ -448,10 +608,10 @@ func (svc function) recordAction(ctx context.Context, props *functionActionProps a.Error = err.Error() a.Severity = actionlog.Severity(m.AsInt("severity")) - a.Description = props.Format(m.AsString(functionLogMetaKey{}), err) + a.Description = props.Format(m.AsString(apigwFunctionLogMetaKey{}), err) - if p, has := m[functionPropsMetaKey{}]; has { - a.Meta = p.(*functionActionProps).Serialize() + if p, has := m[apigwFunctionPropsMetaKey{}]; has { + a.Meta = p.(*apigwFunctionActionProps).Serialize() } svc.actionlog.Record(ctx, a) diff --git a/system/service/function_actions.yaml b/system/service/apigw_function_actions.yaml similarity index 56% rename from system/service/function_actions.yaml rename to system/service/apigw_function_actions.yaml index 0cff17d00..400b43c5e 100644 --- a/system/service/function_actions.yaml +++ b/system/service/apigw_function_actions.yaml @@ -1,7 +1,7 @@ # List of loggable service actions resource: system:function -service: function +service: apigwFunction # Default sensitivity for actions defaultActionSeverity: notice @@ -14,7 +14,7 @@ import: props: - name: function - type: "*types.Function" + type: "*types.ApigwFunction" fields: [ ID, ref ] # - name: new # type: "*types.Route" @@ -23,7 +23,7 @@ props: # type: "*types.Route" # fields: [ endpoint, ID ] - name: search - type: "*types.FunctionFilter" + type: "*types.ApigwFunctionFilter" fields: [] actions: @@ -59,3 +59,23 @@ errors: - error: invalidRoute message: "invalid route" severity: warning + + - error: notAllowedToCreate + message: "not allowed to create a function" + log: "failed to create a route; insufficient permissions" + + - error: notAllowedToRead + message: "not allowed to read this function" + log: "failed to read {function}; insufficient permissions" + + - error: notAllowedToUpdate + message: "not allowed to update this function" + log: "failed to update {function}; insufficient permissions" + + - error: notAllowedToDelete + message: "not allowed to delete this function" + log: "failed to delete {function}; insufficient permissions" + + - error: notAllowedToUndelete + message: "not allowed to undelete this function" + log: "failed to undelete {function}; insufficient permissions" diff --git a/system/service/apigw_route.go b/system/service/apigw_route.go new file mode 100644 index 000000000..b2d1093b1 --- /dev/null +++ b/system/service/apigw_route.go @@ -0,0 +1,241 @@ +package service + +import ( + "context" + + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/apigw" + a "github.com/cortezaproject/corteza-server/pkg/auth" + + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + apigwRoute struct { + actionlog actionlog.Recorder + store store.Storer + ac routeAccessController + } + + routeAccessController interface { + CanGrant(context.Context) bool + CanSearchApiGwRoutes(ctx context.Context) bool + + CanCreateApiGwRoute(context.Context) bool + CanReadApigwRoute(context.Context, *types.ApigwRoute) bool + CanUpdateApigwRoute(context.Context, *types.ApigwRoute) bool + CanDeleteApigwRoute(context.Context, *types.ApigwRoute) bool + } +) + +func Route() *apigwRoute { + return (&apigwRoute{ + ac: DefaultAccessControl, + actionlog: DefaultActionlog, + store: DefaultStore, + }) +} + +func (svc *apigwRoute) FindByID(ctx context.Context, ID uint64) (q *types.ApigwRoute, err error) { + var ( + rProps = &apigwRouteActionProps{} + ) + + err = func() error { + if ID == 0 { + return ApigwRouteErrInvalidID() + } + + if !svc.ac.CanSearchApiGwRoutes(ctx) { + return ApigwRouteErrNotAllowedToRead(rProps) + } + + if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { + return ApigwRouteErrInvalidID().Wrap(err) + } + + rProps.setRoute(q) + + if !svc.ac.CanReadApigwRoute(ctx, q) { + return ApigwRouteErrNotAllowedToRead(rProps) + } + + return nil + }() + + return q, svc.recordAction(ctx, rProps, ApigwRouteActionLookup, err) +} + +func (svc *apigwRoute) Create(ctx context.Context, new *types.ApigwRoute) (q *types.ApigwRoute, err error) { + var ( + qProps = &apigwRouteActionProps{new: new} + ) + + err = func() (err error) { + if !svc.ac.CanCreateApiGwRoute(ctx) { + return ApigwRouteErrNotAllowedToCreate(qProps) + } + + new.ID = nextID() + new.CreatedAt = *now() + new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() + + // todo + new.Group = 0 + + if err = store.CreateApigwRoute(ctx, svc.store, new); err != nil { + return err + } + + q = new + + // send the signal to reload all routes + if new.Enabled { + apigw.Service().Reload(ctx) + } + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwRouteActionCreate, err) +} + +func (svc *apigwRoute) Update(ctx context.Context, upd *types.ApigwRoute) (q *types.ApigwRoute, err error) { + var ( + qProps = &apigwRouteActionProps{update: upd} + qq *types.ApigwRoute + e error + ) + + err = func() (err error) { + if qq, e = store.LookupApigwRouteByID(ctx, svc.store, upd.ID); e != nil { + return ApigwRouteErrNotFound(qProps) + } + + if !svc.ac.CanUpdateApigwRoute(ctx, qq) { + return ApigwRouteErrNotAllowedToUpdate(qProps) + } + + // temp todo - update itself with the same endpoint + // if qq, e = store.LookupApigwRouteByEndpoint(ctx, svc.store, upd.Endpoint); e == nil && qq == nil { + // return ApigwRouteErrExistsEndpoint(qProps) + // } + + upd.UpdatedAt = now() + upd.CreatedAt = qq.CreatedAt + upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwRoute(ctx, svc.store, upd); err != nil { + return + } + + q = upd + + // send the signal to reload all route + apigw.Service().Reload(ctx) + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwRouteActionUpdate, err) +} + +func (svc *apigwRoute) DeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwRouteActionProps{} + q *types.ApigwRoute + ) + + err = func() (err error) { + if ID == 0 { + return ApigwRouteErrInvalidID() + } + + if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { + return + } + + if !svc.ac.CanDeleteApigwRoute(ctx, q) { + return ApigwRouteErrNotAllowedToDelete(qProps) + } + + qProps.setRoute(q) + + q.DeletedAt = now() + q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwRouteActionDelete, err) +} + +func (svc *apigwRoute) UndeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwRouteActionProps{} + q *types.ApigwRoute + ) + + err = func() (err error) { + if ID == 0 { + return ApigwRouteErrInvalidID() + } + + if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { + return + } + + if !svc.ac.CanDeleteApigwRoute(ctx, q) { + return ApigwRouteErrNotAllowedToDelete(qProps) + } + + qProps.setRoute(q) + + q.DeletedAt = nil + q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwRouteActionDelete, err) +} + +func (svc *apigwRoute) Search(ctx context.Context, filter types.ApigwRouteFilter) (r types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + var ( + aProps = &apigwRouteActionProps{search: &filter} + ) + + // For each fetched item, store backend will check if it is valid or not + filter.Check = func(res *types.ApigwRoute) (bool, error) { + if !svc.ac.CanReadApigwRoute(ctx, res) { + return false, nil + } + + return true, nil + } + + err = func() error { + if r, f, err = store.SearchApigwRoutes(ctx, svc.store, filter); err != nil { + return err + } + + return nil + }() + + return r, f, svc.recordAction(ctx, aProps, ApigwRouteActionSearch, err) +} diff --git a/system/service/route_actions.gen.go b/system/service/apigw_route_actions.gen.go similarity index 65% rename from system/service/route_actions.gen.go rename to system/service/apigw_route_actions.gen.go index ee466504b..8be6ff46e 100644 --- a/system/service/route_actions.gen.go +++ b/system/service/apigw_route_actions.gen.go @@ -6,7 +6,7 @@ package service // the code is regenerated. // // Definitions file that controls how this file is generated: -// system/service/route_actions.yaml +// system/service/apigw_route_actions.yaml import ( "context" @@ -19,14 +19,14 @@ import ( ) type ( - routeActionProps struct { - route *types.Route - new *types.Route - update *types.Route - search *types.RouteFilter + apigwRouteActionProps struct { + route *types.ApigwRoute + new *types.ApigwRoute + update *types.ApigwRoute + search *types.ApigwRouteFilter } - routeAction struct { + apigwRouteAction struct { timestamp time.Time resource string action string @@ -36,11 +36,11 @@ type ( // prefix for error when action fails errorMessage string - props *routeActionProps + props *apigwRouteActionProps } - routeLogMetaKey struct{} - routePropsMetaKey struct{} + apigwRouteLogMetaKey struct{} + apigwRoutePropsMetaKey struct{} ) var ( @@ -51,55 +51,55 @@ var ( // ********************************************************************************************************************* // ********************************************************************************************************************* // Props methods -// setRoute updates routeActionProps's route +// setRoute updates apigwRouteActionProps's route // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setRoute(route *types.Route) *routeActionProps { +func (p *apigwRouteActionProps) setRoute(route *types.ApigwRoute) *apigwRouteActionProps { p.route = route return p } -// setNew updates routeActionProps's new +// setNew updates apigwRouteActionProps's new // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setNew(new *types.Route) *routeActionProps { +func (p *apigwRouteActionProps) setNew(new *types.ApigwRoute) *apigwRouteActionProps { p.new = new return p } -// setUpdate updates routeActionProps's update +// setUpdate updates apigwRouteActionProps's update // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setUpdate(update *types.Route) *routeActionProps { +func (p *apigwRouteActionProps) setUpdate(update *types.ApigwRoute) *apigwRouteActionProps { p.update = update return p } -// setSearch updates routeActionProps's search +// setSearch updates apigwRouteActionProps's search // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setSearch(search *types.RouteFilter) *routeActionProps { +func (p *apigwRouteActionProps) setSearch(search *types.ApigwRouteFilter) *apigwRouteActionProps { p.search = search return p } -// Serialize converts routeActionProps to actionlog.Meta +// Serialize converts apigwRouteActionProps to actionlog.Meta // // This function is auto-generated. // -func (p routeActionProps) Serialize() actionlog.Meta { +func (p apigwRouteActionProps) Serialize() actionlog.Meta { var ( m = make(actionlog.Meta) ) @@ -126,7 +126,7 @@ func (p routeActionProps) Serialize() actionlog.Meta { // // This function is auto-generated. // -func (p routeActionProps) Format(in string, err error) string { +func (p apigwRouteActionProps) Format(in string, err error) string { var ( pairs = []string{"{err}"} // first non-empty string @@ -208,8 +208,8 @@ func (p routeActionProps) Format(in string, err error) string { // // This function is auto-generated. // -func (a *routeAction) String() string { - var props = &routeActionProps{} +func (a *apigwRouteAction) String() string { + var props = &apigwRouteActionProps{} if a.props != nil { props = a.props @@ -218,7 +218,7 @@ func (a *routeAction) String() string { return props.Format(a.log, nil) } -func (e *routeAction) ToAction() *actionlog.Action { +func (e *apigwRouteAction) ToAction() *actionlog.Action { return &actionlog.Action{ Resource: e.resource, Action: e.action, @@ -232,12 +232,12 @@ func (e *routeAction) ToAction() *actionlog.Action { // ********************************************************************************************************************* // Action constructors -// RouteActionSearch returns "system:route.search" action +// ApigwRouteActionSearch returns "system:route.search" action // // This function is auto-generated. // -func RouteActionSearch(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionSearch(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "search", @@ -252,12 +252,12 @@ func RouteActionSearch(props ...*routeActionProps) *routeAction { return a } -// RouteActionLookup returns "system:route.lookup" action +// ApigwRouteActionLookup returns "system:route.lookup" action // // This function is auto-generated. // -func RouteActionLookup(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionLookup(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "lookup", @@ -272,12 +272,12 @@ func RouteActionLookup(props ...*routeActionProps) *routeAction { return a } -// RouteActionCreate returns "system:route.create" action +// ApigwRouteActionCreate returns "system:route.create" action // // This function is auto-generated. // -func RouteActionCreate(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionCreate(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "create", @@ -292,12 +292,12 @@ func RouteActionCreate(props ...*routeActionProps) *routeAction { return a } -// RouteActionUpdate returns "system:route.update" action +// ApigwRouteActionUpdate returns "system:route.update" action // // This function is auto-generated. // -func RouteActionUpdate(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionUpdate(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "update", @@ -312,12 +312,12 @@ func RouteActionUpdate(props ...*routeActionProps) *routeAction { return a } -// RouteActionDelete returns "system:route.delete" action +// ApigwRouteActionDelete returns "system:route.delete" action // // This function is auto-generated. // -func RouteActionDelete(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionDelete(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "delete", @@ -332,12 +332,12 @@ func RouteActionDelete(props ...*routeActionProps) *routeAction { return a } -// RouteActionUndelete returns "system:route.undelete" action +// ApigwRouteActionUndelete returns "system:route.undelete" action // // This function is auto-generated. // -func RouteActionUndelete(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionUndelete(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "undelete", @@ -356,13 +356,13 @@ func RouteActionUndelete(props ...*routeActionProps) *routeAction { // ********************************************************************************************************************* // Error constructors -// RouteErrGeneric returns "system:route.generic" as *errors.Error +// ApigwRouteErrGeneric returns "system:route.generic" as *errors.Error // // // This function is auto-generated. // -func RouteErrGeneric(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrGeneric(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -376,8 +376,8 @@ func RouteErrGeneric(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "{err}"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "{err}"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -388,13 +388,13 @@ func RouteErrGeneric(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotFound returns "system:route.notFound" as *errors.Error +// ApigwRouteErrNotFound returns "system:route.notFound" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotFound(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotFound(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -407,7 +407,7 @@ func RouteErrNotFound(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "notFound"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -418,13 +418,13 @@ func RouteErrNotFound(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrInvalidID returns "system:route.invalidID" as *errors.Error +// ApigwRouteErrInvalidID returns "system:route.invalidID" as *errors.Error // // // This function is auto-generated. // -func RouteErrInvalidID(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrInvalidID(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -437,7 +437,7 @@ func RouteErrInvalidID(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "invalidID"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -448,13 +448,13 @@ func RouteErrInvalidID(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrInvalidEndpoint returns "system:route.invalidEndpoint" as *errors.Error +// ApigwRouteErrInvalidEndpoint returns "system:route.invalidEndpoint" as *errors.Error // // // This function is auto-generated. // -func RouteErrInvalidEndpoint(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrInvalidEndpoint(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -467,7 +467,7 @@ func RouteErrInvalidEndpoint(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "invalidEndpoint"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -478,13 +478,13 @@ func RouteErrInvalidEndpoint(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrExistsEndpoint returns "system:route.existsEndpoint" as *errors.Error +// ApigwRouteErrExistsEndpoint returns "system:route.existsEndpoint" as *errors.Error // // // This function is auto-generated. // -func RouteErrExistsEndpoint(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrExistsEndpoint(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -497,7 +497,7 @@ func RouteErrExistsEndpoint(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "existsEndpoint"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -508,13 +508,13 @@ func RouteErrExistsEndpoint(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrAlreadyExists returns "system:route.alreadyExists" as *errors.Error +// ApigwRouteErrAlreadyExists returns "system:route.alreadyExists" as *errors.Error // // // This function is auto-generated. // -func RouteErrAlreadyExists(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrAlreadyExists(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -527,7 +527,7 @@ func RouteErrAlreadyExists(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "alreadyExists"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -538,13 +538,13 @@ func RouteErrAlreadyExists(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToCreate returns "system:route.notAllowedToCreate" as *errors.Error +// ApigwRouteErrNotAllowedToCreate returns "system:route.notAllowedToCreate" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToCreate(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToCreate(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -558,8 +558,8 @@ func RouteErrNotAllowedToCreate(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to create a route; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to create a route; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -570,13 +570,13 @@ func RouteErrNotAllowedToCreate(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToRead returns "system:route.notAllowedToRead" as *errors.Error +// ApigwRouteErrNotAllowedToRead returns "system:route.notAllowedToRead" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToRead(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToRead(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -590,8 +590,8 @@ func RouteErrNotAllowedToRead(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to read {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to read {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -602,13 +602,13 @@ func RouteErrNotAllowedToRead(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToUpdate returns "system:route.notAllowedToUpdate" as *errors.Error +// ApigwRouteErrNotAllowedToUpdate returns "system:route.notAllowedToUpdate" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToUpdate(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToUpdate(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -622,8 +622,8 @@ func RouteErrNotAllowedToUpdate(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to update {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to update {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -634,13 +634,13 @@ func RouteErrNotAllowedToUpdate(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToDelete returns "system:route.notAllowedToDelete" as *errors.Error +// ApigwRouteErrNotAllowedToDelete returns "system:route.notAllowedToDelete" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToDelete(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToDelete(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -654,8 +654,8 @@ func RouteErrNotAllowedToDelete(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to delete {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to delete {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -666,13 +666,13 @@ func RouteErrNotAllowedToDelete(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToUndelete returns "system:route.notAllowedToUndelete" as *errors.Error +// ApigwRouteErrNotAllowedToUndelete returns "system:route.notAllowedToUndelete" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToUndelete(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToUndelete(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -686,8 +686,8 @@ func RouteErrNotAllowedToUndelete(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to undelete {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to undelete {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -698,13 +698,13 @@ func RouteErrNotAllowedToUndelete(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToExec returns "system:route.notAllowedToExec" as *errors.Error +// ApigwRouteErrNotAllowedToExec returns "system:route.notAllowedToExec" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToExec(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToExec(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -718,8 +718,8 @@ func RouteErrNotAllowedToExec(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to exec {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to exec {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -739,7 +739,7 @@ func RouteErrNotAllowedToExec(mm ...*routeActionProps) *errors.Error { // // This function is auto-generated. // -func (svc route) recordAction(ctx context.Context, props *routeActionProps, actionFn func(...*routeActionProps) *routeAction, err error) error { +func (svc apigwRoute) recordAction(ctx context.Context, props *apigwRouteActionProps, actionFn func(...*apigwRouteActionProps) *apigwRouteAction, err error) error { if svc.actionlog == nil || actionFn == nil { // action log disabled or no action fn passed, return error as-is return err @@ -760,10 +760,10 @@ func (svc route) recordAction(ctx context.Context, props *routeActionProps, acti a.Error = err.Error() a.Severity = actionlog.Severity(m.AsInt("severity")) - a.Description = props.Format(m.AsString(routeLogMetaKey{}), err) + a.Description = props.Format(m.AsString(apigwRouteLogMetaKey{}), err) - if p, has := m[routePropsMetaKey{}]; has { - a.Meta = p.(*routeActionProps).Serialize() + if p, has := m[apigwRoutePropsMetaKey{}]; has { + a.Meta = p.(*apigwRouteActionProps).Serialize() } svc.actionlog.Record(ctx, a) diff --git a/system/service/route_actions.yaml b/system/service/apigw_route_actions.yaml similarity index 93% rename from system/service/route_actions.yaml rename to system/service/apigw_route_actions.yaml index 1be4ece87..f6355afe2 100644 --- a/system/service/route_actions.yaml +++ b/system/service/apigw_route_actions.yaml @@ -1,7 +1,7 @@ # List of loggable service actions resource: system:route -service: route +service: apigwRoute # Default sensitivity for actions defaultActionSeverity: notice @@ -14,16 +14,16 @@ import: props: - name: route - type: "*types.Route" + type: "*types.ApigwRoute" fields: [ endpoint, ID ] - name: new - type: "*types.Route" + type: "*types.ApigwRoute" fields: [ endpoint, ID ] - name: update - type: "*types.Route" + type: "*types.ApigwRoute" fields: [ endpoint, ID ] - name: search - type: "*types.RouteFilter" + type: "*types.ApigwRouteFilter" fields: [] actions: diff --git a/system/service/function.go b/system/service/function.go deleted file mode 100644 index d3a2c408e..000000000 --- a/system/service/function.go +++ /dev/null @@ -1,229 +0,0 @@ -package service - -import ( - "context" - - "github.com/cortezaproject/corteza-server/pkg/actionlog" - "github.com/cortezaproject/corteza-server/pkg/apigw" - a "github.com/cortezaproject/corteza-server/pkg/auth" - "github.com/cortezaproject/corteza-server/store" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - function struct { - actionlog actionlog.Recorder - store store.Storer - ac functionAccessController - } - - functionAccessController interface{} -) - -func Function() *function { - return (&function{ - ac: DefaultAccessControl, - actionlog: DefaultActionlog, - store: DefaultStore, - }) -} - -func (svc *function) FindByID(ctx context.Context, ID uint64) (q *types.Function, err error) { - var ( - rProps = &functionActionProps{} - ) - - err = func() error { - if ID == 0 { - return FunctionErrInvalidID() - } - - if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { - return TemplateErrInvalidID().Wrap(err) - } - - rProps.setFunction(q) - - // if !svc.ac.CanReadMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToRead(qProps) - // } - - return nil - }() - - return q, svc.recordAction(ctx, rProps, FunctionActionLookup, err) -} - -func (svc *function) Create(ctx context.Context, new *types.Function) (q *types.Function, err error) { - var ( - qProps = &functionActionProps{function: new} - ) - - err = func() (err error) { - // if !svc.ac.CanCreateMessagebusQueue(ctx) { - // return QueueErrNotAllowedToCreate(qProps) - // } - - // Set new values after beforeCreate events are emitted - new.ID = nextID() - new.CreatedAt = *now() - new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() - - if _, err = DefaultRoute.FindByID(ctx, new.Route); err != nil { - return FunctionErrInvalidRoute(qProps) - } - - if err = store.CreateApigwFunction(ctx, svc.store, new); err != nil { - return err - } - - q = new - // send the signal to reload all functions - // apigw.Service().Reload(ctx) - // } - - return nil - }() - - return q, svc.recordAction(ctx, qProps, FunctionActionCreate, err) -} - -func (svc *function) Update(ctx context.Context, upd *types.Function) (q *types.Function, err error) { - var ( - qProps = &functionActionProps{function: upd} - qq *types.Function - e error - ) - - err = func() (err error) { - // if !svc.ac.CanUpdateMessagebusQueue(ctx, upd) { - // return QueueErrNotAllowedToUpdate(qProps) - // } - - if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e != nil { - return FunctionErrNotFound(qProps) - } - - if _, err = DefaultRoute.FindByID(ctx, upd.Route); err != nil { - return FunctionErrInvalidRoute(qProps) - } - - if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e == nil && qq == nil { - return FunctionErrNotFound(qProps) - } - - // Set new values after beforeCreate events are emitted - upd.UpdatedAt = now() - upd.CreatedAt = qq.CreatedAt - upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwFunction(ctx, svc.store, upd); err != nil { - return - } - - q = upd - - // send the signal to reload all function - // apigw.Service().Reload(ctx) - - return nil - }() - - return q, svc.recordAction(ctx, qProps, FunctionActionUpdate, err) -} - -func (svc *function) DeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &functionActionProps{} - q *types.Function - ) - - err = func() (err error) { - if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { - return FunctionErrNotFound(qProps) - } - - qProps.setFunction(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = now() - q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, FunctionActionDelete, err) -} - -func (svc *function) UndeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &functionActionProps{} - q *types.Function - ) - - err = func() (err error) { - if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { - return FunctionErrNotFound(qProps) - } - - qProps.setFunction(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = nil - q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, FunctionActionDelete, err) -} - -func (svc *function) Search(ctx context.Context, filter types.FunctionFilter) (r types.FunctionSet, f types.FunctionFilter, err error) { - var ( - aProps = &functionActionProps{search: &filter} - ) - - // For each fetched item, store backend will check if it is valid or not - // filter.Check = func(res *messagebus.QueueSettings) (bool, error) { - // if !svc.ac.CanReadMessagebusQueue(ctx, res) { - // return false, nil - // } - - // return true, nil - // } - - err = func() error { - if r, f, err = store.SearchApigwFunctions(ctx, svc.store, filter); err != nil { - return err - } - - return nil - }() - - return r, f, svc.recordAction(ctx, aProps, FunctionActionSearch, err) -} - -func (svc *function) Definitions(ctx context.Context, kind string) (interface{}, error) { - // get the definitions from registry - return apigw.Service().Funcs(kind), nil -} diff --git a/system/service/route.go b/system/service/route.go deleted file mode 100644 index 43d22ec32..000000000 --- a/system/service/route.go +++ /dev/null @@ -1,230 +0,0 @@ -package service - -import ( - "context" - - "github.com/cortezaproject/corteza-server/pkg/actionlog" - a "github.com/cortezaproject/corteza-server/pkg/auth" - "github.com/cortezaproject/corteza-server/store" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - route struct { - actionlog actionlog.Recorder - store store.Storer - ac routeAccessController - } - - routeAccessController interface { - } -) - -func Route() *route { - return (&route{ - ac: DefaultAccessControl, - actionlog: DefaultActionlog, - store: DefaultStore, - }) -} - -func (svc *route) FindByID(ctx context.Context, ID uint64) (q *types.Route, err error) { - var ( - rProps = &routeActionProps{} - ) - - err = func() error { - if ID == 0 { - return RouteErrInvalidID() - } - - if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { - return RouteErrInvalidID().Wrap(err) - } - - rProps.setRoute(q) - - // if !svc.ac.CanReadMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToRead(qProps) - // } - - return nil - }() - - return q, svc.recordAction(ctx, rProps, RouteActionLookup, err) -} - -func (svc *route) Create(ctx context.Context, new *types.Route) (q *types.Route, err error) { - var ( - qProps = &routeActionProps{new: new} - ) - - err = func() (err error) { - // if !svc.ac.CanCreateMessagebusQueue(ctx) { - // return QueueErrNotAllowedToCreate(qProps) - // } - - // Set new values after beforeCreate events are emitted - new.ID = nextID() - new.CreatedAt = *now() - new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() - - // todo - new.Group = 0 - - if err = store.CreateApigwRoute(ctx, svc.store, new); err != nil { - return err - } - - q = new - - // send the signal to reload all routes - // if new.Enabled { - // apigw.Service().Reload(ctx) - // } - - return nil - }() - - return q, svc.recordAction(ctx, qProps, RouteActionCreate, err) -} - -func (svc *route) Update(ctx context.Context, upd *types.Route) (q *types.Route, err error) { - var ( - qProps = &routeActionProps{update: upd} - qq *types.Route - e error - ) - - err = func() (err error) { - // if !svc.ac.CanUpdateMessagebusQueue(ctx, upd) { - // return QueueErrNotAllowedToUpdate(qProps) - // } - - if qq, e = store.LookupApigwRouteByID(ctx, svc.store, upd.ID); e != nil { - return RouteErrNotFound(qProps) - } - - // temp todo - update itself with the same endpoint - // if qq, e = store.LookupApigwRouteByEndpoint(ctx, svc.store, upd.Endpoint); e == nil && qq == nil { - // return RouteErrExistsEndpoint(qProps) - // } - - // Set new values after beforeCreate events are emitted - upd.UpdatedAt = now() - upd.CreatedAt = qq.CreatedAt - upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwRoute(ctx, svc.store, upd); err != nil { - return - } - - q = upd - - // send the signal to reload all route - // apigw.Service().Reload(ctx) - - return nil - }() - - return q, svc.recordAction(ctx, qProps, RouteActionUpdate, err) -} - -func (svc *route) DeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &routeActionProps{} - q *types.Route - ) - - err = func() (err error) { - if ID == 0 { - return RouteErrInvalidID() - } - - if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { - return - } - - qProps.setRoute(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = now() - q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, RouteActionDelete, err) -} - -func (svc *route) UndeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &routeActionProps{} - q *types.Route - ) - - err = func() (err error) { - if ID == 0 { - return RouteErrInvalidID() - } - - if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { - return - } - - qProps.setRoute(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = nil - q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, RouteActionDelete, err) -} - -func (svc *route) Search(ctx context.Context, filter types.RouteFilter) (r types.RouteSet, f types.RouteFilter, err error) { - var ( - aProps = &routeActionProps{search: &filter} - ) - - // For each fetched item, store backend will check if it is valid or not - // filter.Check = func(res *messagebus.QueueSettings) (bool, error) { - // if !svc.ac.CanReadMessagebusQueue(ctx, res) { - // return false, nil - // } - - // return true, nil - // } - - err = func() error { - if r, f, err = store.SearchApigwRoutes(ctx, svc.store, filter); err != nil { - return err - } - - return nil - }() - - return r, f, svc.recordAction(ctx, aProps, RouteActionSearch, err) -} diff --git a/system/service/service.go b/system/service/service.go index 8409f492c..345a2cc2c 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -75,8 +75,8 @@ var ( DefaultAttachment AttachmentService DefaultRenderer TemplateService DefaultQueue *queue - DefaultRoute *route - DefaultFunction *function + DefaultRoute *apigwRoute + DefaultFunction *apigwFunction DefaultStatistics *statistics diff --git a/system/types/function.go b/system/types/apigw_function.go similarity index 56% rename from system/types/function.go rename to system/types/apigw_function.go index 41e6feb68..631af195b 100644 --- a/system/types/function.go +++ b/system/types/apigw_function.go @@ -10,17 +10,15 @@ import ( ) type ( - ApigwFunctionKind string + ApigwFuncParams map[string]interface{} - FuncParams map[string]interface{} - - Function struct { - ID uint64 `json:"functionID,string"` - Route uint64 `json:"routeID,string"` - Weight uint64 `json:"weight"` - Ref string `json:"ref,omitempty"` - Kind string `json:"kind,omitempty"` - Params FuncParams `json:"params"` + ApigwFunction struct { + ID uint64 `json:"functionID,string"` + Route uint64 `json:"routeID,string"` + Weight uint64 `json:"weight,string"` + Ref string `json:"ref,omitempty"` + Kind string `json:"kind,omitempty"` + Params ApigwFuncParams `json:"params"` CreatedAt time.Time `json:"createdAt,omitempty"` CreatedBy uint64 `json:"createdBy,string" ` @@ -30,7 +28,7 @@ type ( DeletedBy uint64 `json:"deletedBy,string,omitempty" ` } - FunctionFilter struct { + ApigwFunctionFilter struct { RouteID uint64 `json:"routeID,string"` Endpoint string `json:"endpoint"` Group string `json:"group"` @@ -42,22 +40,14 @@ type ( // modify the resource and return false if store should not return it // // Store then loads additional resources to satisfy the paging parameters - Check func(*Route) (bool, error) `json:"-"` + Check func(*ApigwFunction) (bool, error) `json:"-"` filter.Sorting filter.Paging } ) -const ( - ApigwFunctionKindVerifier ApigwFunctionKind = "functionVerifier" - ApigwFunctionKindValidator ApigwFunctionKind = "functionValidator" - ApigwFunctionKindMatcher ApigwFunctionKind = "functionMatcher" - ApigwFunctionKindProcesser ApigwFunctionKind = "functionProcesser" - ApigwFunctionKindExpediter ApigwFunctionKind = "functionExpediter" -) - -func (vv *FuncParams) Scan(value interface{}) (err error) { +func (vv *ApigwFuncParams) Scan(value interface{}) (err error) { if err := json.Unmarshal(value.([]byte), vv); err != nil { return fmt.Errorf("cannot scan '%v' into FuncParams", value) } @@ -65,6 +55,6 @@ func (vv *FuncParams) Scan(value interface{}) (err error) { return } -func (vv FuncParams) Value() (driver.Value, error) { +func (vv ApigwFuncParams) Value() (driver.Value, error) { return json.Marshal(vv) } diff --git a/system/types/route.go b/system/types/apigw_route.go similarity index 87% rename from system/types/route.go rename to system/types/apigw_route.go index fc6e61251..813d1fcd8 100644 --- a/system/types/route.go +++ b/system/types/apigw_route.go @@ -7,15 +7,13 @@ import ( ) type ( - RouteMeta struct{} - - Route struct { + ApigwRoute struct { ID uint64 `json:"routeID,string"` Endpoint string `json:"endpoint"` Method string `json:"method"` Debug bool `json:"debug"` Enabled bool `json:"enabled"` - Group uint64 `json:"group"` + Group uint64 `json:"group,string"` CreatedAt time.Time `json:"createdAt,omitempty"` CreatedBy uint64 `json:"createdBy,string" ` @@ -25,7 +23,7 @@ type ( DeletedBy uint64 `json:"deletedBy,string,omitempty" ` } - RouteFilter struct { + ApigwRouteFilter struct { Route string `json:"route"` Group string `json:"group"` Enabled bool `json:"enabled"` @@ -36,7 +34,7 @@ type ( // modify the resource and return false if store should not return it // // Store then loads additional resources to satisfy the paging parameters - Check func(*Route) (bool, error) `json:"-"` + Check func(*ApigwRoute) (bool, error) `json:"-"` filter.Sorting filter.Paging diff --git a/system/types/parsers.go b/system/types/parsers.go index 2cf2d20ec..12e035262 100644 --- a/system/types/parsers.go +++ b/system/types/parsers.go @@ -4,8 +4,8 @@ import ( "encoding/json" ) -func ParseApigwfFunctionParams(ss []string) (p FuncParams, err error) { - p = FuncParams{} +func ParseApigwfFunctionParams(ss []string) (p ApigwFuncParams, err error) { + p = ApigwFuncParams{} return p, parseStringsInput(ss, p) } diff --git a/system/types/rbac.gen.go b/system/types/rbac.gen.go index fcfbac814..3f2c69979 100644 --- a/system/types/rbac.gen.go +++ b/system/types/rbac.gen.go @@ -7,10 +7,11 @@ package types // // Definitions file that controls how this file is generated: +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -28,15 +29,78 @@ type ( ) const ( - ApplicationResourceType = "corteza::system:application" - AuthClientResourceType = "corteza::system:auth-client" - RoleResourceType = "corteza::system:role" - RouteResourceType = "corteza::system:route" - TemplateResourceType = "corteza::system:template" - UserResourceType = "corteza::system:user" - ComponentResourceType = "corteza::system" + ApigwFunctionResourceType = "corteza::system:apigw-function" + ApigwRouteResourceType = "corteza::system:apigw-route" + ApplicationResourceType = "corteza::system:application" + AuthClientResourceType = "corteza::system:auth-client" + RoleResourceType = "corteza::system:role" + TemplateResourceType = "corteza::system:template" + UserResourceType = "corteza::system:user" + ComponentResourceType = "corteza::system" ) +// RbacResource returns string representation of RBAC resource for ApigwFunction by calling ApigwFunctionRbacResource fn +// +// RBAC resource is in the corteza::system:apigw-function/... format +// +// This function is auto-generated +func (r ApigwFunction) RbacResource() string { + return ApigwFunctionRbacResource(r.ID) +} + +// ApigwFunctionRbacResource returns string representation of RBAC resource for ApigwFunction +// +// RBAC resource is in the corteza::system:apigw-function/... format +// +// This function is auto-generated +func ApigwFunctionRbacResource(id uint64) string { + cpts := []interface{}{ApigwFunctionResourceType} + if id != 0 { + cpts = append(cpts, strconv.FormatUint(id, 10)) + } else { + cpts = append(cpts, "*") + } + + return fmt.Sprintf(ApigwFunctionRbacResourceTpl(), cpts...) + +} + +// @todo template +func ApigwFunctionRbacResourceTpl() string { + return "%s/%s" +} + +// RbacResource returns string representation of RBAC resource for ApigwRoute by calling ApigwRouteRbacResource fn +// +// RBAC resource is in the corteza::system:apigw-route/... format +// +// This function is auto-generated +func (r ApigwRoute) RbacResource() string { + return ApigwRouteRbacResource(r.ID) +} + +// ApigwRouteRbacResource returns string representation of RBAC resource for ApigwRoute +// +// RBAC resource is in the corteza::system:apigw-route/... format +// +// This function is auto-generated +func ApigwRouteRbacResource(id uint64) string { + cpts := []interface{}{ApigwRouteResourceType} + if id != 0 { + cpts = append(cpts, strconv.FormatUint(id, 10)) + } else { + cpts = append(cpts, "*") + } + + return fmt.Sprintf(ApigwRouteRbacResourceTpl(), cpts...) + +} + +// @todo template +func ApigwRouteRbacResourceTpl() string { + return "%s/%s" +} + // RbacResource returns string representation of RBAC resource for Application by calling ApplicationRbacResource fn // // RBAC resource is in the corteza::system:application/... format @@ -130,37 +194,6 @@ func RoleRbacResourceTpl() string { return "%s/%s" } -// RbacResource returns string representation of RBAC resource for Route by calling RouteRbacResource fn -// -// RBAC resource is in the corteza::system:route/... format -// -// This function is auto-generated -func (r Route) RbacResource() string { - return RouteRbacResource(r.ID) -} - -// RouteRbacResource returns string representation of RBAC resource for Route -// -// RBAC resource is in the corteza::system:route/... format -// -// This function is auto-generated -func RouteRbacResource(id uint64) string { - cpts := []interface{}{RouteResourceType} - if id != 0 { - cpts = append(cpts, strconv.FormatUint(id, 10)) - } else { - cpts = append(cpts, "*") - } - - return fmt.Sprintf(RouteRbacResourceTpl(), cpts...) - -} - -// @todo template -func RouteRbacResourceTpl() string { - return "%s/%s" -} - // RbacResource returns string representation of RBAC resource for Template by calling TemplateRbacResource fn // // RBAC resource is in the corteza::system:template/... format diff --git a/system/types/type_set.gen.go b/system/types/type_set.gen.go index 05674f883..15ede3f31 100644 --- a/system/types/type_set.gen.go +++ b/system/types/type_set.gen.go @@ -10,6 +10,16 @@ package types type ( + // ApigwFunctionSet slice of ApigwFunction + // + // This type is auto-generated. + ApigwFunctionSet []*ApigwFunction + + // ApigwRouteSet slice of ApigwRoute + // + // This type is auto-generated. + ApigwRouteSet []*ApigwRoute + // ApplicationSet slice of Application // // This type is auto-generated. @@ -45,11 +55,6 @@ type ( // This type is auto-generated. CredentialsSet []*Credentials - // FunctionSet slice of Function - // - // This type is auto-generated. - FunctionSet []*Function - // ReminderSet slice of Reminder // // This type is auto-generated. @@ -65,11 +70,6 @@ type ( // This type is auto-generated. RoleMemberSet []*RoleMember - // RouteSet slice of Route - // - // This type is auto-generated. - RouteSet []*Route - // SettingValueSet slice of SettingValue // // This type is auto-generated. @@ -86,6 +86,118 @@ type ( UserSet []*User ) +// Walk iterates through every slice item and calls w(ApigwFunction) err +// +// This function is auto-generated. +func (set ApigwFunctionSet) Walk(w func(*ApigwFunction) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(ApigwFunction) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set ApigwFunctionSet) Filter(f func(*ApigwFunction) (bool, error)) (out ApigwFunctionSet, err error) { + var ok bool + out = ApigwFunctionSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set ApigwFunctionSet) FindByID(ID uint64) *ApigwFunction { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set ApigwFunctionSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} + +// Walk iterates through every slice item and calls w(ApigwRoute) err +// +// This function is auto-generated. +func (set ApigwRouteSet) Walk(w func(*ApigwRoute) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(ApigwRoute) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set ApigwRouteSet) Filter(f func(*ApigwRoute) (bool, error)) (out ApigwRouteSet, err error) { + var ok bool + out = ApigwRouteSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set ApigwRouteSet) FindByID(ID uint64) *ApigwRoute { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set ApigwRouteSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} + // Walk iterates through every slice item and calls w(Application) err // // This function is auto-generated. @@ -426,62 +538,6 @@ func (set CredentialsSet) IDs() (IDs []uint64) { return } -// Walk iterates through every slice item and calls w(Function) err -// -// This function is auto-generated. -func (set FunctionSet) Walk(w func(*Function) error) (err error) { - for i := range set { - if err = w(set[i]); err != nil { - return - } - } - - return -} - -// Filter iterates through every slice item, calls f(Function) (bool, err) and return filtered slice -// -// This function is auto-generated. -func (set FunctionSet) Filter(f func(*Function) (bool, error)) (out FunctionSet, err error) { - var ok bool - out = FunctionSet{} - for i := range set { - if ok, err = f(set[i]); err != nil { - return - } else if ok { - out = append(out, set[i]) - } - } - - return -} - -// FindByID finds items from slice by its ID property -// -// This function is auto-generated. -func (set FunctionSet) FindByID(ID uint64) *Function { - for i := range set { - if set[i].ID == ID { - return set[i] - } - } - - return nil -} - -// IDs returns a slice of uint64s from all items in the set -// -// This function is auto-generated. -func (set FunctionSet) IDs() (IDs []uint64) { - IDs = make([]uint64, len(set)) - - for i := range set { - IDs[i] = set[i].ID - } - - return -} - // Walk iterates through every slice item and calls w(Reminder) err // // This function is auto-generated. @@ -624,62 +680,6 @@ func (set RoleMemberSet) Filter(f func(*RoleMember) (bool, error)) (out RoleMemb return } -// Walk iterates through every slice item and calls w(Route) err -// -// This function is auto-generated. -func (set RouteSet) Walk(w func(*Route) error) (err error) { - for i := range set { - if err = w(set[i]); err != nil { - return - } - } - - return -} - -// Filter iterates through every slice item, calls f(Route) (bool, err) and return filtered slice -// -// This function is auto-generated. -func (set RouteSet) Filter(f func(*Route) (bool, error)) (out RouteSet, err error) { - var ok bool - out = RouteSet{} - for i := range set { - if ok, err = f(set[i]); err != nil { - return - } else if ok { - out = append(out, set[i]) - } - } - - return -} - -// FindByID finds items from slice by its ID property -// -// This function is auto-generated. -func (set RouteSet) FindByID(ID uint64) *Route { - for i := range set { - if set[i].ID == ID { - return set[i] - } - } - - return nil -} - -// IDs returns a slice of uint64s from all items in the set -// -// This function is auto-generated. -func (set RouteSet) IDs() (IDs []uint64) { - IDs = make([]uint64, len(set)) - - for i := range set { - IDs[i] = set[i].ID - } - - return -} - // Walk iterates through every slice item and calls w(SettingValue) err // // This function is auto-generated. diff --git a/system/types/type_set.gen_test.go b/system/types/type_set.gen_test.go index a74d38013..16d8718c0 100644 --- a/system/types/type_set.gen_test.go +++ b/system/types/type_set.gen_test.go @@ -14,6 +14,186 @@ import ( "testing" ) +func TestApigwFunctionSetWalk(t *testing.T) { + var ( + value = make(ApigwFunctionSet, 3) + req = require.New(t) + ) + + // check walk with no errors + { + err := value.Walk(func(*ApigwFunction) error { + return nil + }) + req.NoError(err) + } + + // check walk with error + req.Error(value.Walk(func(*ApigwFunction) error { return fmt.Errorf("walk error") })) +} + +func TestApigwFunctionSetFilter(t *testing.T) { + var ( + value = make(ApigwFunctionSet, 3) + req = require.New(t) + ) + + // filter nothing + { + set, err := value.Filter(func(*ApigwFunction) (bool, error) { + return true, nil + }) + req.NoError(err) + req.Equal(len(set), len(value)) + } + + // filter one item + { + found := false + set, err := value.Filter(func(*ApigwFunction) (bool, error) { + if !found { + found = true + return found, nil + } + return false, nil + }) + req.NoError(err) + req.Len(set, 1) + } + + // filter error + { + _, err := value.Filter(func(*ApigwFunction) (bool, error) { + return false, fmt.Errorf("filter error") + }) + req.Error(err) + } +} + +func TestApigwFunctionSetIDs(t *testing.T) { + var ( + value = make(ApigwFunctionSet, 3) + req = require.New(t) + ) + + // construct objects + value[0] = new(ApigwFunction) + value[1] = new(ApigwFunction) + value[2] = new(ApigwFunction) + // set ids + value[0].ID = 1 + value[1].ID = 2 + value[2].ID = 3 + + // Find existing + { + val := value.FindByID(2) + req.Equal(uint64(2), val.ID) + } + + // Find non-existing + { + val := value.FindByID(4) + req.Nil(val) + } + + // List IDs from set + { + val := value.IDs() + req.Equal(len(val), len(value)) + } +} + +func TestApigwRouteSetWalk(t *testing.T) { + var ( + value = make(ApigwRouteSet, 3) + req = require.New(t) + ) + + // check walk with no errors + { + err := value.Walk(func(*ApigwRoute) error { + return nil + }) + req.NoError(err) + } + + // check walk with error + req.Error(value.Walk(func(*ApigwRoute) error { return fmt.Errorf("walk error") })) +} + +func TestApigwRouteSetFilter(t *testing.T) { + var ( + value = make(ApigwRouteSet, 3) + req = require.New(t) + ) + + // filter nothing + { + set, err := value.Filter(func(*ApigwRoute) (bool, error) { + return true, nil + }) + req.NoError(err) + req.Equal(len(set), len(value)) + } + + // filter one item + { + found := false + set, err := value.Filter(func(*ApigwRoute) (bool, error) { + if !found { + found = true + return found, nil + } + return false, nil + }) + req.NoError(err) + req.Len(set, 1) + } + + // filter error + { + _, err := value.Filter(func(*ApigwRoute) (bool, error) { + return false, fmt.Errorf("filter error") + }) + req.Error(err) + } +} + +func TestApigwRouteSetIDs(t *testing.T) { + var ( + value = make(ApigwRouteSet, 3) + req = require.New(t) + ) + + // construct objects + value[0] = new(ApigwRoute) + value[1] = new(ApigwRoute) + value[2] = new(ApigwRoute) + // set ids + value[0].ID = 1 + value[1].ID = 2 + value[2].ID = 3 + + // Find existing + { + val := value.FindByID(2) + req.Equal(uint64(2), val.ID) + } + + // Find non-existing + { + val := value.FindByID(4) + req.Nil(val) + } + + // List IDs from set + { + val := value.IDs() + req.Equal(len(val), len(value)) + } +} + func TestApplicationSetWalk(t *testing.T) { var ( value = make(ApplicationSet, 3) @@ -576,96 +756,6 @@ func TestCredentialsSetIDs(t *testing.T) { } } -func TestFunctionSetWalk(t *testing.T) { - var ( - value = make(FunctionSet, 3) - req = require.New(t) - ) - - // check walk with no errors - { - err := value.Walk(func(*Function) error { - return nil - }) - req.NoError(err) - } - - // check walk with error - req.Error(value.Walk(func(*Function) error { return fmt.Errorf("walk error") })) -} - -func TestFunctionSetFilter(t *testing.T) { - var ( - value = make(FunctionSet, 3) - req = require.New(t) - ) - - // filter nothing - { - set, err := value.Filter(func(*Function) (bool, error) { - return true, nil - }) - req.NoError(err) - req.Equal(len(set), len(value)) - } - - // filter one item - { - found := false - set, err := value.Filter(func(*Function) (bool, error) { - if !found { - found = true - return found, nil - } - return false, nil - }) - req.NoError(err) - req.Len(set, 1) - } - - // filter error - { - _, err := value.Filter(func(*Function) (bool, error) { - return false, fmt.Errorf("filter error") - }) - req.Error(err) - } -} - -func TestFunctionSetIDs(t *testing.T) { - var ( - value = make(FunctionSet, 3) - req = require.New(t) - ) - - // construct objects - value[0] = new(Function) - value[1] = new(Function) - value[2] = new(Function) - // set ids - value[0].ID = 1 - value[1].ID = 2 - value[2].ID = 3 - - // Find existing - { - val := value.FindByID(2) - req.Equal(uint64(2), val.ID) - } - - // Find non-existing - { - val := value.FindByID(4) - req.Nil(val) - } - - // List IDs from set - { - val := value.IDs() - req.Equal(len(val), len(value)) - } -} - func TestReminderSetWalk(t *testing.T) { var ( value = make(ReminderSet, 3) @@ -902,96 +992,6 @@ func TestRoleMemberSetFilter(t *testing.T) { } } -func TestRouteSetWalk(t *testing.T) { - var ( - value = make(RouteSet, 3) - req = require.New(t) - ) - - // check walk with no errors - { - err := value.Walk(func(*Route) error { - return nil - }) - req.NoError(err) - } - - // check walk with error - req.Error(value.Walk(func(*Route) error { return fmt.Errorf("walk error") })) -} - -func TestRouteSetFilter(t *testing.T) { - var ( - value = make(RouteSet, 3) - req = require.New(t) - ) - - // filter nothing - { - set, err := value.Filter(func(*Route) (bool, error) { - return true, nil - }) - req.NoError(err) - req.Equal(len(set), len(value)) - } - - // filter one item - { - found := false - set, err := value.Filter(func(*Route) (bool, error) { - if !found { - found = true - return found, nil - } - return false, nil - }) - req.NoError(err) - req.Len(set, 1) - } - - // filter error - { - _, err := value.Filter(func(*Route) (bool, error) { - return false, fmt.Errorf("filter error") - }) - req.Error(err) - } -} - -func TestRouteSetIDs(t *testing.T) { - var ( - value = make(RouteSet, 3) - req = require.New(t) - ) - - // construct objects - value[0] = new(Route) - value[1] = new(Route) - value[2] = new(Route) - // set ids - value[0].ID = 1 - value[1].ID = 2 - value[2].ID = 3 - - // Find existing - { - val := value.FindByID(2) - req.Equal(uint64(2), val.ID) - } - - // Find non-existing - { - val := value.FindByID(4) - req.Nil(val) - } - - // List IDs from set - { - val := value.IDs() - req.Equal(len(val), len(value)) - } -} - func TestSettingValueSetWalk(t *testing.T) { var ( value = make(SettingValueSet, 3) diff --git a/system/types/types.yaml b/system/types/types.yaml index b712fb094..f8a0648be 100644 --- a/system/types/types.yaml +++ b/system/types/types.yaml @@ -21,5 +21,5 @@ types: noIdField: true Template: labelResourceType: template - Route: {} - Function: {} \ No newline at end of file + ApigwRoute: {} + ApigwFunction: {} \ No newline at end of file