From db325fc1255d2fd43464087a259ec4302ba2e0f3 Mon Sep 17 00:00:00 2001 From: Vivek Patel Date: Wed, 3 Aug 2022 16:32:16 +0530 Subject: [PATCH] Add route to list dal connections for privacy console It will only list dal connections with minimal data. --- system/rest.yaml | 28 ++++++ system/rest/data_privacy.go | 63 +++++++++++++ system/rest/handlers/dataPrivacy.go | 57 ++++++++++++ system/rest/request/dataPrivacy.go | 134 ++++++++++++++++++++++++++++ system/rest/router.go | 2 + system/service/data_privacy.go | 57 +++++++++++- system/types/dal_connection.go | 4 +- system/types/data_privacy.go | 20 +++++ system/types/type_set.gen.go | 61 +++++++++++++ system/types/type_set.gen_test.go | 90 +++++++++++++++++++ system/types/types.yaml | 1 + 11 files changed, 512 insertions(+), 5 deletions(-) create mode 100644 system/rest/data_privacy.go create mode 100644 system/rest/handlers/dataPrivacy.go create mode 100644 system/rest/request/dataPrivacy.go diff --git a/system/rest.yaml b/system/rest.yaml index 75761ad1f..f0e57f9be 100644 --- a/system/rest.yaml +++ b/system/rest.yaml @@ -2268,3 +2268,31 @@ endpoints: post: - { name: comment, type: "string", title: "Comment description", required: true } +- title: Data Privacy + entrypoint: dataPrivacy + imports: + - github.com/cortezaproject/corteza-server/pkg/filter + path: "/data-privacy" + apis: + - name: connection list + method: GET + title: List connections for data privacy + path: "/connection/" + parameters: + get: + - name: connectionID + type: "[]string" + required: false + title: Filter by connection ID + - type: string + name: handle + required: false + title: Search handle to match against connections + - type: string + name: type + required: false + title: Search type to match against connections + - name: deleted + required: false + title: Exclude (0, default), include (1) or return only (2) deleted connections + type: filter.State diff --git a/system/rest/data_privacy.go b/system/rest/data_privacy.go new file mode 100644 index 000000000..0edf2814e --- /dev/null +++ b/system/rest/data_privacy.go @@ -0,0 +1,63 @@ +package rest + +import ( + "context" + + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/cortezaproject/corteza-server/system/rest/request" + "github.com/cortezaproject/corteza-server/system/service" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + DataPrivacy struct { + privacy service.DataPrivacyService + ac privacyAccessController + } + + privacyAccessController interface { + CanGrant(context.Context) bool + } + + privacyConnectionSetPayload struct { + Filter types.DalConnectionFilter `json:"filter"` + Set types.PrivacyDalConnectionSet `json:"set"` + } +) + +func (DataPrivacy) New() *DataPrivacy { + return &DataPrivacy{ + privacy: service.DefaultDataPrivacy, + ac: service.DefaultAccessControl, + } +} + +func (ctrl DataPrivacy) ConnectionList(ctx context.Context, r *request.DataPrivacyConnectionList) (interface{}, error) { + var ( + err error + set types.PrivacyDalConnectionSet + + f = types.DalConnectionFilter{ + ConnectionID: payload.ParseUint64s(r.ConnectionID), + Handle: r.Handle, + Type: r.Type, + + Deleted: r.Deleted, + } + ) + + set, f, err = ctrl.privacy.FindConnections(ctx, f) + return ctrl.makeFilterConnectionPayload(ctx, set, f, err) +} + +func (ctrl DataPrivacy) makeFilterConnectionPayload(_ context.Context, rr types.PrivacyDalConnectionSet, f types.DalConnectionFilter, err error) (*privacyConnectionSetPayload, error) { + if err != nil { + return nil, err + } + + if len(rr) == 0 { + rr = make([]*types.PrivacyDalConnection, 0) + } + + return &privacyConnectionSetPayload{Filter: f, Set: rr}, nil +} diff --git a/system/rest/handlers/dataPrivacy.go b/system/rest/handlers/dataPrivacy.go new file mode 100644 index 000000000..885b4acc2 --- /dev/null +++ b/system/rest/handlers/dataPrivacy.go @@ -0,0 +1,57 @@ +package handlers + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "context" + "github.com/cortezaproject/corteza-server/pkg/api" + "github.com/cortezaproject/corteza-server/system/rest/request" + "github.com/go-chi/chi/v5" + "net/http" +) + +type ( + // Internal API interface + DataPrivacyAPI interface { + ConnectionList(context.Context, *request.DataPrivacyConnectionList) (interface{}, error) + } + + // HTTP API interface + DataPrivacy struct { + ConnectionList func(http.ResponseWriter, *http.Request) + } +) + +func NewDataPrivacy(h DataPrivacyAPI) *DataPrivacy { + return &DataPrivacy{ + ConnectionList: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewDataPrivacyConnectionList() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.ConnectionList(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + } +} + +func (h DataPrivacy) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Get("/data-privacy/connection/", h.ConnectionList) + }) +} diff --git a/system/rest/request/dataPrivacy.go b/system/rest/request/dataPrivacy.go new file mode 100644 index 000000000..75be9c302 --- /dev/null +++ b/system/rest/request/dataPrivacy.go @@ -0,0 +1,134 @@ +package request + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// + +import ( + "encoding/json" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/payload" + "github.com/go-chi/chi/v5" + "io" + "mime/multipart" + "net/http" + "strings" +) + +// dummy vars to prevent +// unused imports complain +var ( + _ = chi.URLParam + _ = multipart.ErrMessageTooLarge + _ = payload.ParseUint64s + _ = strings.ToLower + _ = io.EOF + _ = fmt.Errorf + _ = json.NewEncoder +) + +type ( + // Internal API interface + DataPrivacyConnectionList struct { + // ConnectionID GET parameter + // + // Filter by connection ID + ConnectionID []string + + // Handle GET parameter + // + // Search handle to match against connections + Handle string + + // Type GET parameter + // + // Search type to match against connections + Type string + + // Deleted GET parameter + // + // Exclude (0, default), include (1) or return only (2) deleted connections + Deleted filter.State + } +) + +// NewDataPrivacyConnectionList request +func NewDataPrivacyConnectionList() *DataPrivacyConnectionList { + return &DataPrivacyConnectionList{} +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyConnectionList) Auditable() map[string]interface{} { + return map[string]interface{}{ + "connectionID": r.ConnectionID, + "handle": r.Handle, + "type": r.Type, + "deleted": r.Deleted, + } +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyConnectionList) GetConnectionID() []string { + return r.ConnectionID +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyConnectionList) GetHandle() string { + return r.Handle +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyConnectionList) GetType() string { + return r.Type +} + +// Auditable returns all auditable/loggable parameters +func (r DataPrivacyConnectionList) GetDeleted() filter.State { + return r.Deleted +} + +// Fill processes request and fills internal variables +func (r *DataPrivacyConnectionList) Fill(req *http.Request) (err error) { + + { + // GET params + tmp := req.URL.Query() + + if val, ok := tmp["connectionID[]"]; ok { + r.ConnectionID, err = val, nil + if err != nil { + return err + } + } else if val, ok := tmp["connectionID"]; ok { + r.ConnectionID, err = val, nil + if err != nil { + return err + } + } + if val, ok := tmp["handle"]; ok && len(val) > 0 { + r.Handle, err = val[0], nil + if err != nil { + return err + } + } + if val, ok := tmp["type"]; ok && len(val) > 0 { + r.Type, err = val[0], nil + if err != nil { + return err + } + } + if val, ok := tmp["deleted"]; ok && len(val) > 0 { + r.Deleted, err = payload.ParseFilterState(val[0]), nil + if err != nil { + return err + } + } + } + + return err +} diff --git a/system/rest/router.go b/system/rest/router.go index 8239f7f81..b4b19a34c 100644 --- a/system/rest/router.go +++ b/system/rest/router.go @@ -47,6 +47,8 @@ func MountRoutes() func(r chi.Router) { handlers.NewApigwRoute(ApigwRoute{}.New()).MountRoutes(r) handlers.NewApigwFilter(ApigwFilter{}.New()).MountRoutes(r) handlers.NewApigwProfiler(ApigwProfiler{}.New()).MountRoutes(r) + handlers.NewDataPrivacy(DataPrivacy{}.New()).MountRoutes(r) + // @todo move these two to dataPrivacy routes handlers.NewDataPrivacyRequest(DataPrivacyRequest{}.New()).MountRoutes(r) handlers.NewDataPrivacyRequestComment(DataPrivacyRequestComment{}.New()).MountRoutes(r) }) diff --git a/system/service/data_privacy.go b/system/service/data_privacy.go index 9f14997b0..ca582f49a 100644 --- a/system/service/data_privacy.go +++ b/system/service/data_privacy.go @@ -13,11 +13,11 @@ import ( type ( dataPrivacy struct { actionlog actionlog.Recorder + store store.Storer ac dataPrivacyAccessController eventbus eventDispatcher - - store store.Storer + dalConn dalConnectionService } dataPrivacyAccessController interface { @@ -25,9 +25,17 @@ type ( CanCreateDataPrivacyRequest(context.Context) bool CanReadDataPrivacyRequest(context.Context, *types.DataPrivacyRequest) bool CanApproveDataPrivacyRequest(context.Context, *types.DataPrivacyRequest) bool + + CanSearchDalConnections(context.Context) bool + } + + dalConnectionService interface { + Search(ctx context.Context, filter types.DalConnectionFilter) (r types.DalConnectionSet, f types.DalConnectionFilter, err error) } DataPrivacyService interface { + FindConnections(ctx context.Context, filter types.DalConnectionFilter) (out types.PrivacyDalConnectionSet, f types.DalConnectionFilter, err error) + FindRequestByID(ctx context.Context, requestID uint64) (*types.DataPrivacyRequest, error) FindRequests(context.Context, types.DataPrivacyRequestFilter) (types.DataPrivacyRequestSet, types.DataPrivacyRequestFilter, error) CreateRequest(ctx context.Context, request *types.DataPrivacyRequest) (*types.DataPrivacyRequest, error) @@ -41,12 +49,55 @@ type ( func DataPrivacy(s store.Storer, ac dataPrivacyAccessController, al actionlog.Recorder, eb eventDispatcher) *dataPrivacy { return &dataPrivacy{ actionlog: al, + store: s, ac: ac, eventbus: eb, - store: s, } } +func (svc dataPrivacy) FindConnections(ctx context.Context, filter types.DalConnectionFilter) (out types.PrivacyDalConnectionSet, f types.DalConnectionFilter, err error) { + var ( + cc types.DalConnectionSet + ) + err = func() error { + if !svc.ac.CanSearchDalConnections(ctx) { + return DalConnectionErrNotAllowedToSearch() + } + + cc, f, err = svc.dalConn.Search(ctx, filter) + if err != nil { + return err + } + + err = cc.Walk(func(c *types.DalConnection) error { + pc := types.PrivacyDalConnection{ + ID: c.ID, + Name: c.Meta.Name, + Handle: c.Handle, + Type: c.Type, + Location: c.Meta.Location, + Ownership: c.Meta.Ownership, + SensitivityLevel: c.Config.Privacy.SensitivityLevelID, + CreatedAt: c.CreatedAt, + CreatedBy: c.CreatedBy, + UpdatedAt: c.UpdatedAt, + UpdatedBy: c.UpdatedBy, + DeletedAt: c.DeletedAt, + DeletedBy: c.DeletedBy, + } + out = append(out, &pc) + return nil + }) + if err != nil { + return err + } + + return nil + }() + + return +} + func (svc dataPrivacy) FindRequestByID(ctx context.Context, requestID uint64) (r *types.DataPrivacyRequest, err error) { var ( raProps = &dataPrivacyActionProps{dataPrivacyRequest: &types.DataPrivacyRequest{ID: requestID}} diff --git a/system/types/dal_connection.go b/system/types/dal_connection.go index 2d36aef2a..3f64c78fd 100644 --- a/system/types/dal_connection.go +++ b/system/types/dal_connection.go @@ -28,9 +28,9 @@ type ( CreatedAt time.Time `json:"createdAt,omitempty"` CreatedBy uint64 `json:"createdBy,string" ` UpdatedAt *time.Time `json:"updatedAt,omitempty"` - UpdatedBy uint64 `json:"updatedBy,string,omitempty" ` + UpdatedBy uint64 `json:"updatedBy,string,omitempty"` DeletedAt *time.Time `json:"deletedAt,omitempty"` - DeletedBy uint64 `json:"deletedBy,string,omitempty" ` + DeletedBy uint64 `json:"deletedBy,string,omitempty"` } ConnectionConfig struct { diff --git a/system/types/data_privacy.go b/system/types/data_privacy.go index b9553acab..3a54afd2f 100644 --- a/system/types/data_privacy.go +++ b/system/types/data_privacy.go @@ -3,6 +3,7 @@ package types import ( "database/sql/driver" "encoding/json" + "github.com/cortezaproject/corteza-server/pkg/geolocation" "github.com/cortezaproject/corteza-server/pkg/sql" "time" @@ -99,6 +100,25 @@ type ( filter.Paging } + PrivacyDalConnection struct { + ID uint64 `json:"connectionID,string"` + Name string `json:"name"` + Handle string `json:"handle"` + + Type string `json:"type"` + + Location geolocation.Full `json:"location"` + Ownership string `json:"ownership"` + SensitivityLevel uint64 `json:"sensitivityLevel,string,omitempty"` + + CreatedAt time.Time `json:"createdAt,omitempty"` + CreatedBy uint64 `json:"createdBy,string" ` + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + UpdatedBy uint64 `json:"updatedBy,string,omitempty" ` + DeletedAt *time.Time `json:"deletedAt,omitempty"` + DeletedBy uint64 `json:"deletedBy,string,omitempty" ` + } + RequestStatus string RequestKind string ) diff --git a/system/types/type_set.gen.go b/system/types/type_set.gen.go index 9c443d877..0c2286c8d 100644 --- a/system/types/type_set.gen.go +++ b/system/types/type_set.gen.go @@ -85,6 +85,11 @@ type ( // This type is auto-generated. DataPrivacyRequestCommentSet []*DataPrivacyRequestComment + // PrivacyDalConnectionSet slice of PrivacyDalConnection + // + // This type is auto-generated. + PrivacyDalConnectionSet []*PrivacyDalConnection + // QueueSet slice of Queue // // This type is auto-generated. @@ -872,6 +877,62 @@ func (set DataPrivacyRequestCommentSet) IDs() (IDs []uint64) { return } +// Walk iterates through every slice item and calls w(PrivacyDalConnection) err +// +// This function is auto-generated. +func (set PrivacyDalConnectionSet) Walk(w func(*PrivacyDalConnection) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(PrivacyDalConnection) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set PrivacyDalConnectionSet) Filter(f func(*PrivacyDalConnection) (bool, error)) (out PrivacyDalConnectionSet, err error) { + var ok bool + out = PrivacyDalConnectionSet{} + 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 PrivacyDalConnectionSet) FindByID(ID uint64) *PrivacyDalConnection { + 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 PrivacyDalConnectionSet) 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(Queue) 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 6bcfc17d5..bd38dd74e 100644 --- a/system/types/type_set.gen_test.go +++ b/system/types/type_set.gen_test.go @@ -1228,6 +1228,96 @@ func TestDataPrivacyRequestCommentSetIDs(t *testing.T) { } } +func TestPrivacyDalConnectionSetWalk(t *testing.T) { + var ( + value = make(PrivacyDalConnectionSet, 3) + req = require.New(t) + ) + + // check walk with no errors + { + err := value.Walk(func(*PrivacyDalConnection) error { + return nil + }) + req.NoError(err) + } + + // check walk with error + req.Error(value.Walk(func(*PrivacyDalConnection) error { return fmt.Errorf("walk error") })) +} + +func TestPrivacyDalConnectionSetFilter(t *testing.T) { + var ( + value = make(PrivacyDalConnectionSet, 3) + req = require.New(t) + ) + + // filter nothing + { + set, err := value.Filter(func(*PrivacyDalConnection) (bool, error) { + return true, nil + }) + req.NoError(err) + req.Equal(len(set), len(value)) + } + + // filter one item + { + found := false + set, err := value.Filter(func(*PrivacyDalConnection) (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(*PrivacyDalConnection) (bool, error) { + return false, fmt.Errorf("filter error") + }) + req.Error(err) + } +} + +func TestPrivacyDalConnectionSetIDs(t *testing.T) { + var ( + value = make(PrivacyDalConnectionSet, 3) + req = require.New(t) + ) + + // construct objects + value[0] = new(PrivacyDalConnection) + value[1] = new(PrivacyDalConnection) + value[2] = new(PrivacyDalConnection) + // 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 TestQueueSetWalk(t *testing.T) { var ( value = make(QueueSet, 3) diff --git a/system/types/types.yaml b/system/types/types.yaml index fc62b1b3d..837dd1504 100644 --- a/system/types/types.yaml +++ b/system/types/types.yaml @@ -37,3 +37,4 @@ types: noIdField: true DataPrivacyRequest: {} DataPrivacyRequestComment: {} + PrivacyDalConnection: