diff --git a/go.mod b/go.mod index 4c9e88ed3d8..a144487aa5b 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( github.com/onsi/gomega v1.30.0 github.com/open-policy-agent/opa v0.59.0 github.com/orcaman/concurrent-map v1.0.0 - github.com/owncloud/libre-graph-api-go v1.0.5-0.20231128074031-fdcdb2371356 + github.com/owncloud/libre-graph-api-go v1.0.5-0.20231201125350-a08244876423 github.com/pkg/errors v0.9.1 github.com/pkg/xattr v0.4.9 github.com/prometheus/client_golang v1.17.0 diff --git a/go.sum b/go.sum index 93e3fa3ed34..1cc2921a217 100644 --- a/go.sum +++ b/go.sum @@ -1790,8 +1790,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY= github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= -github.com/owncloud/libre-graph-api-go v1.0.5-0.20231128074031-fdcdb2371356 h1:JjjpyUlD5nKF79QMpQ7/KVq41hh6f49GJNuxCYgMIMA= -github.com/owncloud/libre-graph-api-go v1.0.5-0.20231128074031-fdcdb2371356/go.mod h1:v2aAl5IwEI8t+GmcWvBd+bvJMYp9Vf1hekLuRf0UnEs= +github.com/owncloud/libre-graph-api-go v1.0.5-0.20231201125350-a08244876423 h1:G3i2n+lY6cTEerVEearRliEGeAxFuFQN0qM/1mdCQvs= +github.com/owncloud/libre-graph-api-go v1.0.5-0.20231201125350-a08244876423/go.mod h1:v2aAl5IwEI8t+GmcWvBd+bvJMYp9Vf1hekLuRf0UnEs= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/services/graph/pkg/errorcode/errorcode.go b/services/graph/pkg/errorcode/errorcode.go index fdaf94033ab..a94fa3de597 100644 --- a/services/graph/pkg/errorcode/errorcode.go +++ b/services/graph/pkg/errorcode/errorcode.go @@ -121,8 +121,9 @@ func (e Error) Render(w http.ResponseWriter, r *http.Request) { switch e.errorCode { case AccessDenied: status = http.StatusForbidden - case - InvalidRange: + case NotSupported: + status = http.StatusNotImplemented + case InvalidRange: status = http.StatusRequestedRangeNotSatisfiable case InvalidRequest: status = http.StatusBadRequest diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index 9cfcb12dbea..af029f0b373 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -25,13 +25,16 @@ import ( merrors "go-micro.dev/v4/errors" "golang.org/x/sync/errgroup" + revaConversions "github.com/cs3org/reva/v2/pkg/conversions" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/ocis-pkg/conversions" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" settingsServiceExt "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" ) @@ -57,19 +60,175 @@ var ( ErrForbiddenCharacter = fmt.Errorf("spacenames must not contain %v", _invalidSpaceNameCharacters) ) -// GetDrives lists all drives the current user has access to -func (g Graph) GetDrives(w http.ResponseWriter, r *http.Request) { - g.getDrives(w, r, false) +// GetDrives serves as a factory method that returns the appropriate +// http.Handler function based on the specified API version. +func (g Graph) GetDrives(version APIVersion) http.HandlerFunc { + switch version { + case APIVersion_1: + return g.GetDrivesV1 + case APIVersion_1_Beta_1: + return g.GetDrivesV1Beta1 + default: + return func(w http.ResponseWriter, r *http.Request) { + errorcode.New(errorcode.NotSupported, "api version not supported").Render(w, r) + } + } +} + +// GetDrivesV1 attempts to retrieve the current users drives; +// it lists all drives the current user has access to. +func (g Graph) GetDrivesV1(w http.ResponseWriter, r *http.Request) { + spaces, errCode := g.getDrives(r, false) + if errCode != nil { + errCode.Render(w, r) + return + } + + render.Status(r, http.StatusOK) + + switch { + case spaces == nil && errCode == nil: + render.JSON(w, r, nil) + default: + render.JSON(w, r, &ListResponse{Value: spaces}) + } + +} + +// GetDrivesV1Beta1 is the same as the GetDrivesV1 endpoint, expect: +// it includes the grantedtoV2 property +// it uses unified roles instead of the cs3 representations +func (g Graph) GetDrivesV1Beta1(w http.ResponseWriter, r *http.Request) { + spaces, errCode := g.getDrivesBeta(r, false) + if errCode != nil { + errCode.Render(w, r) + return + } + + render.Status(r, http.StatusOK) + + switch { + case spaces == nil && errCode == nil: + render.JSON(w, r, nil) + default: + render.JSON(w, r, &ListResponse{Value: spaces}) + } } -// GetAllDrives lists all drives, including other user's drives, if the current -// user has the permission. -func (g Graph) GetAllDrives(w http.ResponseWriter, r *http.Request) { - g.getDrives(w, r, true) +// GetAllDrives serves as a factory method that returns the appropriate +// http.Handler function based on the specified API version. +func (g Graph) GetAllDrives(version APIVersion) http.HandlerFunc { + switch version { + case APIVersion_1: + return g.GetAllDrivesV1 + case APIVersion_1_Beta_1: + return g.GetAllDrivesV1Beta1 + default: + return func(w http.ResponseWriter, r *http.Request) { + errorcode.New(errorcode.NotSupported, "api version not supported").Render(w, r) + } + } +} + +// GetAllDrivesV1 attempts to retrieve the current users drives; +// it includes another user's drives, if the current user has the permission. +func (g Graph) GetAllDrivesV1(w http.ResponseWriter, r *http.Request) { + spaces, errCode := g.getDrives(r, true) + if errCode != nil { + errCode.Render(w, r) + return + } + + render.Status(r, http.StatusOK) + + switch { + case spaces == nil && errCode == nil: + render.JSON(w, r, nil) + default: + render.JSON(w, r, &ListResponse{Value: spaces}) + } +} + +// GetAllDrivesV1Beta1 is the same as the GetAllDrivesV1 endpoint, expect: +// it includes the grantedtoV2 property +// it uses unified roles instead of the cs3 representations +func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) { + drives, errCode := g.getDrivesBeta(r, true) + if errCode != nil { + errCode.Render(w, r) + return + } + + render.Status(r, http.StatusOK) + + switch { + case drives == nil && errCode == nil: + render.JSON(w, r, nil) + default: + render.JSON(w, r, &ListResponse{Value: drives}) + } +} + +// getDrivesBeta retrieves the drives associated with the given request 'r'. +// It updates the 'GrantedToIdentities' to 'GrantedToV2', +// which represents the transition from legacy identity representation to a newer version. +// It also maps the old role names to their new unified role identifiers. +func (g Graph) getDrivesBeta(r *http.Request, unrestricted bool) ([]*libregraph.Drive, *errorcode.Error) { + drives, errCode := g.getDrives(r, unrestricted) + if errCode != nil { + return nil, errCode + } + + for _, drive := range drives { + for i, permission := range drive.GetRoot().Permissions { + grantedToIdentities := permission.GetGrantedToIdentities() + + if len(grantedToIdentities) < 1 { + continue + } + + permission.GrantedToIdentities = nil + grantedToIdentity := grantedToIdentities[0] + + permission.GrantedToV2 = &libregraph.SharePointIdentitySet{ + User: grantedToIdentity.User, + Group: grantedToIdentity.Group, + } + + for i, role := range permission.GetRoles() { + + // v1 implementation for getDrives > ** > cs3PermissionsToLibreGraph + // does not use space related role names, we first need to resolve the correct descriptor. + switch role { + case revaConversions.RoleViewer: + role = revaConversions.RoleSpaceViewer + case revaConversions.RoleEditor: + role = revaConversions.RoleSpaceEditor + } + + cs3Role := revaConversions.RoleFromName(role, g.config.FilesSharing.EnableResharing) + uniRole := unifiedrole.CS3ResourcePermissionsToUnifiedRole( + *cs3Role.CS3ResourcePermissions(), + unifiedrole.UnifiedRoleConditionOwner, + g.config.FilesSharing.EnableResharing, + ) + + if uniRole == nil { + continue + } + + permission.Roles[i] = uniRole.GetId() + } + + drive.Root.Permissions[i] = permission + } + } + + return drives, nil } // getDrives implements the Service interface. -func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bool) { +func (g Graph) getDrives(r *http.Request, unrestricted bool) ([]*libregraph.Drive, *errorcode.Error) { logger := g.logger.SubloggerWithRequestID(r.Context()) logger.Info(). Interface("query", r.URL.Query()). @@ -80,23 +239,20 @@ func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bo odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query()) if err != nil { logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: query error") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return + return nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, err.Error())) } ctx := r.Context() filters, err := generateCs3Filters(odataReq) if err != nil { logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: error parsing filters") - errorcode.NotSupported.Render(w, r, http.StatusNotImplemented, err.Error()) - return + return nil, conversions.ToPointer(errorcode.New(errorcode.NotSupported, err.Error())) } if !unrestricted { user, ok := revactx.ContextGetUser(r.Context()) if !ok { logger.Debug().Msg("could not create drive: invalid user") - errorcode.NotAllowed.Render(w, r, http.StatusUnauthorized, "invalid user") - return + return nil, conversions.ToPointer(errorcode.New(errorcode.AccessDenied, "invalid user")) } filters = append(filters, &storageprovider.ListStorageSpacesRequest_Filter{ Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_USER, @@ -114,43 +270,35 @@ func (g Graph) getDrives(w http.ResponseWriter, r *http.Request, unrestricted bo switch { case err != nil: logger.Error().Err(err).Msg("could not get drives: transport error") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return + return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error())) case res.Status.Code != cs3rpc.Code_CODE_OK: if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND { - // return an empty list - render.Status(r, http.StatusOK) - render.JSON(w, r, &ListResponse{}) - return + // ok, empty return + return nil, nil } logger.Debug().Str("message", res.GetStatus().GetMessage()).Msg("could not get drives: grpc error") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message) - return + return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, res.Status.Message)) } webDavBaseURL, err := g.getWebDavBaseURL() if err != nil { logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drives: error parsing url") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return + return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error())) } spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces) if err != nil { logger.Debug().Err(err).Msg("could not get drives: error parsing grpc response") - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return + return nil, conversions.ToPointer(errorcode.New(errorcode.GeneralException, err.Error())) } spaces, err = sortSpaces(odataReq, spaces) if err != nil { logger.Debug().Err(err).Msg("could not get drives: error sorting the spaces list according to query") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error()) - return + return nil, conversions.ToPointer(errorcode.New(errorcode.InvalidRequest, err.Error())) } - render.Status(r, http.StatusOK) - render.JSON(w, r, &ListResponse{Value: spaces}) + return spaces, nil } // GetSingleDrive does a lookup of a single space by spaceId diff --git a/services/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go index 4b5c60f541d..92edfcb2b51 100644 --- a/services/graph/pkg/service/v0/graph.go +++ b/services/graph/pkg/service/v0/graph.go @@ -10,9 +10,6 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/events" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/go-chi/chi/v5" "github.com/jellydator/ttlcache/v3" "go-micro.dev/v4/client" @@ -20,6 +17,10 @@ import ( "go.opentelemetry.io/otel/trace" "google.golang.org/protobuf/types/known/emptypb" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/owncloud/ocis/v2/ocis-pkg/keycloak" "github.com/owncloud/ocis/v2/ocis-pkg/log" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" @@ -123,6 +124,19 @@ const ( SpaceImageSpecialFolderName = "image" ) +type APIVersion int + +const ( + // APIVersion_1 represents the first version of the API. + APIVersion_1 APIVersion = iota + 1 + + // APIVersion_1_Beta_1 refers to the beta version of the API. + // It is typically used for testing purposes and may have more + // inconsistencies and bugs than the stable version as it is + // still in the testing phase, use it with caution. + APIVersion_1_Beta_1 +) + // TODO might be different for /education/users vs /users func (g Graph) parseMemberRef(ref string) (string, string, error) { memberURL, err := url.ParseRequestURI(ref) diff --git a/services/graph/pkg/service/v0/graph_test.go b/services/graph/pkg/service/v0/graph_test.go index 970dd35b66d..b82b6686d9c 100644 --- a/services/graph/pkg/service/v0/graph_test.go +++ b/services/graph/pkg/service/v0/graph_test.go @@ -14,15 +14,20 @@ import ( userprovider "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/go-chi/chi/v5" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/pkg/errors" + "github.com/stretchr/testify/mock" + "github.com/tidwall/gjson" + "google.golang.org/grpc" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" - "github.com/go-chi/chi/v5" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/shared" v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" @@ -31,9 +36,7 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/pkg/errors" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" + "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" ) var _ = Describe("Graph", func() { @@ -91,7 +94,7 @@ var _ = Describe("Graph", func() { }) Describe("Drives", func() { - Describe("List drives", func() { + Describe("GetDrivesV1 and GetAllDrivesV1", func() { It("can list an empty list of spaces", func() { gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), @@ -101,7 +104,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusOK)) }) @@ -114,7 +117,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetAllDrives(rr, r) + svc.GetAllDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusOK)) }) @@ -144,7 +147,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusOK)) @@ -218,7 +221,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?$orderby=name%20asc", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusOK)) @@ -309,7 +312,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusOK)) @@ -350,7 +353,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?$orderby=owner%20asc", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusBadRequest)) body, _ := io.ReadAll(rr.Body) @@ -363,7 +366,7 @@ var _ = Describe("Graph", func() { It("can list a spaces with invalid query parameter", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?§orderby=owner%20asc", nil) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusBadRequest)) body, _ := io.ReadAll(rr.Body) @@ -376,7 +379,7 @@ var _ = Describe("Graph", func() { It("can list a spaces with an unsupported operand", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives?$filter=contains(driveType,personal)", nil) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusNotImplemented)) body, _ := io.ReadAll(rr.Body) @@ -392,7 +395,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives)", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusInternalServerError)) body, _ := io.ReadAll(rr.Body) @@ -410,7 +413,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives)", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusInternalServerError)) body, _ := io.ReadAll(rr.Body) @@ -428,7 +431,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives)", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusOK)) body, _ := io.ReadAll(rr.Body) @@ -464,7 +467,7 @@ var _ = Describe("Graph", func() { r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil) r = r.WithContext(ctx) rr := httptest.NewRecorder() - svc.GetDrives(rr, r) + svc.GetDrivesV1(rr, r) Expect(rr.Code).To(Equal(http.StatusInternalServerError)) @@ -476,6 +479,58 @@ var _ = Describe("Graph", func() { Expect(libreError.Error.Code).To(Equal(errorcode.GeneralException.String())) }) }) + DescribeTable("GetDrivesV1Beta1 and GetAllDrivesV1Beta1", + func(check func(gjson.Result), resourcePermissions provider.ResourcePermissions) { + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Times(1).Return(&provider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*provider.StorageSpace{ + { + Opaque: utils.AppendJSONToOpaque(nil, "grants", map[string]provider.ResourcePermissions{ + "1": resourcePermissions, + }), + Root: &provider.ResourceId{}, + }, + }, + }, nil) + gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gateway.InitiateFileDownloadResponse{ + Status: status.NewNotFound(ctx, "not found"), + }, nil) + gatewayClient.On("GetQuota", mock.Anything, mock.Anything).Return(&provider.GetQuotaResponse{ + Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"), + }, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&userprovider.GetUserResponse{ + Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"), + }, nil) + + r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil) + r = r.WithContext(ctx) + rr := httptest.NewRecorder() + svc.GetDrivesV1Beta1(rr, r) + + Expect(rr.Code).To(Equal(http.StatusOK)) + + jsonData := gjson.Get(rr.Body.String(), "value") + + Expect(jsonData.Get("#").Num).To(Equal(float64(1))) + Expect(jsonData.Get("0.root.permissions.#").Num).To(Equal(float64(1))) + Expect(jsonData.Get("0.root.permissions.0.grantedToIdentities").Exists()).To(BeFalse()) + Expect(jsonData.Get("0.root.permissions.0.grantedToIdentities").Exists()).To(BeFalse()) + Expect(jsonData.Get("0.root.permissions.0.grantedToV2.user.id").Str).To(Equal("1")) + Expect(jsonData.Get("0.root.permissions.0.roles.#").Num).To(Equal(float64(1))) + + check(jsonData) + }, + Entry("injects grantedToV2", func(jsonData gjson.Result) {}, provider.ResourcePermissions{RemoveGrant: true}), + Entry("remaps manager role to the unified counterpart", func(jsonData gjson.Result) { + Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleManagerID)) + }, provider.ResourcePermissions{RemoveGrant: true}), + Entry("remaps editor role to the unified counterpart", func(jsonData gjson.Result) { + Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleSpaceEditorID)) + }, provider.ResourcePermissions{InitiateFileUpload: true}), + Entry("remaps viewer role to the unified counterpart", func(jsonData gjson.Result) { + Expect(jsonData.Get("0.root.permissions.0.roles.0").Str).To(Equal(unifiedrole.UnifiedRoleSpaceViewerID)) + }, provider.ResourcePermissions{Stat: true}), + ) Describe("Create Drive", func() { It("cannot create a space without valid user in context", func() { jsonBody := []byte(`{"Name": "Test Space"}`) diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index 7dc91060ceb..f2566e30ddd 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -94,9 +94,11 @@ type Service interface { PostEducationClassTeacher(w http.ResponseWriter, r *http.Request) DeleteEducationClassTeacher(w http.ResponseWriter, r *http.Request) - GetDrives(w http.ResponseWriter, r *http.Request) + GetDrivesV1(w http.ResponseWriter, r *http.Request) + GetDrivesV1Beta1(w http.ResponseWriter, r *http.Request) GetSingleDrive(w http.ResponseWriter, r *http.Request) - GetAllDrives(w http.ResponseWriter, r *http.Request) + GetAllDrivesV1(w http.ResponseWriter, r *http.Request) + GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) CreateDrive(w http.ResponseWriter, r *http.Request) UpdateDrive(w http.ResponseWriter, r *http.Request) DeleteDrive(w http.ResponseWriter, r *http.Request) @@ -201,14 +203,24 @@ func NewService(opts ...Option) (Graph, error) { m.Route(options.Config.HTTP.Root, func(r chi.Router) { r.Use(middleware.StripSlashes) r.Route("/v1beta1", func(r chi.Router) { - r.Get("/me/drive/sharedByMe", svc.GetSharedByMe) - r.Get("/me/drive/sharedWithMe", svc.ListSharedWithMe) - r.Route("/drives/{driveID}/items/{itemID}", func(r chi.Router) { - r.Post("/invite", svc.Invite) - r.Get("/permissions", svc.ListPermissions) - r.Delete("/permissions/{permissionID}", svc.DeletePermission) - r.Post("/createLink", svc.CreateLink) + r.Route("/me", func(r chi.Router) { + r.Get("/drives", svc.GetDrives(APIVersion_1_Beta_1)) + r.Route("/drive", func(r chi.Router) { + r.Get("/sharedByMe", svc.GetSharedByMe) + r.Get("/sharedWithMe", svc.ListSharedWithMe) + }) + }) + + r.Route("/drives", func(r chi.Router) { + r.Get("/", svc.GetAllDrives(APIVersion_1_Beta_1)) + r.Route("/{driveID}/items/{itemID}", func(r chi.Router) { + r.Post("/invite", svc.Invite) + r.Get("/permissions", svc.ListPermissions) + r.Delete("/permissions/{permissionID}", svc.DeletePermission) + r.Post("/createLink", svc.CreateLink) + }) }) + r.Route("/roleManagement/permissions/roleDefinitions", func(r chi.Router) { r.Get("/", svc.GetRoleDefinitions) r.Get("/{roleID}", svc.GetRoleDefinition) @@ -231,7 +243,7 @@ func NewService(opts ...Option) (Graph, error) { r.Get("/", svc.GetUserDrive) r.Get("/root/children", svc.GetRootDriveChildren) }) - r.Get("/drives", svc.GetDrives) + r.Get("/drives", svc.GetDrives(APIVersion_1)) r.Post("/changePassword", svc.ChangeOwnPassword) }) r.Route("/users", func(r chi.Router) { @@ -267,7 +279,7 @@ func NewService(opts ...Option) (Graph, error) { }) }) r.Route("/drives", func(r chi.Router) { - r.Get("/", svc.GetAllDrives) + r.Get("/", svc.GetAllDrives(APIVersion_1)) r.Post("/", svc.CreateDrive) r.Route("/{driveID}", func(r chi.Router) { r.Patch("/", svc.UpdateDrive) diff --git a/services/graph/pkg/unifiedrole/unifiedrole.go b/services/graph/pkg/unifiedrole/unifiedrole.go index a2d0948fb0b..bfc3e1ec833 100644 --- a/services/graph/pkg/unifiedrole/unifiedrole.go +++ b/services/graph/pkg/unifiedrole/unifiedrole.go @@ -6,9 +6,10 @@ import ( "slices" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v2/pkg/conversions" libregraph "github.com/owncloud/libre-graph-api-go" "google.golang.org/protobuf/proto" + + "github.com/cs3org/reva/v2/pkg/conversions" ) const ( @@ -188,6 +189,10 @@ func NewManagerUnifiedRole() *libregraph.UnifiedRoleDefinition { AllowedResourceActions: convert(r), Condition: proto.String(UnifiedRoleConditionGrantee), }, + { + AllowedResourceActions: convert(r), + Condition: proto.String(UnifiedRoleConditionOwner), + }, }, LibreGraphWeight: proto.Int32(0), } @@ -225,23 +230,27 @@ func GetApplicableRoleDefinitionsForActions(actions []string, constraints string var definitions []*libregraph.UnifiedRoleDefinition for _, definition := range GetBuiltinRoleDefinitionList(resharing) { - match := true + definitionMatch := true for _, permission := range definition.GetRolePermissions() { if permission.GetCondition() != constraints { - match = false - break + definitionMatch = false + continue } for _, action := range permission.GetAllowedResourceActions() { if !slices.Contains(actions, action) { - match = false + definitionMatch = false break } } + + if definitionMatch { + break + } } - if !match { + if !definitionMatch { continue } diff --git a/services/graph/pkg/unifiedrole/unifiedrole_test.go b/services/graph/pkg/unifiedrole/unifiedrole_test.go index 2d35901f374..f11225a5a5e 100644 --- a/services/graph/pkg/unifiedrole/unifiedrole_test.go +++ b/services/graph/pkg/unifiedrole/unifiedrole_test.go @@ -17,18 +17,21 @@ import ( var _ = Describe("unifiedroles", func() { DescribeTable("CS3ResourcePermissionsToUnifiedRole", - func(legacyRole *rConversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition) { + func(legacyRole *rConversions.Role, unifiedRole *libregraph.UnifiedRoleDefinition, constraints string) { cs3perm := legacyRole.CS3ResourcePermissions() - r := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3perm, unifiedrole.UnifiedRoleConditionGrantee, true) + r := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3perm, constraints, true) Expect(r.GetId()).To(Equal(unifiedRole.GetId())) }, - Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true)), - Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true)), - Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true)), - Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole()), - Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole()), + Entry(rConversions.RoleViewer, rConversions.NewViewerRole(true), unifiedrole.NewViewerUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee), + Entry(rConversions.RoleEditor, rConversions.NewEditorRole(true), unifiedrole.NewEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee), + Entry(rConversions.RoleFileEditor, rConversions.NewFileEditorRole(true), unifiedrole.NewFileEditorUnifiedRole(true), unifiedrole.UnifiedRoleConditionGrantee), + Entry(rConversions.RoleCoowner, rConversions.NewCoownerRole(), unifiedrole.NewCoownerUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee), + Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionGrantee), + Entry(rConversions.RoleManager, rConversions.NewManagerRole(), unifiedrole.NewManagerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner), + Entry(rConversions.RoleSpaceViewer, rConversions.NewSpaceViewerRole(), unifiedrole.NewSpaceViewerUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner), + Entry(rConversions.RoleSpaceEditor, rConversions.NewSpaceEditorRole(), unifiedrole.NewSpaceEditorUnifiedRole(), unifiedrole.UnifiedRoleConditionOwner), ) DescribeTable("UnifiedRolePermissionsToCS3ResourcePermissions", diff --git a/vendor/github.com/owncloud/libre-graph-api-go/README.md b/vendor/github.com/owncloud/libre-graph-api-go/README.md index 0aa654ff92c..322d7a46589 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/README.md +++ b/vendor/github.com/owncloud/libre-graph-api-go/README.md @@ -84,6 +84,7 @@ Class | Method | HTTP request | Description *DrivesApi* | [**GetDrive**](docs/DrivesApi.md#getdrive) | **Get** /v1.0/drives/{drive-id} | Get drive by id *DrivesApi* | [**UpdateDrive**](docs/DrivesApi.md#updatedrive) | **Patch** /v1.0/drives/{drive-id} | Update the drive *DrivesGetDrivesApi* | [**ListAllDrives**](docs/DrivesGetDrivesApi.md#listalldrives) | **Get** /v1.0/drives | Get all available drives +*DrivesGetDrivesApi* | [**ListAllDrivesBeta**](docs/DrivesGetDrivesApi.md#listalldrivesbeta) | **Get** /v1beta1/drives | Alias for '/v1.0/drives', the difference is that grantedtoV2 is used and roles contain unified roles instead of cs3 roles *DrivesPermissionsApi* | [**CreateLink**](docs/DrivesPermissionsApi.md#createlink) | **Post** /v1beta1/drives/{drive-id}/items/{item-id}/createLink | Create a sharing link for a DriveItem *DrivesPermissionsApi* | [**DeletePermission**](docs/DrivesPermissionsApi.md#deletepermission) | **Delete** /v1beta1/drives/{drive-id}/items/{item-id}/permissions/{perm-id} | Remove access to a DriveItem *DrivesPermissionsApi* | [**GetPermission**](docs/DrivesPermissionsApi.md#getpermission) | **Get** /v1beta1/drives/{drive-id}/items/{item-id}/permissions/{perm-id} | Get sharing permission for a file or folder @@ -134,6 +135,7 @@ Class | Method | HTTP request | Description *MeDriveRootApi* | [**HomeGetRoot**](docs/MeDriveRootApi.md#homegetroot) | **Get** /v1.0/me/drive/root | Get root from personal space *MeDriveRootChildrenApi* | [**HomeGetChildren**](docs/MeDriveRootChildrenApi.md#homegetchildren) | **Get** /v1.0/me/drive/root/children | Get children from drive *MeDrivesApi* | [**ListMyDrives**](docs/MeDrivesApi.md#listmydrives) | **Get** /v1.0/me/drives | Get all drives where the current user is a regular member of +*MeDrivesApi* | [**ListMyDrivesBeta**](docs/MeDrivesApi.md#listmydrivesbeta) | **Get** /v1beta1/me/drives | Alias for '/v1.0/drives', the difference is that grantedtoV2 is used and roles contain unified roles instead of cs3 roles *MeUserApi* | [**GetOwnUser**](docs/MeUserApi.md#getownuser) | **Get** /v1.0/me | Get current user *MeUserApi* | [**UpdateOwnUser**](docs/MeUserApi.md#updateownuser) | **Patch** /v1.0/me | Update the current user *RoleManagementApi* | [**GetPermissionRoleDefinition**](docs/RoleManagementApi.md#getpermissionroledefinition) | **Get** /v1beta1/roleManagement/permissions/roleDefinitions/{role-id} | Get unifiedRoleDefinition diff --git a/vendor/github.com/owncloud/libre-graph-api-go/api_drives_get_drives.go b/vendor/github.com/owncloud/libre-graph-api-go/api_drives_get_drives.go index 47edf8580c2..084bb2eb0f9 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/api_drives_get_drives.go +++ b/vendor/github.com/owncloud/libre-graph-api-go/api_drives_get_drives.go @@ -145,3 +145,128 @@ func (a *DrivesGetDrivesApiService) ListAllDrivesExecute(r ApiListAllDrivesReque return localVarReturnValue, localVarHTTPResponse, nil } + +type ApiListAllDrivesBetaRequest struct { + ctx context.Context + ApiService *DrivesGetDrivesApiService + orderby *string + filter *string +} + +// The $orderby system query option allows clients to request resources in either ascending order using asc or descending order using desc. +func (r ApiListAllDrivesBetaRequest) Orderby(orderby string) ApiListAllDrivesBetaRequest { + r.orderby = &orderby + return r +} + +// Filter items by property values +func (r ApiListAllDrivesBetaRequest) Filter(filter string) ApiListAllDrivesBetaRequest { + r.filter = &filter + return r +} + +func (r ApiListAllDrivesBetaRequest) Execute() (*CollectionOfDrives1, *http.Response, error) { + return r.ApiService.ListAllDrivesBetaExecute(r) +} + +/* +ListAllDrivesBeta Alias for '/v1.0/drives', the difference is that grantedtoV2 is used and roles contain unified roles instead of cs3 roles + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return ApiListAllDrivesBetaRequest +*/ +func (a *DrivesGetDrivesApiService) ListAllDrivesBeta(ctx context.Context) ApiListAllDrivesBetaRequest { + return ApiListAllDrivesBetaRequest{ + ApiService: a, + ctx: ctx, + } +} + +// Execute executes the request +// @return CollectionOfDrives1 +func (a *DrivesGetDrivesApiService) ListAllDrivesBetaExecute(r ApiListAllDrivesBetaRequest) (*CollectionOfDrives1, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *CollectionOfDrives1 + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DrivesGetDrivesApiService.ListAllDrivesBeta") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/v1beta1/drives" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + if r.orderby != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$orderby", r.orderby, "") + } + if r.filter != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$filter", r.filter, "") + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + var v OdataError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/vendor/github.com/owncloud/libre-graph-api-go/api_me_drives.go b/vendor/github.com/owncloud/libre-graph-api-go/api_me_drives.go index adedf10d014..28cfec661c1 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/api_me_drives.go +++ b/vendor/github.com/owncloud/libre-graph-api-go/api_me_drives.go @@ -145,3 +145,128 @@ func (a *MeDrivesApiService) ListMyDrivesExecute(r ApiListMyDrivesRequest) (*Col return localVarReturnValue, localVarHTTPResponse, nil } + +type ApiListMyDrivesBetaRequest struct { + ctx context.Context + ApiService *MeDrivesApiService + orderby *string + filter *string +} + +// The $orderby system query option allows clients to request resources in either ascending order using asc or descending order using desc. +func (r ApiListMyDrivesBetaRequest) Orderby(orderby string) ApiListMyDrivesBetaRequest { + r.orderby = &orderby + return r +} + +// Filter items by property values +func (r ApiListMyDrivesBetaRequest) Filter(filter string) ApiListMyDrivesBetaRequest { + r.filter = &filter + return r +} + +func (r ApiListMyDrivesBetaRequest) Execute() (*CollectionOfDrives, *http.Response, error) { + return r.ApiService.ListMyDrivesBetaExecute(r) +} + +/* +ListMyDrivesBeta Alias for '/v1.0/drives', the difference is that grantedtoV2 is used and roles contain unified roles instead of cs3 roles + + @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + @return ApiListMyDrivesBetaRequest +*/ +func (a *MeDrivesApiService) ListMyDrivesBeta(ctx context.Context) ApiListMyDrivesBetaRequest { + return ApiListMyDrivesBetaRequest{ + ApiService: a, + ctx: ctx, + } +} + +// Execute executes the request +// @return CollectionOfDrives +func (a *MeDrivesApiService) ListMyDrivesBetaExecute(r ApiListMyDrivesBetaRequest) (*CollectionOfDrives, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + formFiles []formFile + localVarReturnValue *CollectionOfDrives + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MeDrivesApiService.ListMyDrivesBeta") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/v1beta1/me/drives" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + if r.orderby != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$orderby", r.orderby, "") + } + if r.filter != nil { + parameterAddToHeaderOrQuery(localVarQueryParams, "$filter", r.filter, "") + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + var v OdataError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/vendor/github.com/owncloud/libre-graph-api-go/model_unified_role_permission.go b/vendor/github.com/owncloud/libre-graph-api-go/model_unified_role_permission.go index 64f46ca439d..0d0659db52e 100644 --- a/vendor/github.com/owncloud/libre-graph-api-go/model_unified_role_permission.go +++ b/vendor/github.com/owncloud/libre-graph-api-go/model_unified_role_permission.go @@ -19,7 +19,7 @@ var _ MappedNullable = &UnifiedRolePermission{} // UnifiedRolePermission Represents a collection of allowed resource actions and the conditions that must be met for the action to be allowed. Resource actions are tasks that can be performed on a resource. For example, an application resource may support create, update, delete, and reset password actions. type UnifiedRolePermission struct { - // Set of tasks that can be performed on a resource. Required. The following is the schema for resource actions: ``` {Namespace}/{Entity}/{PropertySet}/{Action} ``` For example: `libre.graph/applications/credentials/update` * *{Namespace}* - The services that exposes the task. For example, all tasks in libre graph use the namespace `libre.graph`. * *{Entity}* - The logical features or components exposed by the service in libre graph. For example, `applications`, `servicePrincipals`, or `groups`. * *{PropertySet}* - Optional. The specific properties or aspects of the entity for which access is being granted. For example, `libre.graph/applications/authentication/read` grants the ability to read the reply URL, logout URL, and implicit flow property on the **application** object in libre graph. The following are reserved names for common property sets: * `allProperties` - Designates all properties of the entity, including privileged properties. Examples include `libre.graph/applications/allProperties/read` and `libre.graph/applications/allProperties/update`. * `basic` - Designates common read properties but excludes privileged ones. For example, `libre.graph/applications/basic/update` includes the ability to update standard properties like display name. * `standard` - Designates common update properties but excludes privileged ones. For example, `libre.graph/applications/standard/read`. * *{Actions}* - The operations being granted. In most circumstances, permissions should be expressed in terms of CRUD operations or allTasks. Actions include: * `create` - The ability to create a new instance of the entity. * `read` - The ability to read a given property set (including allProperties). * `update` - The ability to update a given property set (including allProperties). * `delete` - The ability to delete a given entity. * `allTasks` - Represents all CRUD operations (create, read, update, and delete). Following the CS3 API we can represent the CS3 permissions by mapping them to driveItem properties or relations like this: | [CS3 ResourcePermission](https://cs3org.github.io/cs3apis/#cs3.storage.provider.v1beta1.ResourcePermissions) | action | comment | | ------------------------------------------------------------------------------------------------------------ | ------ | ------- | | `stat` | `libre.graph/driveItem/basic/read` | `basic` because it does not include versions or trashed items | | `get_quota` | `libre.graph/driveItem/quota/read` | read only the `quota` property | | `get_path` | `libre.graph/driveItem/path/read` | read only the `path` property | | `move` | `libre.graph/driveItem/path/update` | allows updating the `path` property of a CS3 resource | | `delete` | `libre.graph/driveItem/standard/delete` | `standard` because deleting is a common update operation | | `list_container` | `libre.graph/driveItem/children/read` | | | `create_container` | `libre.graph/driveItem/children/create` | | | `initiate_file_download` | `libre.graph/driveItem/content/read` | `content` is the property read when initiating a download | | `initiate_file_upload` | `libre.graph/driveItem/upload/create` | `uploads` are a separate property. postprocessing creates the `content` | | `add_grant` | `libre.graph/driveItem/permissions/create` | | | `list_grant` | `libre.graph/driveItem/permissions/read` | | | `update_grant` | `libre.graph/driveItem/permissions/update` | | | `remove_grant` | `libre.graph/driveItem/permissions/delete` | | | `deny_grant` | `libre.graph/driveItem/permissions/deny` | uses a non CRUD action `deny` | | `list_file_versions` | `libre.graph/driveItem/versions/read` | `versions` is a `driveItemVersion` collection | | `restore_file_version` | `libre.graph/driveItem/versions/update` | the only `update` action is restore | | `list_recycle` | `libre.graph/driveItem/deleted/read` | reading a driveItem `deleted` property implies listing | | `restore_recycle_item` | `libre.graph/driveItem/deleted/update` | the only `update` action is restore | | `purge_recycle` | `libre.graph/driveItem/deleted/delete` | allows purging deleted `driveItems` | Managing drives would be a different entity. A space manager role could be written as `libre.graph/drive/permission/allTasks`. + // Set of tasks that can be performed on a resource. Required. The following is the schema for resource actions: ``` {Namespace}/{Entity}/{PropertySet}/{Action} ``` For example: `libre.graph/applications/credentials/update` * *{Namespace}* - The services that exposes the task. For example, all tasks in libre graph use the namespace `libre.graph`. * *{Entity}* - The logical features or components exposed by the service in libre graph. For example, `applications`, `servicePrincipals`, or `groups`. * *{PropertySet}* - Optional. The specific properties or aspects of the entity for which access is being granted. For example, `libre.graph/applications/authentication/read` grants the ability to read the reply URL, logout URL, and implicit flow property on the **application** object in libre graph. The following are reserved names for common property sets: * `allProperties` - Designates all properties of the entity, including privileged properties. Examples include `libre.graph/applications/allProperties/read` and `libre.graph/applications/allProperties/update`. * `basic` - Designates common read properties but excludes privileged ones. For example, `libre.graph/applications/basic/update` includes the ability to update standard properties like display name. * `standard` - Designates common update properties but excludes privileged ones. For example, `libre.graph/applications/standard/read`. * *{Actions}* - The operations being granted. In most circumstances, permissions should be expressed in terms of CRUD operations or allTasks. Actions include: * `create` - The ability to create a new instance of the entity. * `read` - The ability to read a given property set (including allProperties). * `update` - The ability to update a given property set (including allProperties). * `delete` - The ability to delete a given entity. * `allTasks` - Represents all CRUD operations (create, read, update, and delete). Following the CS3 API we can represent the CS3 permissions by mapping them to driveItem properties or relations like this: | [CS3 ResourcePermission](https://cs3org.github.io/cs3apis/#cs3.storage.provider.v1beta1.ResourcePermissions) | action | comment | | ------------------------------------------------------------------------------------------------------------ | ------ | ------- | | `stat` | `libre.graph/driveItem/basic/read` | `basic` because it does not include versions or trashed items | | `get_quota` | `libre.graph/driveItem/quota/read` | read only the `quota` property | | `get_path` | `libre.graph/driveItem/path/read` | read only the `path` property | | `move` | `libre.graph/driveItem/path/update` | allows updating the `path` property of a CS3 resource | | `delete` | `libre.graph/driveItem/standard/delete` | `standard` because deleting is a common update operation | | `list_container` | `libre.graph/driveItem/children/read` | | | `create_container` | `libre.graph/driveItem/children/create` | | | `initiate_file_download` | `libre.graph/driveItem/content/read` | `content` is the property read when initiating a download | | `initiate_file_upload` | `libre.graph/driveItem/upload/create` | `uploads` are a separate property. postprocessing creates the `content` | | `add_grant` | `libre.graph/driveItem/permissions/create` | | | `list_grant` | `libre.graph/driveItem/permissions/read` | | | `update_grant` | `libre.graph/driveItem/permissions/update` | | | `remove_grant` | `libre.graph/driveItem/permissions/delete` | | | `deny_grant` | `libre.graph/driveItem/permissions/deny` | uses a non CRUD action `deny` | | `list_file_versions` | `libre.graph/driveItem/versions/read` | `versions` is a `driveItemVersion` collection | | `restore_file_version` | `libre.graph/driveItem/versions/update` | the only `update` action is restore | | `list_recycle` | `libre.graph/driveItem/deleted/read` | reading a driveItem `deleted` property implies listing | | `restore_recycle_item` | `libre.graph/driveItem/deleted/update` | the only `update` action is restore | | `purge_recycle` | `libre.graph/driveItem/deleted/delete` | allows purging deleted `driveItems` | Managing drives would be a different entity. A space manager role could be written as `libre.graph/drive/permission/allTasks`. AllowedResourceActions []string `json:"allowedResourceActions,omitempty"` // Optional constraints that must be met for the permission to be effective. Not supported for custom roles. Conditions define constraints that must be met. For example, a requirement that the principal be an owner of the target resource. The following are the supported conditions: * Self: `@Subject.objectId == @Resource.objectId` * Owner: `@Subject.objectId Any_of @Resource.owners` * Grantee: `@Subject.objectId Any_of @Resource.grantee` - does not exist in MS Graph, but we use it to express permissions on shared resources. The following is an example of a role permission with a condition that the principal be the owner of the target resource. ```json \"rolePermissions\": [ { \"allowedResourceActions\": [ \"libre.graph/applications/basic/update\", \"libre.graph/applications/credentials/update\" ], \"condition\": \"@Subject.objectId Any_of @Resource.owners\" } ] ``` Conditions aren't supported for custom roles. Condition *string `json:"condition,omitempty"` diff --git a/vendor/modules.txt b/vendor/modules.txt index 65033e5b611..ec3026fb60f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1575,7 +1575,7 @@ github.com/opentracing/opentracing-go/log # github.com/orcaman/concurrent-map v1.0.0 ## explicit github.com/orcaman/concurrent-map -# github.com/owncloud/libre-graph-api-go v1.0.5-0.20231128074031-fdcdb2371356 +# github.com/owncloud/libre-graph-api-go v1.0.5-0.20231201125350-a08244876423 ## explicit; go 1.18 github.com/owncloud/libre-graph-api-go # github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c