diff --git a/lib/api/interface.go b/lib/api/interface.go index 3554d86..dd22547 100644 --- a/lib/api/interface.go +++ b/lib/api/interface.go @@ -50,7 +50,7 @@ type Query interface { //migration Import(imports map[string][]model.ResourceRights) (err error) - Export() (exports map[string][]model.ResourceRights, err error) + Export(token string) (exports map[string][]model.ResourceRights, err error) } type V3 interface { @@ -63,4 +63,6 @@ type V3 interface { GetRights(token string, kind string, resource string) (result model.ResourceRights, err error) GetTermAggregation(token string, kind string, rights string, field string, limit int) (result []model.TermAggregationResultElement, err error) + + ExportKind(token string, kind string, limit int, offset int) (result []model.ResourceRights, err error) } diff --git a/lib/api/v1endpoints.go b/lib/api/v1endpoints.go index ee0b337..d9ee3c0 100644 --- a/lib/api/v1endpoints.go +++ b/lib/api/v1endpoints.go @@ -569,7 +569,7 @@ func V1Endpoints(router *httprouter.Router, config configuration.Config, q Query router.GET("/export", func(res http.ResponseWriter, r *http.Request, ps httprouter.Params) { res.Header().Set("Deprecation", "true") - exports, err := q.Export() + exports, err := q.Export(auth.GetAuthToken(r)) if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) return diff --git a/lib/api/v3endpoints.go b/lib/api/v3endpoints.go index 020d4e1..12dfacf 100644 --- a/lib/api/v3endpoints.go +++ b/lib/api/v3endpoints.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "fmt" "github.com/SENERGY-Platform/permission-search/lib/auth" "github.com/SENERGY-Platform/permission-search/lib/configuration" "github.com/SENERGY-Platform/permission-search/lib/model" @@ -263,5 +264,37 @@ func V3Endpoints(router *httprouter.Router, config configuration.Config, q Query writer.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(writer).Encode(result) }) + + router.GET("/v3/export/:resource", func(res http.ResponseWriter, r *http.Request, ps httprouter.Params) { + var err error + token := auth.GetAuthToken(r) + resource := ps.ByName("resource") + limit := 100 + limitStr := r.URL.Query().Get("limit") + if limitStr != "" { + limit, err = strconv.Atoi(limitStr) + if err != nil { + http.Error(res, fmt.Sprintf("invalit limit: %v", err.Error()), http.StatusBadRequest) + return + } + } + offset := 0 + offsetStr := r.URL.Query().Get("offset") + if offsetStr != "" { + offset, err = strconv.Atoi(offsetStr) + if err != nil { + http.Error(res, fmt.Sprintf("invalit offset: %v", err.Error()), http.StatusBadRequest) + return + } + } + exports, err := q.ExportKind(token, resource, limit, offset) + if err != nil { + http.Error(res, err.Error(), http.StatusInternalServerError) + return + } + res.Header().Set("Content-Type", "application/json; charset=utf-8") + json.NewEncoder(res).Encode(exports) + }) + return true } diff --git a/lib/apiv3_test.go b/lib/apiv3_test.go index 653e486..95094ed 100644 --- a/lib/apiv3_test.go +++ b/lib/apiv3_test.go @@ -102,6 +102,60 @@ func TestApiV3(t *testing.T) { time.Sleep(10 * time.Second) //kafka latency + t.Run("export", func(t *testing.T) { + list, err := c.ExportKind(admintoken, "aspects", 3, 1) + if err != nil { + t.Error(err) + return + } + if len(list) != 3 { + t.Error(len(list)) + return + } + expected := []model.ResourceRights{ + { + ResourceRightsBase: model.ResourceRightsBase{ + UserRights: map[string]model.Right{"testOwner": {Read: true, Write: true, Execute: true, Administrate: true}}, + GroupRights: map[string]model.Right{ + "admin": {Read: true, Write: true, Execute: true, Administrate: true}, + "user": {Read: true, Write: false, Execute: true, Administrate: false}, + }, + }, + ResourceId: "aspect1", + Features: map[string]interface{}{"name": "aspect1_name", "raw": map[string]interface{}{"name": "aspect1_name", "rdf_type": "aspect_type"}}, + Creator: "testOwner", + }, + { + ResourceRightsBase: model.ResourceRightsBase{ + UserRights: map[string]model.Right{"testOwner": {Read: true, Write: true, Execute: true, Administrate: true}}, + GroupRights: map[string]model.Right{ + "admin": {Read: true, Write: true, Execute: true, Administrate: true}, + "user": {Read: true, Write: false, Execute: true, Administrate: false}, + }, + }, + ResourceId: "aspect2", + Features: map[string]interface{}{"name": "aspect2_name", "raw": map[string]interface{}{"name": "aspect2_name", "rdf_type": "aspect_type"}}, + Creator: "testOwner", + }, + { + ResourceRightsBase: model.ResourceRightsBase{ + UserRights: map[string]model.Right{"testOwner": {Read: true, Write: true, Execute: true, Administrate: true}}, + GroupRights: map[string]model.Right{ + "admin": {Read: true, Write: true, Execute: true, Administrate: true}, + "user": {Read: true, Write: false, Execute: true, Administrate: false}, + }, + }, + ResourceId: "aspect3", + Features: map[string]interface{}{"name": "aspect3_name", "raw": map[string]interface{}{"name": "aspect3_name", "rdf_type": "aspect_type"}}, + Creator: "testOwner", + }, + } + if !reflect.DeepEqual(expected, list) { + t.Errorf("\ne:%#v\na:%#v\n", expected, list) + return + } + }) + t.Run("client list", func(t *testing.T) { actual, err := c.List(ctoken, "aspects", client.ListOptions{}) if err != nil { diff --git a/lib/client/client.go b/lib/client/client.go index 74a73cf..fffe21c 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -32,6 +32,8 @@ type Client interface { GetRights(token string, kind string, resource string) (result model.ResourceRights, err error) GetTermAggregation(token string, kind string, rights string, field string, limit int) (result []model.TermAggregationResultElement, err error) + + ExportKind(token string, kind string, limit int, offset int) (result []model.ResourceRights, err error) } type impl struct { diff --git a/lib/client/v3.go b/lib/client/v3.go index 477fcf1..ed36de3 100644 --- a/lib/client/v3.go +++ b/lib/client/v3.go @@ -52,40 +52,50 @@ func (this *impl) Query(token string, query model.QueryMessage) (result interfac func (this *impl) List(token string, kind string, options model.ListOptions) (result []map[string]interface{}, err error) { req, err := http.NewRequest(http.MethodGet, this.baseUrl+"/v3/resources/"+url.PathEscape(kind)+"?"+options.QueryValues().Encode(), nil) - req.Header.Set("Authorization", token) if err != nil { return result, err } + req.Header.Set("Authorization", token) result, _, err = do[[]map[string]interface{}](req) return } func (this *impl) Total(token string, kind string, options model.ListOptions) (result int64, err error) { req, err := http.NewRequest(http.MethodGet, this.baseUrl+"/v3/total/"+url.PathEscape(kind)+"?"+options.QueryValues().Encode(), nil) - req.Header.Set("Authorization", token) if err != nil { return result, err } + req.Header.Set("Authorization", token) result, _, err = do[int64](req) return } func (this *impl) CheckUserOrGroup(token string, kind string, resource string, rights string) (err error) { req, err := http.NewRequest(http.MethodHead, this.baseUrl+"/v3/resources/"+url.PathEscape(kind)+"/"+url.PathEscape(resource)+"?rights="+rights, nil) - req.Header.Set("Authorization", token) if err != nil { return err } + req.Header.Set("Authorization", token) _, err = head(req) return } func (this *impl) GetTermAggregation(token string, kind string, rights string, term string, limit int) (result []model.TermAggregationResultElement, err error) { req, err := http.NewRequest(http.MethodHead, this.baseUrl+"/v3/aggregates/term/"+url.PathEscape(kind)+"/"+url.PathEscape(term)+"?limit="+strconv.Itoa(limit)+"&rights="+rights, nil) - req.Header.Set("Authorization", token) if err != nil { return result, err } + req.Header.Set("Authorization", token) result, _, err = do[[]model.TermAggregationResultElement](req) return } + +func (this *impl) ExportKind(token string, kind string, limit int, offset int) (result []model.ResourceRights, err error) { + req, err := http.NewRequest(http.MethodGet, this.baseUrl+"/v3/export/"+url.PathEscape(kind)+"?limit="+strconv.Itoa(limit)+"&offset="+strconv.Itoa(offset), nil) + if err != nil { + return result, err + } + req.Header.Set("Authorization", token) + result, _, err = do[[]model.ResourceRights](req) + return +} diff --git a/lib/model/model.go b/lib/model/model.go index e08a18c..9339647 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -67,7 +67,7 @@ func (entry *Entry) AddGroupRights(group string, rights string) { case 'w': entry.WriteGroups = append(entry.WriteGroups, group) case 'x': - entry.ExecuteGroups = append(entry.AdminGroups, group) + entry.ExecuteGroups = append(entry.ExecuteGroups, group) } } } diff --git a/lib/query/migration.go b/lib/query/migration.go index 87fbd8e..b4fd2cf 100644 --- a/lib/query/migration.go +++ b/lib/query/migration.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "errors" + "github.com/SENERGY-Platform/permission-search/lib/auth" "github.com/SENERGY-Platform/permission-search/lib/model" "github.com/opensearch-project/opensearch-go/opensearchutil" ) @@ -55,10 +56,10 @@ func (this *Query) ImportResource(kind string, resource model.ResourceRights) (e return nil } -func (this *Query) Export() (exports map[string][]model.ResourceRights, err error) { +func (this *Query) Export(token string) (exports map[string][]model.ResourceRights, err error) { exports = map[string][]model.ResourceRights{} for kind := range this.config.Resources { - exports[kind], err = this.ExportKindAll(kind) + exports[kind], err = this.ExportKindAll(token, kind) if err != nil { return } @@ -66,12 +67,12 @@ func (this *Query) Export() (exports map[string][]model.ResourceRights, err erro return } -func (this *Query) ExportKindAll(kind string) (result []model.ResourceRights, err error) { +func (this *Query) ExportKindAll(token string, kind string) (result []model.ResourceRights, err error) { result = []model.ResourceRights{} limit := 100 offset := 0 for { - temp, err := this.ExportKind(kind, limit, offset) + temp, err := this.ExportKind(token, kind, limit, offset) if err != nil { return result, err } @@ -81,10 +82,16 @@ func (this *Query) ExportKindAll(kind string) (result []model.ResourceRights, er } offset = offset + limit } - return } -func (this *Query) ExportKind(kind string, limit int, offset int) (result []model.ResourceRights, err error) { +func (this *Query) ExportKind(tokenStr string, kind string, limit int, offset int) (result []model.ResourceRights, err error) { + token, err := auth.Parse(tokenStr) + if err != nil { + return nil, err + } + if !token.IsAdmin() { + return nil, errors.New("only admins may export") + } ctx := context.Background() query := map[string]interface{}{ "query": map[string]interface{}{ @@ -96,6 +103,7 @@ func (this *Query) ExportKind(kind string, limit int, offset int) (result []mode this.opensearchClient.Search.WithContext(ctx), this.opensearchClient.Search.WithSize(limit), this.opensearchClient.Search.WithFrom(offset), + this.opensearchClient.Search.WithSort("resource:asc"), this.opensearchClient.Search.WithBody(opensearchutil.NewJSONReader(query)), ) if err != nil {