diff --git a/changelog/unreleased/fix-enforce-password.md b/changelog/unreleased/fix-enforce-password.md new file mode 100644 index 00000000000..99daa0e12d8 --- /dev/null +++ b/changelog/unreleased/fix-enforce-password.md @@ -0,0 +1,6 @@ +Bugfix: fix enforce-password issue + +Fix updating public share without password when enforce-password is enabled + +https://github.com/cs3org/reva/pull/3970 +https://github.com/owncloud/ocis/issues/6476 diff --git a/changelog/unreleased/fix-panic.md b/changelog/unreleased/fix-panic.md index f9f0697955e..d8ac83ec14b 100644 --- a/changelog/unreleased/fix-panic.md +++ b/changelog/unreleased/fix-panic.md @@ -1,4 +1,3 @@ Bugfix: fix panic https://github.com/cs3org/reva/pull/3955 - diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 9e2cd9398e8..c290b984eaa 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -23,12 +23,14 @@ import ( "fmt" "net/http" "strconv" + "strings" permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/rs/zerolog/log" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions" @@ -36,7 +38,6 @@ import ( "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/publicshare" - "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/pkg/errors" ) @@ -127,7 +128,12 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, } } - permissions, err := ocPublicPermToCs3(permKey, h) + // default perms: read-only + // TODO: the default might change depending on allowed permissions and configs + if permKey == nil { + permKey = &_defaultPublicLinkPermission + } + permissions, err := ocPublicPermToCs3(permKey) if err != nil { return nil, &ocsError{ Code: response.MetaBadRequest.StatusCode, @@ -135,16 +141,13 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, Error: err, } } - if permissions == nil { - // default perms: read-only - // TODO: the default might change depending on allowed permissions and configs - permissions, err = ocPublicPermToCs3(&_defaultPublicLinkPermission, h) - if err != nil { - return nil, &ocsError{ - Code: response.MetaServerError.StatusCode, - Message: "Could not convert default permissions", - Error: err, - } + + password := strings.TrimSpace(r.FormValue("password")) + if h.enforcePassword(permKey) && len(password) == 0 { + return nil, &ocsError{ + Code: response.MetaBadRequest.StatusCode, + Message: "missing required password", + Error: errors.New("missing required password"), } } @@ -172,7 +175,7 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, Permissions: &link.PublicSharePermissions{ Permissions: permissions, }, - Password: r.FormValue("password"), + Password: password, }, } @@ -198,7 +201,7 @@ func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request, Metadata: map[string]string{ "name": r.FormValue("name"), "quicklink": r.FormValue("quicklink"), - // "password": r.FormValue("password"), + // "password": password, }, } @@ -275,9 +278,10 @@ func (h *Handler) listPublicShares(r *http.Request, filters []*link.ListPublicSh func (h *Handler) isPublicShare(r *http.Request, oid string) (*link.PublicShare, bool) { logger := appctx.GetLogger(r.Context()) - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { logger.Err(err) + return nil, false } psRes, err := client.GetPublicShare(r.Context(), &link.GetPublicShareRequest{ @@ -301,7 +305,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar updates := []*link.UpdatePublicShareRequest_Update{} logger := appctx.GetLogger(r.Context()) - gwC, err := pool.GetGatewayServiceClient(h.gatewayAddr) + gwC, err := h.getClient() if err != nil { log.Err(err).Str("shareID", share.GetId().GetOpaqueId()).Msg("updatePublicShare") response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "error getting a connection to the gateway service", nil) @@ -377,7 +381,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar } // Permissions - newPermissions, err := ocPublicPermToCs3(permKey, h) + newPermissions, err := ocPublicPermToCs3(permKey) logger.Debug().Interface("newPermissions", newPermissions).Msg("Parsed permissions") if err != nil { response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid permissions", err) @@ -447,6 +451,14 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar // Password newPassword, ok := r.Form["password"] + // enforcePassword + if h.enforcePassword(permKey) { + if (!ok && !share.PasswordProtected) || (ok && len(strings.TrimSpace(newPassword[0])) == 0) { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing required password", err) + return + } + } + // update or clear password if ok { updatesFound = true @@ -500,7 +512,7 @@ func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shar func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, share *link.PublicShare) { ctx := r.Context() - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + c, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return @@ -548,6 +560,39 @@ func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, shar response.WriteOCSSuccess(w, r, nil) } +// enforcePassword validate Password enforce based on configuration +// read_only: 1 +// read_write: 3 or 5 +// read_write_delete: 15 +// upload_only: 4 +func (h *Handler) enforcePassword(pk *int) bool { + if pk == nil { + return false + } + p, err := conversions.NewPermissions(decreasePermissionsIfNecessary(*pk)) + if err != nil { + return false + } + if h.publicPasswordEnforced.EnforcedForReadOnly && + p == conversions.PermissionRead { + return true + } + if h.publicPasswordEnforced.EnforcedForReadWrite && + (p == conversions.PermissionRead|conversions.PermissionWrite || + p == conversions.PermissionRead|conversions.PermissionCreate) { + return true + } + if h.publicPasswordEnforced.EnforcedForReadWriteDelete && + p == conversions.PermissionRead|conversions.PermissionWrite|conversions.PermissionCreate|conversions.PermissionDelete { + return true + } + if h.publicPasswordEnforced.EnforcedForUploadOnly && + p == conversions.PermissionCreate { + return true + } + return false +} + // for public links oc10 api decreases all permissions to read: stay compatible! func decreasePermissionsIfNecessary(perm int) int { if perm == int(conversions.PermissionAll) { @@ -556,7 +601,7 @@ func decreasePermissionsIfNecessary(perm int) int { return perm } -func ocPublicPermToCs3(pk *int, h *Handler) (*provider.ResourcePermissions, error) { +func ocPublicPermToCs3(pk *int) (*provider.ResourcePermissions, error) { if pk == nil { return nil, nil } diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public_test.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public_test.go new file mode 100644 index 00000000000..f90414c9db4 --- /dev/null +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public_test.go @@ -0,0 +1,103 @@ +package shares + +import ( + "testing" +) + +func TestHandler_enforcePassword(t *testing.T) { + tests := []struct { + name string + h *Handler + permKey int + exp bool + }{ + { + name: "enforce permission 1", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForReadOnly: true, + }, + }, + permKey: 1, + exp: true, + }, + { + name: "enforce permission 3", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForReadWrite: true, + }, + }, + permKey: 3, + exp: true, + }, + { + name: "enforce permission 4", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForUploadOnly: true, + }, + }, + permKey: 4, + exp: true, + }, + { + name: "enforce permission 5", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForReadWrite: true, + }, + }, + permKey: 5, + exp: true, + }, + { + name: "enforce permission 15", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForReadWriteDelete: true, + }, + }, + permKey: 15, + exp: true, + }, + { + name: "enforce permission 3 failed", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForReadOnly: true, + }, + }, + permKey: 3, + exp: false, + }, + { + name: "enforce permission 8 failed", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForReadWriteDelete: true, + EnforcedForUploadOnly: true, + }, + }, + permKey: 8, + exp: false, + }, + { + name: "enforce permission 11 failed", + h: &Handler{ + publicPasswordEnforced: passwordEnforced{ + EnforcedForReadWriteDelete: true, + }, + }, + permKey: 11, + exp: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.h.enforcePassword(&tt.permKey) != tt.exp { + t.Errorf("enforcePassword(\"%v\") returned %v instead of expected %v", tt.permKey, tt.h.enforcePassword(&tt.permKey), tt.exp) + } + }) + } +} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 97a55a9b8d6..7c675f9bd41 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -86,6 +86,7 @@ type Handler struct { statCache cache.StatCache deniable bool resharing bool + publicPasswordEnforced passwordEnforced getClient GatewayClientGetter } @@ -103,6 +104,13 @@ type ocsError struct { Message string } +type passwordEnforced struct { + EnforcedForReadOnly bool + EnforcedForReadWrite bool + EnforcedForReadWriteDelete bool + EnforcedForUploadOnly bool +} + func getCacheWarmupManager(c *config.Config) (sharecache.Warmup, error) { if f, ok := warmupreg.NewFuncs[c.CacheWarmupDriver]; ok { return f(c.CacheWarmupDrivers[c.CacheWarmupDriver]) @@ -129,6 +137,7 @@ func (h *Handler) Init(c *config.Config) { _ = h.userIdentifierCache.SetTTL(time.Second * time.Duration(c.UserIdentifierCacheTTL)) h.deniable = c.EnableDenials h.resharing = resharing(c) + h.publicPasswordEnforced = publicPwdEnforced(c) h.statCache = cache.GetStatCache(c.StatCacheStore, c.StatCacheNodes, c.StatCacheDatabase, "stat", time.Duration(c.StatCacheTTL)*time.Second, c.StatCacheSize) if c.CacheWarmupDriver != "" { @@ -503,7 +512,7 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sublog := appctx.GetLogger(ctx).With().Str("shareID", shareID).Logger() - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return @@ -695,7 +704,7 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, share *col ctx := r.Context() sublog := appctx.GetLogger(ctx).With().Str("shareID", share.GetId().GetOpaqueId()).Logger() - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return @@ -1110,7 +1119,7 @@ func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, ref *provid ctx := r.Context() // first check if the file exists - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) return nil, nil, err @@ -1159,7 +1168,7 @@ func (h *Handler) addFileInfo(ctx context.Context, s *conversions.ShareData, inf switch { case h.sharePrefix == "/": s.FileTarget = info.Path - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err == nil { gpRes, err := client.GetPath(ctx, &provider.GetPathRequest{ ResourceId: info.Id, @@ -1538,6 +1547,23 @@ func (h *Handler) granteeExists(ctx context.Context, g *provider.Grantee, rid *p return false, nil } +func publicPwdEnforced(c *config.Config) passwordEnforced { + enf := passwordEnforced{} + if c == nil || + c.Capabilities.Capabilities == nil || + c.Capabilities.Capabilities.FilesSharing == nil || + c.Capabilities.Capabilities.FilesSharing.Public == nil || + c.Capabilities.Capabilities.FilesSharing.Public.Password == nil || + c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor == nil { + return enf + } + enf.EnforcedForReadOnly = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadOnly) + enf.EnforcedForReadWrite = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadWrite) + enf.EnforcedForReadWriteDelete = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadWriteDelete) + enf.EnforcedForUploadOnly = bool(c.Capabilities.Capabilities.FilesSharing.Public.Password.EnforcedFor.UploadOnly) + return enf +} + // sufficientPermissions returns true if the `existing` permissions contain the `requested` permissions func sufficientPermissions(existing, requested *provider.ResourcePermissions, islink bool) bool { ep := conversions.RoleFromResourcePermissions(existing, islink).OCSPermissions() diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go index b4da547c73b..09cf66a23f3 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go @@ -35,12 +35,14 @@ import ( link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/config" + cdata "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/data" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" helpers "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/testhelpers" cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" + "github.com/go-chi/chi/v5" "github.com/stretchr/testify/mock" . "github.com/onsi/ginkgo/v2" @@ -413,6 +415,297 @@ var _ = Describe("The ocs API", func() { }) }) + Describe("UpdatePublicShare", func() { + BeforeEach(func() { + gatewayClient.On("GetUserByClaim", mock.Anything, mock.Anything).Return(&userpb.GetUserByClaimResponse{ + Status: status.NewOK(context.Background()), + User: user, + }, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&userpb.GetUserResponse{ + Status: status.NewOK(context.Background()), + User: user, + }, nil) + gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{ + Status: status.NewOK(context.Background()), + }, nil) + }) + + Context("Password Enforced when update a share", func() { + var ( + resID = &provider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "share1", + } + share2 = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "2"}, + ResourceId: resID, + Owner: user.Id, + Quicklink: true, + PasswordProtected: false, + } + + statResponse = &provider.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &provider.ResourceInfo{ + Type: provider.ResourceType_RESOURCE_TYPE_FILE, + Path: "/2", + Id: resID, + Owner: user.Id, + PermissionSet: &provider.ResourcePermissions{ + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListContainer: true, + ListRecycle: true, + RestoreRecycleItem: true, + Stat: true, + }, + Size: 10, + }, + } + ) + + BeforeEach(func() { + h = &shares.Handler{} + pool.RemoveSelector("GatewaySelector" + "any") + gatewayClient = &cs3mocks.GatewayAPIClient{} + + c := &config.Config{} + c.GatewaySvc = "gatewaysvc" + c.StatCacheDatabase = strconv.FormatInt(rand.Int63(), 10) // Use a fresh database for each test + // this is equivalent of the ocis OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD=true + c.Capabilities = cdata.CapabilitiesData{ + Capabilities: &cdata.Capabilities{FilesSharing: &cdata.CapabilitiesFilesSharing{Public: &cdata.CapabilitiesFilesSharingPublic{ + Password: &cdata.CapabilitiesFilesSharingPublicPassword{ + EnforcedFor: &cdata.CapabilitiesFilesSharingPublicPasswordEnforcedFor{ + ReadOnly: false, + ReadWrite: true, + ReadWriteDelete: true, + UploadOnly: true, + }, + }}}}, + } + c.Init() + h.InitWithGetter(c, func() (gateway.GatewayAPIClient, error) { + return gatewayClient, nil + }) + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(&link.GetPublicShareResponse{ + Status: status.NewOK(context.Background()), + Share: share2, + }, nil) + + gatewayClient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Return(&permissions.CheckPermissionResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_OK}, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + + gatewayClient.On("UpdatePublicShare", mock.Anything, mock.Anything).Return(&link.UpdatePublicShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_OK}, + Share: share2, + }, nil) + + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&userpb.GetUserResponse{ + Status: status.NewOK(context.Background()), + User: user, + }, nil) + }) + + Context("when change the permission to 3", func() { + + It("the password exists. update succeed", func() { + form := url.Values{} + form.Add("permissions", "3") + form.Add("password", "passwass") + req := httptest.NewRequest("PUT", "/ocs/v1.php/apps/files_sharing/api/v1/shares", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rctx := chi.NewRouteContext() + rctx.URLParams.Add("shareid", "2") + req = req.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + h.UpdateShare(w, req) + Expect(w.Result().StatusCode).To(Equal(200)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "UpdatePublicShare", 2) + }) + + It("the password doesn't exist. update failed", func() { + form := url.Values{} + form.Add("permissions", "3") + req := httptest.NewRequest("PUT", "/ocs/v1.php/apps/files_sharing/api/v1/shares", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rctx := chi.NewRouteContext() + rctx.URLParams.Add("shareid", "2") + req = req.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + h.UpdateShare(w, req) + Expect(w.Result().StatusCode).To(Equal(400)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "UpdatePublicShare", 0) + }) + + It("permissions=1, the password doesn't exist. update succeed", func() { + form := url.Values{} + form.Add("permissions", "1") + req := httptest.NewRequest("PUT", "/ocs/v1.php/apps/files_sharing/api/v1/shares", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rctx := chi.NewRouteContext() + rctx.URLParams.Add("shareid", "2") + req = req.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + h.UpdateShare(w, req) + Expect(w.Result().StatusCode).To(Equal(200)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "UpdatePublicShare", 1) + }) + }) + }) + + Context("Password Enforced when update a share", func() { + var ( + resID = &provider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "share1", + } + share2 = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "3"}, + ResourceId: resID, + Owner: user.Id, + Quicklink: true, + PasswordProtected: false, + } + + statResponse = &provider.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &provider.ResourceInfo{ + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: "/3", + Id: resID, + Owner: user.Id, + PermissionSet: &provider.ResourcePermissions{ + AddGrant: true, + CreateContainer: true, + Delete: true, + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + InitiateFileUpload: true, + ListGrants: true, + ListContainer: true, + ListRecycle: true, + Move: true, + RemoveGrant: true, + PurgeRecycle: true, + RestoreFileVersion: true, + RestoreRecycleItem: true, + Stat: true, + UpdateGrant: true, + DenyGrant: true, + }, + }, + } + ) + + BeforeEach(func() { + h = &shares.Handler{} + pool.RemoveSelector("GatewaySelector" + "any") + gatewayClient = &cs3mocks.GatewayAPIClient{} + + c := &config.Config{} + c.GatewaySvc = "gatewaysvc" + c.StatCacheDatabase = strconv.FormatInt(rand.Int63(), 10) // Use a fresh database for each test + // this is equivalent of the ocis OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD=true + c.Capabilities = cdata.CapabilitiesData{ + Capabilities: &cdata.Capabilities{FilesSharing: &cdata.CapabilitiesFilesSharing{Public: &cdata.CapabilitiesFilesSharingPublic{ + Password: &cdata.CapabilitiesFilesSharingPublicPassword{ + EnforcedFor: &cdata.CapabilitiesFilesSharingPublicPasswordEnforcedFor{ + ReadOnly: false, + ReadWrite: true, + ReadWriteDelete: true, + UploadOnly: true, + }, + }}}}, + } + c.Init() + h.InitWithGetter(c, func() (gateway.GatewayAPIClient, error) { + return gatewayClient, nil + }) + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(&link.GetPublicShareResponse{ + Status: status.NewOK(context.Background()), + Share: share2, + }, nil) + + gatewayClient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Return(&permissions.CheckPermissionResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_OK}, + }, nil) + + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + + gatewayClient.On("UpdatePublicShare", mock.Anything, mock.Anything).Return(&link.UpdatePublicShareResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_OK}, + Share: share2, + }, nil) + + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&userpb.GetUserResponse{ + Status: status.NewOK(context.Background()), + User: user, + }, nil) + }) + + Context("when change the permission", func() { + for _, perm := range []string{"4", "5", "15"} { + perm := perm + It("the password exists. update succeed", func() { + form := url.Values{} + form.Add("permissions", perm) + form.Add("password", "passwass") + req := httptest.NewRequest("PUT", "/ocs/v1.php/apps/files_sharing/api/v1/shares", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rctx := chi.NewRouteContext() + rctx.URLParams.Add("shareid", "3") + req = req.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + h.UpdateShare(w, req) + Expect(w.Result().StatusCode).To(Equal(200)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "UpdatePublicShare", 2) + }) + } + + It("the password doesn't exist. update failed", func() { + form := url.Values{} + form.Add("permissions", "3") + req := httptest.NewRequest("PUT", "/ocs/v1.php/apps/files_sharing/api/v1/shares", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rctx := chi.NewRouteContext() + rctx.URLParams.Add("shareid", "3") + req = req.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + h.UpdateShare(w, req) + Expect(w.Result().StatusCode).To(Equal(400)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "UpdatePublicShare", 0) + }) + + It("permissions=1, the password doesn't exist. update succeed", func() { + form := url.Values{} + form.Add("permissions", "1") + req := httptest.NewRequest("PUT", "/ocs/v1.php/apps/files_sharing/api/v1/shares", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rctx := chi.NewRouteContext() + rctx.URLParams.Add("shareid", "3") + req = req.WithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + h.UpdateShare(w, req) + Expect(w.Result().StatusCode).To(Equal(200)) + gatewayClient.AssertNumberOfCalls(GinkgoT(), "UpdatePublicShare", 1) + }) + }) + }) + }) + Describe("ListShares", func() { BeforeEach(func() { resID := &provider.ResourceId{ diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go index 90d34f561e9..7956918ff8d 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go @@ -44,7 +44,7 @@ import ( func (h *Handler) getGrantee(ctx context.Context, name string) (provider.Grantee, error) { log := appctx.GetLogger(ctx) - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { return provider.Grantee{}, err } @@ -96,7 +96,7 @@ func (h *Handler) addSpaceMember(w http.ResponseWriter, r *http.Request, info *p return } - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + client, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "error getting gateway client", err) return @@ -226,7 +226,7 @@ func (h *Handler) removeSpaceMember(w http.ResponseWriter, r *http.Request, spac return } - gatewayClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + gatewayClient, err := h.getClient() if err != nil { response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "error getting gateway client", err) return diff --git a/tests/oc-integration-tests/drone/frontend-global.toml b/tests/oc-integration-tests/drone/frontend-global.toml index 1d29cdf2335..85eb09a9046 100644 --- a/tests/oc-integration-tests/drone/frontend-global.toml +++ b/tests/oc-integration-tests/drone/frontend-global.toml @@ -72,12 +72,13 @@ multiple = true supports_upload_only = true [http.services.ocs.capabilities.capabilities.files_sharing.public.password] -enforced = true +enforced = false [http.services.ocs.capabilities.capabilities.files_sharing.public.password.enforced_for] -read_only = true -read_write = true -upload_only = true +read_only = false +read_write = false +read_write_delete = false +upload_only = false [http.services.ocs.capabilities.capabilities.files_sharing.public.expire_date] enabled = true @@ -111,4 +112,4 @@ prefix = "ocm" driver = "json" [http.middlewares.providerauthorizer.drivers.json] -providers = "providers.demo.json" \ No newline at end of file +providers = "providers.demo.json" diff --git a/tests/oc-integration-tests/drone/frontend.toml b/tests/oc-integration-tests/drone/frontend.toml index 3f4eac85dda..b06f28f95fe 100644 --- a/tests/oc-integration-tests/drone/frontend.toml +++ b/tests/oc-integration-tests/drone/frontend.toml @@ -74,12 +74,13 @@ multiple = true supports_upload_only = true [http.services.ocs.capabilities.capabilities.files_sharing.public.password] -enforced = true +enforced = false [http.services.ocs.capabilities.capabilities.files_sharing.public.password.enforced_for] -read_only = true -read_write = true -upload_only = true +read_only = false +read_write = false +read_write_delete = false +upload_only = false [http.services.ocs.capabilities.capabilities.files_sharing.public.expire_date] enabled = true @@ -113,4 +114,4 @@ prefix = "ocm" driver = "json" [http.middlewares.providerauthorizer.drivers.json] -providers = "providers.demo.json" \ No newline at end of file +providers = "providers.demo.json" diff --git a/tests/oc-integration-tests/local-mesh/frontend-global.toml b/tests/oc-integration-tests/local-mesh/frontend-global.toml index c9ef1454661..a98f7e31b24 100644 --- a/tests/oc-integration-tests/local-mesh/frontend-global.toml +++ b/tests/oc-integration-tests/local-mesh/frontend-global.toml @@ -72,12 +72,13 @@ multiple = true supports_upload_only = true [http.services.ocs.capabilities.capabilities.files_sharing.public.password] -enforced = true +enforced = false [http.services.ocs.capabilities.capabilities.files_sharing.public.password.enforced_for] -read_only = true -read_write = true -upload_only = true +read_only = false +read_write = false +read_write_delete = false +upload_only = false [http.services.ocs.capabilities.capabilities.files_sharing.public.expire_date] enabled = true diff --git a/tests/oc-integration-tests/local-mesh/frontend.toml b/tests/oc-integration-tests/local-mesh/frontend.toml index 0e23a75375a..14a7259bd43 100644 --- a/tests/oc-integration-tests/local-mesh/frontend.toml +++ b/tests/oc-integration-tests/local-mesh/frontend.toml @@ -74,12 +74,13 @@ multiple = true supports_upload_only = true [http.services.ocs.capabilities.capabilities.files_sharing.public.password] -enforced = true +enforced = false [http.services.ocs.capabilities.capabilities.files_sharing.public.password.enforced_for] -read_only = true -read_write = true -upload_only = true +read_only = false +read_write = false +read_write_delete = false +upload_only = false [http.services.ocs.capabilities.capabilities.files_sharing.public.expire_date] enabled = true @@ -113,4 +114,4 @@ prefix = "ocm" driver = "json" [http.middlewares.providerauthorizer.drivers.json] -providers = "providers.demo.json" \ No newline at end of file +providers = "providers.demo.json" diff --git a/tests/oc-integration-tests/local/combined.toml b/tests/oc-integration-tests/local/combined.toml index 331c4c149e4..1c500bd0bdd 100644 --- a/tests/oc-integration-tests/local/combined.toml +++ b/tests/oc-integration-tests/local/combined.toml @@ -153,12 +153,13 @@ multiple = true supports_upload_only = true [http.services.ocs.capabilities.capabilities.files_sharing.public.password] -enforced = true +enforced = false [http.services.ocs.capabilities.capabilities.files_sharing.public.password.enforced_for] -read_only = true -read_write = true -upload_only = true +read_only = false +read_write = false +read_write_delete = false +upload_only = false [http.services.ocs.capabilities.capabilities.files_sharing.public.expire_date] enabled = true diff --git a/tests/oc-integration-tests/local/frontend-global.toml b/tests/oc-integration-tests/local/frontend-global.toml index 9a9b9fb2fdc..2c8c1a254ca 100644 --- a/tests/oc-integration-tests/local/frontend-global.toml +++ b/tests/oc-integration-tests/local/frontend-global.toml @@ -79,12 +79,13 @@ multiple = true supports_upload_only = true [http.services.ocs.capabilities.capabilities.files_sharing.public.password] -enforced = true +enforced = false [http.services.ocs.capabilities.capabilities.files_sharing.public.password.enforced_for] -read_only = true -read_write = true -upload_only = true +read_only = false +read_write = false +read_write_delete = false +upload_only = false [http.services.ocs.capabilities.capabilities.files_sharing.public.expire_date] enabled = true diff --git a/tests/oc-integration-tests/local/frontend.toml b/tests/oc-integration-tests/local/frontend.toml index c410fa10dce..330c86addde 100644 --- a/tests/oc-integration-tests/local/frontend.toml +++ b/tests/oc-integration-tests/local/frontend.toml @@ -82,12 +82,13 @@ multiple = true supports_upload_only = true [http.services.ocs.capabilities.capabilities.files_sharing.public.password] -enforced = true +enforced = false [http.services.ocs.capabilities.capabilities.files_sharing.public.password.enforced_for] -read_only = true -read_write = true -upload_only = true +read_only = false +read_write = false +read_write_delete = false +upload_only = false [http.services.ocs.capabilities.capabilities.files_sharing.public.expire_date] enabled = true