From d2bff9e3fd3c70b46f2b56cbfaf7f9050eadbc6f Mon Sep 17 00:00:00 2001 From: Vivek Patel Date: Fri, 17 Sep 2021 17:42:25 +0530 Subject: [PATCH] Apigw tweaks - Add system/apigw tests - Removes permissions from APIGw filters from each level --- def/system.apigw-filter.yaml | 8 - def/system.yaml | 5 - pkg/apigw/filter/postfilter.go | 16 +- pkg/apigw/filter/prefilter.go | 36 +- pkg/apigw/filter/processer.go | 6 +- pkg/apigw/filter/proxy/proxy.go | 8 +- pkg/apigw/filter/proxy/proxy_auth.go | 20 +- pkg/apigw/filter/proxy/proxy_test.go | 5 +- pkg/apigw/pipeline/pipeline.go | 2 +- pkg/apigw/service.go | 4 +- .../resource/rbac_references_system.gen.go | 14 - pkg/envoy/resource/rbac_rules_parse.gen.go | 11 - provision/000_base/system_access_control.yaml | 2 - store/apigw_filter.yaml | 1 - store/apigw_route.yaml | 1 - store/rdbms/apigw_filter.gen.go | 14 +- store/rdbms/apigw_route.gen.go | 14 +- system/rest.yaml | 3 +- system/rest/request/apigwFilter.go | 17 - system/service/access_control.gen.go | 115 ---- system/service/apigw_filter.go | 113 ++-- system/service/apigw_filter_actions.gen.go | 180 ------- system/service/apigw_filter_actions.yaml | 20 - system/service/apigw_route.go | 26 +- system/service/apigw_route_actions.gen.go | 36 ++ system/service/apigw_route_actions.yaml | 4 + system/types/apigw_filter.go | 6 +- system/types/rbac.gen.go | 33 -- tests/system/apigw_test.go | 504 ++++++++++++++++++ 29 files changed, 699 insertions(+), 525 deletions(-) delete mode 100644 def/system.apigw-filter.yaml create mode 100644 tests/system/apigw_test.go diff --git a/def/system.apigw-filter.yaml b/def/system.apigw-filter.yaml deleted file mode 100644 index 554b0eefe..000000000 --- a/def/system.apigw-filter.yaml +++ /dev/null @@ -1,8 +0,0 @@ -rbac: - operations: - read: - description: Read API Gateway filter - update: - description: Update API Gateway filter - delete: - description: Delete API Gateway filter diff --git a/def/system.yaml b/def/system.yaml index 3fa71f29a..b757e5c36 100644 --- a/def/system.yaml +++ b/def/system.yaml @@ -60,10 +60,5 @@ rbac: apigw-routes.search: description: List search or filter API gateway routes - apigw-filter.create: - description: Add API gateway filter to route - apigw-filters.search: - description: List, search or filter API gateway filters - resource-translations.manage: description: List, search, create, or update resource translations diff --git a/pkg/apigw/filter/postfilter.go b/pkg/apigw/filter/postfilter.go index 7f983e01a..736aa28ec 100644 --- a/pkg/apigw/filter/postfilter.go +++ b/pkg/apigw/filter/postfilter.go @@ -113,23 +113,23 @@ func NewDefaultJsonResponse() (e *defaultJsonResponse) { return } -func (h defaultJsonResponse) New() types.Handler { +func (j defaultJsonResponse) New() types.Handler { return NewDefaultJsonResponse() } -func (h defaultJsonResponse) String() string { - return fmt.Sprintf("apigw filter %s (%s)", h.Name, h.Label) +func (j defaultJsonResponse) String() string { + return fmt.Sprintf("apigw filter %s (%s)", j.Name, j.Label) } -func (h defaultJsonResponse) Meta() types.FilterMeta { - return h.FilterMeta +func (j defaultJsonResponse) Meta() types.FilterMeta { + return j.FilterMeta } -func (f *defaultJsonResponse) Merge(params []byte) (h types.Handler, err error) { - return f, err +func (j *defaultJsonResponse) Merge(params []byte) (h types.Handler, err error) { + return j, err } -func (h defaultJsonResponse) Handler() types.HandlerFunc { +func (j defaultJsonResponse) Handler() types.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) error { rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusAccepted) diff --git a/pkg/apigw/filter/prefilter.go b/pkg/apigw/filter/prefilter.go index 964ee2bdc..2a5b9e287 100644 --- a/pkg/apigw/filter/prefilter.go +++ b/pkg/apigw/filter/prefilter.go @@ -68,21 +68,21 @@ func (h header) Meta() types.FilterMeta { return h.FilterMeta } -func (v *header) Merge(params []byte) (types.Handler, error) { - err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) +func (h *header) Merge(params []byte) (types.Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h.params) if err != nil { return nil, err } parser := expr.NewParser() - v.eval, err = parser.Parse(v.params.Expr) + h.eval, err = parser.Parse(h.params.Expr) if err != nil { return nil, fmt.Errorf("could not validate origin parameters: %s", err) } - return v, err + return h, err } func (h header) Handler() types.HandlerFunc { @@ -137,36 +137,36 @@ func NewQueryParam() (v *queryParam) { return } -func (h queryParam) New() types.Handler { +func (qp queryParam) New() types.Handler { return NewQueryParam() } -func (h queryParam) String() string { - return fmt.Sprintf("apigw filter %s (%s)", h.Name, h.Label) +func (qp queryParam) String() string { + return fmt.Sprintf("apigw filter %s (%s)", qp.Name, qp.Label) } -func (h queryParam) Meta() types.FilterMeta { - return h.FilterMeta +func (qp queryParam) Meta() types.FilterMeta { + return qp.FilterMeta } -func (v *queryParam) Merge(params []byte) (types.Handler, error) { - err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) +func (qp *queryParam) Merge(params []byte) (types.Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&qp.params) if err != nil { return nil, err } parser := expr.NewParser() - v.eval, err = parser.Parse(v.params.Expr) + qp.eval, err = parser.Parse(qp.params.Expr) if err != nil { return nil, fmt.Errorf("could not validate query parameters: %s", err) } - return v, err + return qp, err } -func (h *queryParam) Handler() types.HandlerFunc { +func (qp *queryParam) Handler() types.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) error { var ( ctx = r.Context() @@ -183,17 +183,17 @@ func (h *queryParam) Handler() types.HandlerFunc { out, err := expr.NewVars(vv) if err != nil { - return pe.Internal("could not validate query parameters: (%v) (%s)", err, h.params.Expr) + return pe.Internal("could not validate query parameters: (%v) (%s)", err, qp.params.Expr) } - b, err := h.eval.Test(ctx, out) + b, err := qp.eval.Test(ctx, out) if err != nil { - return pe.InvalidData("could not validate query parameters: (%v) (%s)", err, h.params.Expr) + return pe.InvalidData("could not validate query parameters: (%v) (%s)", err, qp.params.Expr) } if !b { - return pe.InvalidData("could not validate query parameters: (%v) (%s)", errors.New("validation failed"), h.params.Expr) + return pe.InvalidData("could not validate query parameters: (%v) (%s)", errors.New("validation failed"), qp.params.Expr) } return nil diff --git a/pkg/apigw/filter/processer.go b/pkg/apigw/filter/processer.go index bb7c3bdd7..b7dbab77d 100644 --- a/pkg/apigw/filter/processer.go +++ b/pkg/apigw/filter/processer.go @@ -77,10 +77,10 @@ func (h workflow) Meta() types.FilterMeta { return h.FilterMeta } -func (f *workflow) Merge(params []byte) (types.Handler, error) { - err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) +func (h *workflow) Merge(params []byte) (types.Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h.params) - return f, err + return h, err } func (h workflow) Handler() types.HandlerFunc { diff --git a/pkg/apigw/filter/proxy/proxy.go b/pkg/apigw/filter/proxy/proxy.go index 861490c04..81d8c4dfe 100644 --- a/pkg/apigw/filter/proxy/proxy.go +++ b/pkg/apigw/filter/proxy/proxy.go @@ -79,21 +79,21 @@ func (h proxy) Meta() types.FilterMeta { return h.FilterMeta } -func (f *proxy) Merge(params []byte) (types.Handler, error) { - err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) +func (h *proxy) Merge(params []byte) (types.Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h.params) if err != nil { return nil, err } // get the auth mechanism - f.a, err = NewProxyAuthServicer(f.c, f.params.Auth, f.s) + h.a, err = NewProxyAuthServicer(h.c, h.params.Auth, h.s) if err != nil { return nil, fmt.Errorf("could not load auth servicer for proxying: %s", err) } - return f, err + return h, err } func (h proxy) Handler() types.HandlerFunc { diff --git a/pkg/apigw/filter/proxy/proxy_auth.go b/pkg/apigw/filter/proxy/proxy_auth.go index 916240545..9b23b5864 100644 --- a/pkg/apigw/filter/proxy/proxy_auth.go +++ b/pkg/apigw/filter/proxy/proxy_auth.go @@ -80,21 +80,21 @@ type ( func NewProxyAuthServicer(c *http.Client, p ProxyAuthParams, s types.SecureStorager) (ProxyAuthServicer, error) { switch p.Type { case proxyAuthTypeHeader: - return NewProxyAuthHeader(p) + return newProxyAuthHeader(p) case proxyAuthTypeQuery: - return NewProxyAuthQuery(p) + return newProxyAuthQuery(p) case proxyAuthTypeBasic: - return NewProxyAuthBasic(p) + return newProxyAuthBasic(p) case proxyAuthTypeOauth2: - return NewProxyAuthOauth2(p, c, s) + return newProxyAuthOauth2(p, c, s) case proxyAuthTypeJWT: - return NewProxyAuthJWT(p) + return newProxyAuthJWT(p) default: return proxyAuthServicerNoop{}, nil } } -func NewProxyAuthHeader(p ProxyAuthParams) (s proxyAuthServicerHeader, err error) { +func newProxyAuthHeader(p ProxyAuthParams) (s proxyAuthServicerHeader, err error) { s = proxyAuthServicerHeader{ params: p.Params, } @@ -102,7 +102,7 @@ func NewProxyAuthHeader(p ProxyAuthParams) (s proxyAuthServicerHeader, err error return } -func NewProxyAuthQuery(p ProxyAuthParams) (s proxyAuthServicerQuery, err error) { +func newProxyAuthQuery(p ProxyAuthParams) (s proxyAuthServicerQuery, err error) { s = proxyAuthServicerQuery{ params: p.Params, } @@ -110,7 +110,7 @@ func NewProxyAuthQuery(p ProxyAuthParams) (s proxyAuthServicerQuery, err error) return } -func NewProxyAuthBasic(p ProxyAuthParams) (s proxyAuthServicerBasic, err error) { +func newProxyAuthBasic(p ProxyAuthParams) (s proxyAuthServicerBasic, err error) { var ( ok bool user, pass string @@ -136,7 +136,7 @@ func NewProxyAuthBasic(p ProxyAuthParams) (s proxyAuthServicerBasic, err error) return } -func NewProxyAuthOauth2(p ProxyAuthParams, c *http.Client, s types.SecureStorager) (ss proxyAuthServicerOauth2, err error) { +func newProxyAuthOauth2(p ProxyAuthParams, c *http.Client, s types.SecureStorager) (ss proxyAuthServicerOauth2, err error) { var ( u *url.URL ok bool @@ -182,7 +182,7 @@ func NewProxyAuthOauth2(p ProxyAuthParams, c *http.Client, s types.SecureStorage return } -func NewProxyAuthJWT(p ProxyAuthParams) (ss proxyAuthServicerJWT, err error) { +func newProxyAuthJWT(p ProxyAuthParams) (ss proxyAuthServicerJWT, err error) { var ( ok bool jwt string diff --git a/pkg/apigw/filter/proxy/proxy_test.go b/pkg/apigw/filter/proxy/proxy_test.go index eb86baa4e..c8afd838b 100644 --- a/pkg/apigw/filter/proxy/proxy_test.go +++ b/pkg/apigw/filter/proxy/proxy_test.go @@ -175,7 +175,8 @@ func Test_proxy(t *testing.T) { } proxy := New(zap.NewNop(), c, struct{}{}) - proxy.Merge([]byte(tc.params)) + _, err := proxy.Merge([]byte(tc.params)) + req.NoError(err) scope := &types.Scp{ "opts": options.Apigw(), @@ -185,7 +186,7 @@ func Test_proxy(t *testing.T) { rq = rq.WithContext(ctx) hn := proxy.Handler() - err := hn(rc, rq) + err = hn(rc, rq) if tc.err != "" { req.EqualError(err, tc.err) diff --git a/pkg/apigw/pipeline/pipeline.go b/pkg/apigw/pipeline/pipeline.go index a5f87036d..387f412a5 100644 --- a/pkg/apigw/pipeline/pipeline.go +++ b/pkg/apigw/pipeline/pipeline.go @@ -50,7 +50,7 @@ func (pp *Pl) Error() types.ErrorHandlerFunc { return pp.err } -// add error handler +// ErrorHandler adds error handler func (pp *Pl) ErrorHandler(ff types.ErrorHandlerFunc) { pp.err = ff } diff --git a/pkg/apigw/service.go b/pkg/apigw/service.go index a0ce4bb0f..de0c6d399 100644 --- a/pkg/apigw/service.go +++ b/pkg/apigw/service.go @@ -166,7 +166,7 @@ func (s *apigw) Router(r chi.Router) { }() } -// init all the routes +// Init all the routes func (s *apigw) Init(ctx context.Context, route ...*route) { var ( defaultPostFilter types.Handler @@ -240,7 +240,7 @@ func (s *apigw) Init(ctx context.Context, route ...*route) { r.handler = pipe.Handler() r.errHandler = pipe.Error() - log.Debug("successfuly registered route") + log.Debug("successfully registered route") } } diff --git a/pkg/envoy/resource/rbac_references_system.gen.go b/pkg/envoy/resource/rbac_references_system.gen.go index 3fc05fc29..a13acd47f 100644 --- a/pkg/envoy/resource/rbac_references_system.gen.go +++ b/pkg/envoy/resource/rbac_references_system.gen.go @@ -7,7 +7,6 @@ package resource // // Definitions file that controls how this file is generated: -// - system.apigw-filter.yaml // - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml @@ -21,19 +20,6 @@ import ( "github.com/cortezaproject/corteza-server/system/types" ) -// SystemApigwFilterRbacReferences generates RBAC references -// -// Resources with "envoy: false" are skipped -// -// This function is auto-generated -func SystemApigwFilterRbacReferences(apigwFilter string) (res *Ref, pp []*Ref, err error) { - if apigwFilter != "*" { - res = &Ref{ResourceType: types.ApigwFilterResourceType, Identifiers: MakeIdentifiers(apigwFilter)} - } - - return -} - // SystemApigwRouteRbacReferences 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 7c033ac7c..845414c2d 100644 --- a/pkg/envoy/resource/rbac_rules_parse.gen.go +++ b/pkg/envoy/resource/rbac_rules_parse.gen.go @@ -16,7 +16,6 @@ package resource // - compose.page.yaml // - compose.record.yaml // - compose.yaml -// - system.apigw-filter.yaml // - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml @@ -171,16 +170,6 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) { // Component resource, no path return composeTypes.ComponentResourceType, nil, nil, nil - case systemTypes.ApigwFilterResourceType: - if len(path) != 1 { - return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) - } - ref, pp, err := SystemApigwFilterRbacReferences( - // apigwFilter - path[0], - ) - return systemTypes.ApigwFilterResourceType, 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)) diff --git a/provision/000_base/system_access_control.yaml b/provision/000_base/system_access_control.yaml index f17c10663..51a493497 100644 --- a/provision/000_base/system_access_control.yaml +++ b/provision/000_base/system_access_control.yaml @@ -37,8 +37,6 @@ allow: - application.flag.global - apigw-route.create - apigw-routes.search - - apigw-filter.create - - apigw-filters.search - report.create - reports.search diff --git a/store/apigw_filter.yaml b/store/apigw_filter.yaml index 66bf7be0a..da4f88074 100644 --- a/store/apigw_filter.yaml +++ b/store/apigw_filter.yaml @@ -37,7 +37,6 @@ rdbms: search: enablePaging: true - enableFilterCheckFunction: false upsert: enable: false diff --git a/store/apigw_route.yaml b/store/apigw_route.yaml index da8074442..bccb05f67 100644 --- a/store/apigw_route.yaml +++ b/store/apigw_route.yaml @@ -37,7 +37,6 @@ rdbms: search: enablePaging: true - enableFilterCheckFunction: false upsert: enable: false diff --git a/store/rdbms/apigw_filter.gen.go b/store/rdbms/apigw_filter.gen.go index c92c02547..6e308d203 100644 --- a/store/rdbms/apigw_filter.gen.go +++ b/store/rdbms/apigw_filter.gen.go @@ -81,7 +81,7 @@ func (s Store) SearchApigwFilters(ctx context.Context, f types.ApigwFilterFilter ctx, q, f.Sort, f.PageCursor, f.Limit, - nil, + f.Check, func(cur *filter.PagingCursor) squirrel.Sqlizer { return builders.CursorCondition(cur, nil) }, @@ -265,6 +265,16 @@ func (s Store) QueryApigwFilters( for _, res = range tmp { + // check fn set, call it and see if it passed the test + // if not, skip the item + if check != nil { + if chk, err := check(res); err != nil { + return nil, err + } else if !chk { + continue + } + } + set = append(set, res) } @@ -465,7 +475,7 @@ func (Store) apigwFilterColumns(aa ...string) []string { } } -// {true true false true true false} +// {true true false true true true} // sortableApigwFilterColumns returns all ApigwFilter columns flagged as sortable // diff --git a/store/rdbms/apigw_route.gen.go b/store/rdbms/apigw_route.gen.go index 3ec19437a..8dfcda462 100644 --- a/store/rdbms/apigw_route.gen.go +++ b/store/rdbms/apigw_route.gen.go @@ -81,7 +81,7 @@ func (s Store) SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) ctx, q, f.Sort, f.PageCursor, f.Limit, - nil, + f.Check, func(cur *filter.PagingCursor) squirrel.Sqlizer { return builders.CursorCondition(cur, nil) }, @@ -265,6 +265,16 @@ func (s Store) QueryApigwRoutes( for _, res = range tmp { + // check fn set, call it and see if it passed the test + // if not, skip the item + if check != nil { + if chk, err := check(res); err != nil { + return nil, err + } else if !chk { + continue + } + } + set = append(set, res) } @@ -465,7 +475,7 @@ func (Store) apigwRouteColumns(aa ...string) []string { } } -// {true true false true true false} +// {true true false true true true} // sortableApigwRouteColumns returns all ApigwRoute columns flagged as sortable // diff --git a/system/rest.yaml b/system/rest.yaml index eeb0ee706..926e53edf 100644 --- a/system/rest.yaml +++ b/system/rest.yaml @@ -1699,8 +1699,7 @@ endpoints: path: "/" parameters: get: - - { name: routeID, type: "uint64", title: "Filter by route ID" } - - { name: query, type: "string", title: "Filter filters" } + - { name: routeID, type: "uint64", title: "Filter by route ID", required: true } - { name: deleted, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) deleted filters" } - { name: disabled, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) disabled filters" } - { name: limit, type: "uint", title: "Limit" } diff --git a/system/rest/request/apigwFilter.go b/system/rest/request/apigwFilter.go index 59029914a..b57513330 100644 --- a/system/rest/request/apigwFilter.go +++ b/system/rest/request/apigwFilter.go @@ -40,11 +40,6 @@ type ( // Filter by route ID RouteID uint64 `json:",string"` - // Query GET parameter - // - // Filter filters - Query string - // Deleted GET parameter // // Exclude (0, default), include (1) or return only (2) deleted filters @@ -171,7 +166,6 @@ func NewApigwFilterList() *ApigwFilterList { func (r ApigwFilterList) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, - "query": r.Query, "deleted": r.Deleted, "disabled": r.Disabled, "limit": r.Limit, @@ -185,11 +179,6 @@ func (r ApigwFilterList) GetRouteID() uint64 { return r.RouteID } -// Auditable returns all auditable/loggable parameters -func (r ApigwFilterList) GetQuery() string { - return r.Query -} - // Auditable returns all auditable/loggable parameters func (r ApigwFilterList) GetDeleted() uint64 { return r.Deleted @@ -228,12 +217,6 @@ func (r *ApigwFilterList) Fill(req *http.Request) (err error) { return err } } - if val, ok := tmp["query"]; ok && len(val) > 0 { - r.Query, err = val[0], nil - if err != nil { - return err - } - } if val, ok := tmp["deleted"]; ok && len(val) > 0 { r.Deleted, err = payload.ParseUint64(val[0]), nil if err != nil { diff --git a/system/service/access_control.gen.go b/system/service/access_control.gen.go index 672defaaa..91ecd8a61 100644 --- a/system/service/access_control.gen.go +++ b/system/service/access_control.gen.go @@ -7,7 +7,6 @@ package service // // Definitions file that controls how this file is generated: -// - system.apigw-filter.yaml // - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml @@ -64,21 +63,6 @@ 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.ApigwFilterResourceType, - "any": types.ApigwFilterRbacResource(0), - "op": "read", - }, - { - "type": types.ApigwFilterResourceType, - "any": types.ApigwFilterRbacResource(0), - "op": "update", - }, - { - "type": types.ApigwFilterResourceType, - "any": types.ApigwFilterRbacResource(0), - "op": "delete", - }, { "type": types.ApigwRouteResourceType, "any": types.ApigwRouteRbacResource(0), @@ -344,16 +328,6 @@ func (svc accessControl) List() (out []map[string]string) { "any": types.ComponentRbacResource(), "op": "apigw-routes.search", }, - { - "type": types.ComponentResourceType, - "any": types.ComponentRbacResource(), - "op": "apigw-filter.create", - }, - { - "type": types.ComponentResourceType, - "any": types.ComponentRbacResource(), - "op": "apigw-filters.search", - }, { "type": types.ComponentResourceType, "any": types.ComponentRbacResource(), @@ -421,27 +395,6 @@ func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) ( return svc.rbac.FindRulesByRoleID(roleID), nil } -// CanReadApigwFilter checks if current user can read api gateway filter -// -// This function is auto-generated -func (svc accessControl) CanReadApigwFilter(ctx context.Context, r *types.ApigwFilter) bool { - return svc.can(ctx, "read", r) -} - -// CanUpdateApigwFilter checks if current user can update api gateway filter -// -// This function is auto-generated -func (svc accessControl) CanUpdateApigwFilter(ctx context.Context, r *types.ApigwFilter) bool { - return svc.can(ctx, "update", r) -} - -// CanDeleteApigwFilter checks if current user can delete api gateway filter -// -// This function is auto-generated -func (svc accessControl) CanDeleteApigwFilter(ctx context.Context, r *types.ApigwFilter) bool { - return svc.can(ctx, "delete", r) -} - // CanReadApigwRoute checks if current user can read api gateway route // // This function is auto-generated @@ -813,20 +766,6 @@ func (svc accessControl) CanSearchApigwRoutes(ctx context.Context) bool { return svc.can(ctx, "apigw-routes.search", &types.Component{}) } -// CanCreateApigwFilter checks if current user can add api gateway filter to route -// -// This function is auto-generated -func (svc accessControl) CanCreateApigwFilter(ctx context.Context) bool { - return svc.can(ctx, "apigw-filter.create", &types.Component{}) -} - -// CanSearchApigwFilters checks if current user can list, search or filter api gateway filters -// -// This function is auto-generated -func (svc accessControl) CanSearchApigwFilters(ctx context.Context) bool { - return svc.can(ctx, "apigw-filters.search", &types.Component{}) -} - // CanManageResourceTranslations checks if current user can list, search, create, or update resource translations // // This function is auto-generated @@ -839,8 +778,6 @@ func (svc accessControl) CanManageResourceTranslations(ctx context.Context) bool // This function is auto-generated func rbacResourceValidator(r string, oo ...string) error { switch rbac.ResourceType(r) { - case types.ApigwFilterResourceType: - return rbacApigwFilterResourceValidator(r, oo...) case types.ApigwRouteResourceType: return rbacApigwRouteResourceValidator(r, oo...) case types.ApplicationResourceType: @@ -867,12 +804,6 @@ 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.ApigwFilterResourceType: - return map[string]bool{ - "read": true, - "update": true, - "delete": true, - } case types.ApigwRouteResourceType: return map[string]bool{ "read": true, @@ -949,8 +880,6 @@ func rbacResourceOperations(r string) map[string]bool { "queues.search": true, "apigw-route.create": true, "apigw-routes.search": true, - "apigw-filter.create": true, - "apigw-filters.search": true, "resource-translations.manage": true, } } @@ -958,50 +887,6 @@ func rbacResourceOperations(r string) map[string]bool { return nil } -// rbacApigwFilterResourceValidator 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 rbacApigwFilterResourceValidator(r string, oo ...string) error { - defOps := rbacResourceOperations(r) - for _, o := range oo { - if !defOps[o] { - return fmt.Errorf("invalid operation '%s' for system ApigwFilter resource", o) - } - } - - if !strings.HasPrefix(r, types.ApigwFilterResourceType) { - // expecting resource to always include path - return fmt.Errorf("invalid resource type") - } - - const sep = "/" - var ( - pp = strings.Split(strings.Trim(r[len(types.ApigwFilterResourceType):], sep), sep) - prc = []string{ - "ID", - } - ) - - if len(pp) != len(prc) { - return fmt.Errorf("invalid resource path structure") - } - - for i := 0; i < len(pp); i++ { - if pp[i] != "*" { - if i > 0 && pp[i-1] == "*" { - return fmt.Errorf("invalid resource path wildcard level (%d) for ApigwFilter", i) - } - - if _, err := cast.ToUint64E(pp[i]); err != nil { - return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i]) - } - } - } - return nil -} - // rbacApigwRouteResourceValidator 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_filter.go b/system/service/apigw_filter.go index 963eb3cfc..5f117318b 100644 --- a/system/service/apigw_filter.go +++ b/system/service/apigw_filter.go @@ -15,49 +15,46 @@ type ( apigwFilter struct { actionlog actionlog.Recorder store store.Storer - ac functionAccessController + ac routeAccessController route *apigwRoute } - - functionAccessController interface { - CanSearchApigwFilters(context.Context) bool - - CanCreateApigwFilter(context.Context) bool - CanReadApigwFilter(context.Context, *types.ApigwFilter) bool - CanUpdateApigwFilter(context.Context, *types.ApigwFilter) bool - CanDeleteApigwFilter(context.Context, *types.ApigwFilter) bool - } ) func Filter() *apigwFilter { - return (&apigwFilter{ + return &apigwFilter{ route: DefaultApigwRoute, ac: DefaultAccessControl, actionlog: DefaultActionlog, store: DefaultStore, - }) + } } -func (svc *apigwFilter) FindByID(ctx context.Context, ID uint64) (q *types.ApigwFilter, err error) { +func (svc *apigwFilter) FindByID(ctx context.Context, filterID uint64) (q *types.ApigwFilter, err error) { var ( rProps = &apigwFilterActionProps{} + r *types.ApigwRoute ) err = func() error { - if ID == 0 { + if filterID == 0 { return ApigwFilterErrInvalidID() } - if !svc.ac.CanSearchApigwFilters(ctx) { - return ApigwFilterErrNotAllowedToRead(rProps) - } - - if q, err = store.LookupApigwFilterByID(ctx, svc.store, ID); err != nil { + if q, err = store.LookupApigwFilterByID(ctx, svc.store, filterID); err != nil { return TemplateErrInvalidID().Wrap(err) } rProps.setFilter(q) + // Get route + if r, err = svc.route.FindByID(ctx, q.Route); err != nil { + return err + } + + if !svc.ac.CanReadApigwRoute(ctx, r) { + return ApigwRouteErrNotAllowedToRead() + } + return nil }() @@ -71,17 +68,17 @@ func (svc *apigwFilter) Create(ctx context.Context, new *types.ApigwFilter) (q * ) err = func() (err error) { - if !svc.ac.CanCreateApigwFilter(ctx) { - return ApigwFilterErrNotAllowedToCreate(qProps) - } - // Set new values after beforeCreate events are emitted new.ID = nextID() new.CreatedAt = *now() new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() - if r, err = svc.route.FindByID(ctx, new.Route); err != nil { - return ApigwFilterErrNotFound(qProps) + if r, err = store.LookupApigwRouteByID(ctx, svc.store, new.Route); err != nil { + return + } + + if !svc.ac.CanUpdateApigwRoute(ctx, r) { + return ApigwRouteErrNotAllowedToUpdate() } // check for existing filters if route is async @@ -148,14 +145,14 @@ func (svc *apigwFilter) Update(ctx context.Context, upd *types.ApigwFilter) (q * return ApigwFilterErrNotFound(qProps) } - if !svc.ac.CanUpdateApigwFilter(ctx, qq) { - return ApigwFilterErrNotAllowedToUpdate(qProps) - } - if r, err = svc.route.FindByID(ctx, upd.Route); err != nil { return err } + if !svc.ac.CanUpdateApigwRoute(ctx, r) { + return ApigwRouteErrNotAllowedToUpdate() + } + if qq, e = store.LookupApigwFilterByID(ctx, svc.store, upd.ID); e == nil && qq == nil { return ApigwFilterErrNotFound(qProps) } @@ -193,12 +190,14 @@ func (svc *apigwFilter) DeleteByID(ctx context.Context, ID uint64) (err error) { return ApigwFilterErrNotFound(qProps) } - if !svc.ac.CanDeleteApigwFilter(ctx, q) { - return ApigwFilterErrNotAllowedToDelete(qProps) + if r, err = store.LookupApigwRouteByID(ctx, svc.store, q.Route); err == store.ErrNotFound { + return ApigwRouteErrNotFound() + } else if err != nil { + return } - if r, err = svc.route.FindByID(ctx, q.Route); err != nil { - return err + if !svc.ac.CanDeleteApigwRoute(ctx, r) { + return ApigwRouteErrNotAllowedToDelete() } qProps.setFilter(q) @@ -233,12 +232,14 @@ func (svc *apigwFilter) UndeleteByID(ctx context.Context, ID uint64) (err error) return ApigwFilterErrNotFound(qProps) } - if !svc.ac.CanDeleteApigwFilter(ctx, q) { - return ApigwFilterErrNotAllowedToDelete(qProps) + if r, err = store.LookupApigwRouteByID(ctx, svc.store, q.Route); err == store.ErrNotFound { + return ApigwRouteErrNotFound() + } else if err != nil { + return } - if r, err = svc.route.FindByID(ctx, q.Route); err != nil { - return err + if !svc.ac.CanDeleteApigwRoute(ctx, r) { + return ApigwRouteErrNotAllowedToUndelete() } qProps.setFilter(q) @@ -264,18 +265,32 @@ func (svc *apigwFilter) UndeleteByID(ctx context.Context, ID uint64) (err error) func (svc *apigwFilter) Search(ctx context.Context, filter types.ApigwFilterFilter) (r types.ApigwFilterSet, f types.ApigwFilterFilter, err error) { var ( aProps = &apigwFilterActionProps{search: &filter} + route *types.ApigwRoute ) - // For each fetched item, store backend will check if it is valid or not - filter.Check = func(res *types.ApigwFilter) (bool, error) { - if !svc.ac.CanReadApigwFilter(ctx, res) { - return false, nil + err = func() error { + // Preload the corresponding API GW route for access control + if filter.RouteID == 0 { + return ApigwRouteErrInvalidID() } - return true, nil - } + if route, err = store.LookupApigwRouteByID(ctx, svc.store, filter.RouteID); err != nil { + return ApigwRouteErrNotFound() + } - err = func() error { + if !svc.ac.CanReadApigwRoute(ctx, route) { + return ApigwRouteErrNotAllowedToRead() + } + + // Prepare the filter checker so we can evaluate access to specific filters + filter.Check = func(res *types.ApigwFilter) (bool, error) { + if !svc.ac.CanReadApigwRoute(ctx, route) { + return false, nil + } + return true, nil + } + + // Go! if r, f, err = store.SearchApigwFilters(ctx, svc.store, filter); err != nil { return err } @@ -292,10 +307,9 @@ func (svc *apigwFilter) DefFilter(ctx context.Context, kind string) (l interface ) err = func() error { - if !svc.ac.CanSearchApigwFilters(ctx) { - return ApigwFilterErrNotAllowedToRead(qProps) + if !svc.ac.CanSearchApigwRoutes(ctx) { + return ApigwRouteErrNotAllowedToRead() } - // get the definitions from registry l = apigw.Service().Funcs(kind) @@ -312,10 +326,9 @@ func (svc *apigwFilter) DefProxyAuth(ctx context.Context) (l interface{}, err er ) err = func() error { - if !svc.ac.CanSearchApigwFilters(ctx) { - return ApigwFilterErrNotAllowedToRead(qProps) + if !svc.ac.CanSearchApigwRoutes(ctx) { + return ApigwRouteErrNotAllowedToRead() } - // get the definitions from registry l = apigw.Service().ProxyAuthDef() diff --git a/system/service/apigw_filter_actions.gen.go b/system/service/apigw_filter_actions.gen.go index 48ec1d871..3b950aec0 100644 --- a/system/service/apigw_filter_actions.gen.go +++ b/system/service/apigw_filter_actions.gen.go @@ -435,186 +435,6 @@ func ApigwFilterErrInvalidRoute(mm ...*apigwFilterActionProps) *errors.Error { return e } -// ApigwFilterErrNotAllowedToCreate returns "system:filter.notAllowedToCreate" as *errors.Error -// -// -// This function is auto-generated. -// -func ApigwFilterErrNotAllowedToCreate(mm ...*apigwFilterActionProps) *errors.Error { - var p = &apigwFilterActionProps{} - if len(mm) > 0 { - p = mm[0] - } - - var e = errors.New( - errors.KindInternal, - - p.Format("not allowed to create a filter", nil), - - errors.Meta("type", "notAllowedToCreate"), - errors.Meta("resource", "system:filter"), - - // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(apigwFilterLogMetaKey{}, "failed to create a route; insufficient permissions"), - errors.Meta(apigwFilterPropsMetaKey{}, p), - - // translation namespace & key - errors.Meta(locale.ErrorMetaNamespace{}, "system"), - errors.Meta(locale.ErrorMetaKey{}, "apigwFilter.errors.notAllowedToCreate"), - - errors.StackSkip(1), - ) - - if len(mm) > 0 { - } - - return e -} - -// ApigwFilterErrNotAllowedToRead returns "system:filter.notAllowedToRead" as *errors.Error -// -// -// This function is auto-generated. -// -func ApigwFilterErrNotAllowedToRead(mm ...*apigwFilterActionProps) *errors.Error { - var p = &apigwFilterActionProps{} - if len(mm) > 0 { - p = mm[0] - } - - var e = errors.New( - errors.KindInternal, - - p.Format("not allowed to read this filter", nil), - - errors.Meta("type", "notAllowedToRead"), - errors.Meta("resource", "system:filter"), - - // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(apigwFilterLogMetaKey{}, "failed to read {{filter}}; insufficient permissions"), - errors.Meta(apigwFilterPropsMetaKey{}, p), - - // translation namespace & key - errors.Meta(locale.ErrorMetaNamespace{}, "system"), - errors.Meta(locale.ErrorMetaKey{}, "apigwFilter.errors.notAllowedToRead"), - - errors.StackSkip(1), - ) - - if len(mm) > 0 { - } - - return e -} - -// ApigwFilterErrNotAllowedToUpdate returns "system:filter.notAllowedToUpdate" as *errors.Error -// -// -// This function is auto-generated. -// -func ApigwFilterErrNotAllowedToUpdate(mm ...*apigwFilterActionProps) *errors.Error { - var p = &apigwFilterActionProps{} - if len(mm) > 0 { - p = mm[0] - } - - var e = errors.New( - errors.KindInternal, - - p.Format("not allowed to update this filter", nil), - - errors.Meta("type", "notAllowedToUpdate"), - errors.Meta("resource", "system:filter"), - - // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(apigwFilterLogMetaKey{}, "failed to update {{filter}}; insufficient permissions"), - errors.Meta(apigwFilterPropsMetaKey{}, p), - - // translation namespace & key - errors.Meta(locale.ErrorMetaNamespace{}, "system"), - errors.Meta(locale.ErrorMetaKey{}, "apigwFilter.errors.notAllowedToUpdate"), - - errors.StackSkip(1), - ) - - if len(mm) > 0 { - } - - return e -} - -// ApigwFilterErrNotAllowedToDelete returns "system:filter.notAllowedToDelete" as *errors.Error -// -// -// This function is auto-generated. -// -func ApigwFilterErrNotAllowedToDelete(mm ...*apigwFilterActionProps) *errors.Error { - var p = &apigwFilterActionProps{} - if len(mm) > 0 { - p = mm[0] - } - - var e = errors.New( - errors.KindInternal, - - p.Format("not allowed to delete this filter", nil), - - errors.Meta("type", "notAllowedToDelete"), - errors.Meta("resource", "system:filter"), - - // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(apigwFilterLogMetaKey{}, "failed to delete {{filter}}; insufficient permissions"), - errors.Meta(apigwFilterPropsMetaKey{}, p), - - // translation namespace & key - errors.Meta(locale.ErrorMetaNamespace{}, "system"), - errors.Meta(locale.ErrorMetaKey{}, "apigwFilter.errors.notAllowedToDelete"), - - errors.StackSkip(1), - ) - - if len(mm) > 0 { - } - - return e -} - -// ApigwFilterErrNotAllowedToUndelete returns "system:filter.notAllowedToUndelete" as *errors.Error -// -// -// This function is auto-generated. -// -func ApigwFilterErrNotAllowedToUndelete(mm ...*apigwFilterActionProps) *errors.Error { - var p = &apigwFilterActionProps{} - if len(mm) > 0 { - p = mm[0] - } - - var e = errors.New( - errors.KindInternal, - - p.Format("not allowed to undelete this filter", nil), - - errors.Meta("type", "notAllowedToUndelete"), - errors.Meta("resource", "system:filter"), - - // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(apigwFilterLogMetaKey{}, "failed to undelete {{filter}}; insufficient permissions"), - errors.Meta(apigwFilterPropsMetaKey{}, p), - - // translation namespace & key - errors.Meta(locale.ErrorMetaNamespace{}, "system"), - errors.Meta(locale.ErrorMetaKey{}, "apigwFilter.errors.notAllowedToUndelete"), - - errors.StackSkip(1), - ) - - if len(mm) > 0 { - } - - return e -} - // ApigwFilterErrAsyncRouteTooManyProcessers returns "system:filter.asyncRouteTooManyProcessers" as *errors.Error // // diff --git a/system/service/apigw_filter_actions.yaml b/system/service/apigw_filter_actions.yaml index 928af0002..bf6b8eeac 100644 --- a/system/service/apigw_filter_actions.yaml +++ b/system/service/apigw_filter_actions.yaml @@ -54,26 +54,6 @@ errors: message: "invalid route" severity: warning - - error: notAllowedToCreate - message: "not allowed to create a filter" - log: "failed to create a route; insufficient permissions" - - - error: notAllowedToRead - message: "not allowed to read this filter" - log: "failed to read {{filter}}; insufficient permissions" - - - error: notAllowedToUpdate - message: "not allowed to update this filter" - log: "failed to update {{filter}}; insufficient permissions" - - - error: notAllowedToDelete - message: "not allowed to delete this filter" - log: "failed to delete {{filter}}; insufficient permissions" - - - error: notAllowedToUndelete - message: "not allowed to undelete this filter" - log: "failed to undelete {{filter}}; insufficient permissions" - - error: asyncRouteTooManyProcessers message: "processer already exists for this async route" log: "failed to add {{filter}}; too many processers, async route" diff --git a/system/service/apigw_route.go b/system/service/apigw_route.go index 6fc5ae1b4..e60668dc7 100644 --- a/system/service/apigw_route.go +++ b/system/service/apigw_route.go @@ -30,11 +30,11 @@ type ( ) func Route() *apigwRoute { - return (&apigwRoute{ + return &apigwRoute{ ac: DefaultAccessControl, actionlog: DefaultActionlog, store: DefaultStore, - }) + } } func (svc *apigwRoute) FindByID(ctx context.Context, ID uint64) (q *types.ApigwRoute, err error) { @@ -47,10 +47,6 @@ func (svc *apigwRoute) FindByID(ctx context.Context, ID uint64) (q *types.ApigwR 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) } @@ -133,7 +129,9 @@ func (svc *apigwRoute) Update(ctx context.Context, upd *types.ApigwRoute) (q *ty q = upd // send the signal to reload all route - apigw.Service().Reload(ctx) + if qq.Enabled != upd.Enabled { + apigw.Service().Reload(ctx) + } return nil }() @@ -170,7 +168,9 @@ func (svc *apigwRoute) DeleteByID(ctx context.Context, ID uint64) (err error) { } // send the signal to reload all queues - apigw.Service().Reload(ctx) + if q.Enabled { + apigw.Service().Reload(ctx) + } return nil }() @@ -194,7 +194,7 @@ func (svc *apigwRoute) UndeleteByID(ctx context.Context, ID uint64) (err error) } if !svc.ac.CanDeleteApigwRoute(ctx, q) { - return ApigwRouteErrNotAllowedToDelete(qProps) + return ApigwRouteErrNotAllowedToUndelete(qProps) } qProps.setRoute(q) @@ -207,7 +207,9 @@ func (svc *apigwRoute) UndeleteByID(ctx context.Context, ID uint64) (err error) } // send the signal to reload all queues - apigw.Service().Reload(ctx) + if q.Enabled { + apigw.Service().Reload(ctx) + } return nil }() @@ -230,6 +232,10 @@ func (svc *apigwRoute) Search(ctx context.Context, filter types.ApigwRouteFilter } err = func() error { + if !svc.ac.CanSearchApigwRoutes(ctx) { + return ApigwRouteErrNotAllowedToSearch() + } + if r, f, err = store.SearchApigwRoutes(ctx, svc.store, filter); err != nil { return err } diff --git a/system/service/apigw_route_actions.gen.go b/system/service/apigw_route_actions.gen.go index 16db54314..672852e6f 100644 --- a/system/service/apigw_route_actions.gen.go +++ b/system/service/apigw_route_actions.gen.go @@ -635,6 +635,42 @@ func ApigwRouteErrNotAllowedToRead(mm ...*apigwRouteActionProps) *errors.Error { return e } +// ApigwRouteErrNotAllowedToSearch returns "system:apigw-route.notAllowedToSearch" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwRouteErrNotAllowedToSearch(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to list or search routes", nil), + + errors.Meta("type", "notAllowedToSearch"), + errors.Meta("resource", "system:apigw-route"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwRouteLogMetaKey{}, "failed to search for routes; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "apigwRoute.errors.notAllowedToSearch"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + // ApigwRouteErrNotAllowedToUpdate returns "system:apigw-route.notAllowedToUpdate" as *errors.Error // // diff --git a/system/service/apigw_route_actions.yaml b/system/service/apigw_route_actions.yaml index 00887980c..6891dc00a 100644 --- a/system/service/apigw_route_actions.yaml +++ b/system/service/apigw_route_actions.yaml @@ -76,6 +76,10 @@ errors: message: "not allowed to read this route" log: "failed to read {{route.endpoint}}; insufficient permissions" + - error: notAllowedToSearch + message: "not allowed to list or search routes" + log: "failed to search for routes; insufficient permissions" + - error: notAllowedToUpdate message: "not allowed to update this route" log: "failed to update {{route.endpoint}}; insufficient permissions" diff --git a/system/types/apigw_filter.go b/system/types/apigw_filter.go index 43e286106..5f36ffd89 100644 --- a/system/types/apigw_filter.go +++ b/system/types/apigw_filter.go @@ -29,10 +29,8 @@ type ( } ApigwFilterFilter struct { - RouteID uint64 `json:"routeID,string"` - Endpoint string `json:"endpoint"` - Group string `json:"group"` - Enabled bool `json:"enabled"` + RouteID uint64 `json:"routeID,string"` + Enabled bool `json:"enabled"` Deleted filter.State `json:"deleted"` diff --git a/system/types/rbac.gen.go b/system/types/rbac.gen.go index c7b4e681b..0f27447a8 100644 --- a/system/types/rbac.gen.go +++ b/system/types/rbac.gen.go @@ -7,7 +7,6 @@ package types // // Definitions file that controls how this file is generated: -// - system.apigw-filter.yaml // - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml @@ -30,7 +29,6 @@ type ( ) const ( - ApigwFilterResourceType = "corteza::system:apigw-filter" ApigwRouteResourceType = "corteza::system:apigw-route" ApplicationResourceType = "corteza::system:application" AuthClientResourceType = "corteza::system:auth-client" @@ -41,37 +39,6 @@ const ( ComponentResourceType = "corteza::system" ) -// RbacResource returns string representation of RBAC resource for ApigwFilter by calling ApigwFilterRbacResource fn -// -// RBAC resource is in the corteza::system:apigw-filter/... format -// -// This function is auto-generated -func (r ApigwFilter) RbacResource() string { - return ApigwFilterRbacResource(r.ID) -} - -// ApigwFilterRbacResource returns string representation of RBAC resource for ApigwFilter -// -// RBAC resource is in the corteza::system:apigw-filter/... format -// -// This function is auto-generated -func ApigwFilterRbacResource(id uint64) string { - cpts := []interface{}{ApigwFilterResourceType} - if id != 0 { - cpts = append(cpts, strconv.FormatUint(id, 10)) - } else { - cpts = append(cpts, "*") - } - - return fmt.Sprintf(ApigwFilterRbacResourceTpl(), cpts...) - -} - -// @todo template -func ApigwFilterRbacResourceTpl() 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 diff --git a/tests/system/apigw_test.go b/tests/system/apigw_test.go new file mode 100644 index 000000000..e2e04f1e6 --- /dev/null +++ b/tests/system/apigw_test.go @@ -0,0 +1,504 @@ +package system + +import ( + "context" + "fmt" + "net/http" + "strconv" + "testing" + "time" + + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/service" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" + jsonpath "github.com/steinfletcher/apitest-jsonpath" +) + +func (h helper) createRouteWithFilter(s string, fkind string) (*types.ApigwRoute, *types.ApigwFilter) { + r := h.createRoute(&types.ApigwRoute{Endpoint: "/" + s, Method: "GET"}) + f := h.createFilters(&types.ApigwFilter{Kind: fkind}, r.ID) + + return r, f +} + +func (h helper) createRoute(route *types.ApigwRoute) *types.ApigwRoute { + if route.ID == 0 { + route.ID = id.Next() + } + + if route.CreatedAt.IsZero() { + route.CreatedAt = time.Now() + } + + h.a.NoError(service.DefaultStore.CreateApigwRoute(context.Background(), route)) + return route +} + +func (h helper) createFilters(f *types.ApigwFilter, routeID uint64) *types.ApigwFilter { + f.Route = routeID + + if f.ID == 0 { + f.ID = id.Next() + } + + if f.CreatedAt.IsZero() { + f.CreatedAt = time.Now() + } + + h.a.NoError(service.DefaultStore.CreateApigwFilter(context.Background(), f)) + return f +} + +func (h helper) clearRoutes() { + h.noError(store.TruncateApigwFilters(context.Background(), service.DefaultStore)) + h.noError(store.TruncateApigwRoutes(context.Background(), service.DefaultStore)) +} + +func TestApigwRouteRead(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/route/%d", r.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))). + Assert(jsonpath.Equal(`$.response.endpoint`, "/test")). + End() +} + +func TestApigwRouteRead_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/route/%d", r.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToRead")). + End() +} + +func TestApigwRouteSearch(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + h.createRouteWithFilter("test1", "") + h.createRouteWithFilter("test2", "") + + helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search") + helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/route/")). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Len(`$.response.set`, 2)). + Assert(jsonpath.Equal(`$.response.set[0].endpoint`, "/test1")). + Assert(jsonpath.Equal(`$.response.set[1].endpoint`, "/test2")). + End() +} + +func TestApigwRouteSearch_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + h.createRouteWithFilter("test1", "") + h.createRouteWithFilter("test2", "") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/route/")). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToSearch")). + End() +} + +func TestApigwRouteSearch_forbidenSpecific(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test1", "") + h.createRouteWithFilter("test2", "") + + helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search") + helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read") + helpers.DenyMe(h, types.ApigwRouteRbacResource(r.ID), "read") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/route/")). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Len(`$.response.set`, 1)). + Assert(jsonpath.Equal(`$.response.set[0].endpoint`, "/test2")). + End() +} + +func TestApigwRouteCreate(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-route.create") + + h.apiInit(). + Post(fmt.Sprintf("/apigw/route")). + Header("Accept", "application/json"). + FormData("endpoint", "/test"). + FormData("method", "GET"). + FormData("enabled", "false"). + FormData("group", "g1"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.routeID`)). + Assert(jsonpath.Equal(`$.response.endpoint`, "/test")). + End() +} + +func TestApigwRouteCreate_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + h.apiInit(). + Post(fmt.Sprintf("/apigw/route")). + Header("Accept", "application/json"). + FormData("endpoint", "/test"). + FormData("method", "GET"). + FormData("enabled", "false"). + FormData("group", "g1"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToCreate")). + End() +} + +func TestApigwRouteUpdate(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update") + + h.apiInit(). + Put(fmt.Sprintf("/apigw/route/%d", r.ID)). + Header("Accept", "application/json"). + FormData("endpoint", "/test-edited"). + FormData("enabled", "false"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.routeID`)). + Assert(jsonpath.Equal(`$.response.endpoint`, "/test-edited")). + End() +} + +func TestApigwRouteUpdate_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + h.apiInit(). + Put(fmt.Sprintf("/apigw/route/%d", r.ID)). + Header("Accept", "application/json"). + FormData("endpoint", "/test-edited"). + FormData("enabled", "false"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToUpdate")). + End() +} + +func TestApigwRouteDelete(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete") + + h.apiInit(). + Delete(fmt.Sprintf("/apigw/route/%d", r.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} + +func TestApigwRouteDelete_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + h.apiInit(). + Delete(fmt.Sprintf("/apigw/route/%d", r.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToDelete")). + End() +} + +func TestApigwRouteUnDelete(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete") + + h.apiInit(). + Post(fmt.Sprintf("/apigw/route/%d/undelete", r.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} + +func TestApigwRouteUnDelete_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test", "") + + h.apiInit(). + Post(fmt.Sprintf("/apigw/route/%d/undelete", r.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToUndelete")). + End() +} + +// Filters + +func TestApigwFilterRead(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, f := h.createRouteWithFilter("test", "test") + + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/filter/%d", f.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Equal(`$.response.filterID`, strconv.FormatUint(f.ID, 10))). + Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))). + End() +} + +func TestApigwFilterRead_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + _, f := h.createRouteWithFilter("test", "test") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/filter/%d", f.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToRead")). + End() +} + +func TestApigwFilterSearch(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, f := h.createRouteWithFilter("test1", "") + + helpers.AllowMe(h, types.ComponentRbacResource(), "apigw-routes.search") + helpers.AllowMe(h, types.ApigwRouteRbacResource(0), "read") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/filter/")). + Query("routeID", strconv.FormatUint(r.ID, 10)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Len(`$.response.set`, 1)). + Assert(jsonpath.Equal(`$.response.set[0].filterID`, strconv.FormatUint(f.ID, 10))). + Assert(jsonpath.Equal(`$.response.set[0].routeID`, strconv.FormatUint(r.ID, 10))). + End() +} + +func TestApigwFilterSearch_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test1", "") + + h.apiInit(). + Get(fmt.Sprintf("/apigw/filter/")). + Query("routeID", strconv.FormatUint(r.ID, 10)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToRead")). + End() +} + +func TestApigwFilterCreate(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test1", "") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update") + + h.apiInit(). + Put(fmt.Sprintf("/apigw/filter")). + Header("Accept", "application/json"). + FormData("routeID", strconv.FormatUint(r.ID, 10)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.filterID`)). + Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))). + End() +} + +func TestApigwFilterCreate_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, _ := h.createRouteWithFilter("test1", "") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + + h.apiInit(). + Put(fmt.Sprintf("/apigw/filter")). + Header("Accept", "application/json"). + FormData("routeID", strconv.FormatUint(r.ID, 10)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToUpdate")). + End() +} + +func TestApigwFilterUpdate(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, f := h.createRouteWithFilter("test1", "") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "update") + + h.apiInit(). + Post(fmt.Sprintf("/apigw/filter/%d", f.ID)). + Header("Accept", "application/json"). + FormData("routeID", strconv.FormatUint(r.ID, 10)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Equal(`$.response.filterID`, strconv.FormatUint(f.ID, 10))). + Assert(jsonpath.Equal(`$.response.routeID`, strconv.FormatUint(r.ID, 10))). + End() +} + +func TestApigwFilterUpdate_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, f := h.createRouteWithFilter("test1", "") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + + h.apiInit(). + Post(fmt.Sprintf("/apigw/filter/%d", f.ID)). + Header("Accept", "application/json"). + FormData("routeID", strconv.FormatUint(r.ID, 10)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToUpdate")). + End() +} + +func TestApigwFilterDelete(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, f := h.createRouteWithFilter("test1", "") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete") + + h.apiInit(). + Delete(fmt.Sprintf("/apigw/filter/%d", f.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} + +func TestApigwFilterDelete_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + _, f := h.createRouteWithFilter("test1", "") + + h.apiInit(). + Delete(fmt.Sprintf("/apigw/filter/%d", f.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToDelete")). + End() +} + +func TestApigwFilterUnDelete(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, f := h.createRouteWithFilter("test1", "") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "delete") + + h.apiInit(). + Post(fmt.Sprintf("/apigw/filter/%d/undelete", f.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} + +func TestApigwFilterUnDelete_forbiden(t *testing.T) { + h := newHelper(t) + h.clearRoutes() + + r, f := h.createRouteWithFilter("test1", "") + helpers.AllowMe(h, types.ApigwRouteRbacResource(r.ID), "read") + + h.apiInit(). + Post(fmt.Sprintf("/apigw/filter/%d/undelete", f.ID)). + Header("Accept", "application/json"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("apigwRoute.errors.notAllowedToUndelete")). + End() +}