github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/sharings/replicator.go (about)

     1  package sharings
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"io"
     7  	"net/http"
     8  
     9  	"github.com/cozy/cozy-stack/model/sharing"
    10  	"github.com/cozy/cozy-stack/model/vfs"
    11  	"github.com/cozy/cozy-stack/pkg/consts"
    12  	"github.com/cozy/cozy-stack/pkg/jsonapi"
    13  	"github.com/cozy/cozy-stack/web/middlewares"
    14  	"github.com/labstack/echo/v4"
    15  )
    16  
    17  // RevsDiff is part of the replicator
    18  func RevsDiff(c echo.Context) error {
    19  	inst := middlewares.GetInstance(c)
    20  	sharingID := c.Param("sharing-id")
    21  	s, err := sharing.FindSharing(inst, sharingID)
    22  	if err != nil {
    23  		inst.Logger().WithNamespace("replicator").Infof("Sharing was not found: %s", err)
    24  		return wrapErrors(err)
    25  	}
    26  	var changed sharing.Changed
    27  	if err = json.NewDecoder(c.Request().Body).Decode(&changed); err != nil {
    28  		inst.Logger().WithNamespace("replicator").Infof("Changes cannot be bound: %s", err)
    29  		return wrapErrors(err)
    30  	}
    31  	if changed == nil {
    32  		inst.Logger().WithNamespace("replicator").Infof("No changes")
    33  		return echo.NewHTTPError(http.StatusBadRequest)
    34  	}
    35  	missings, err := s.ComputeRevsDiff(inst, changed)
    36  	if err != nil {
    37  		inst.Logger().WithNamespace("replicator").Infof("Error on compute: %s", err)
    38  		return wrapErrors(err)
    39  	}
    40  	return c.JSON(http.StatusOK, missings)
    41  }
    42  
    43  // BulkDocs is part of the replicator
    44  func BulkDocs(c echo.Context) error {
    45  	inst := middlewares.GetInstance(c)
    46  	sharingID := c.Param("sharing-id")
    47  	s, err := sharing.FindSharing(inst, sharingID)
    48  	if err != nil {
    49  		inst.Logger().WithNamespace("replicator").Infof("Sharing was not found: %s", err)
    50  		return wrapErrors(err)
    51  	}
    52  	var docs sharing.DocsByDoctype
    53  	if err = json.NewDecoder(c.Request().Body).Decode(&docs); err != nil {
    54  		inst.Logger().WithNamespace("replicator").Infof("Docs cannot be bound: %s", err)
    55  		return wrapErrors(err)
    56  	}
    57  	if docs == nil {
    58  		inst.Logger().WithNamespace("replicator").Infof("No bulk docs")
    59  		return echo.NewHTTPError(http.StatusBadRequest)
    60  	}
    61  	err = s.ApplyBulkDocs(inst, docs)
    62  	if err != nil {
    63  		inst.Logger().WithNamespace("replicator").Warnf("Error on apply: %s", err)
    64  		return wrapErrors(err)
    65  	}
    66  	return c.JSON(http.StatusOK, []interface{}{})
    67  }
    68  
    69  // GetFolder returns informations about a folder
    70  func GetFolder(c echo.Context) error {
    71  	inst := middlewares.GetInstance(c)
    72  	sharingID := c.Param("sharing-id")
    73  	s, err := sharing.FindSharing(inst, sharingID)
    74  	if err != nil {
    75  		inst.Logger().WithNamespace("replicator").Infof("Sharing was not found: %s", err)
    76  		return wrapErrors(err)
    77  	}
    78  	member, err := requestMember(c, s)
    79  	if err != nil {
    80  		inst.Logger().WithNamespace("replicator").Infof("Member was not found: %s", err)
    81  		return wrapErrors(err)
    82  	}
    83  	folderID := c.Param("id")
    84  	folder, err := s.GetFolder(inst, member, folderID)
    85  	if err != nil {
    86  		inst.Logger().WithNamespace("replicator").Infof("Folder %s was not found: %s", folderID, err)
    87  		return wrapErrors(err)
    88  	}
    89  	return c.JSON(http.StatusOK, folder)
    90  }
    91  
    92  // SyncFile will try to synchronize a file from just its metadata. If it's not
    93  // possible, it will respond with a key that allow to send the content to
    94  // finish the synchronization.
    95  func SyncFile(c echo.Context) error {
    96  	inst := middlewares.GetInstance(c)
    97  	sharingID := c.Param("sharing-id")
    98  	s, err := sharing.FindSharing(inst, sharingID)
    99  	if err != nil {
   100  		inst.Logger().WithNamespace("replicator").Infof("Sharing was not found: %s", err)
   101  		return wrapErrors(err)
   102  	}
   103  	var fileDoc sharing.FileDocWithRevisions
   104  	if err = c.Bind(&fileDoc); err != nil {
   105  		inst.Logger().WithNamespace("replicator").Infof("File cannot be bound: %s", err)
   106  		return wrapErrors(err)
   107  	}
   108  	if c.Param("id") != fileDoc.DocID {
   109  		err = errors.New("The identifiers in the URL and in the doc are not the same")
   110  		return jsonapi.InvalidAttribute("id", err)
   111  	}
   112  	key, err := s.SyncFile(inst, &fileDoc)
   113  	if err != nil {
   114  		inst.Logger().WithNamespace("replicator").Infof("Error on sync file: %s", err)
   115  		return wrapErrors(err)
   116  	}
   117  	if key == nil {
   118  		return c.NoContent(http.StatusNoContent)
   119  	}
   120  	return c.JSON(http.StatusOK, key)
   121  }
   122  
   123  // FileHandler is used to receive a file upload
   124  func FileHandler(c echo.Context) error {
   125  	inst := middlewares.GetInstance(c)
   126  	sharingID := c.Param("sharing-id")
   127  	s, err := sharing.FindSharing(inst, sharingID)
   128  	if err != nil {
   129  		inst.Logger().WithNamespace("replicator").Infof("Sharing was not found: %s", err)
   130  		return wrapErrors(err)
   131  	}
   132  
   133  	create := func(fs vfs.VFS, newdoc, olddoc *vfs.FileDoc) error {
   134  		file, err := fs.CreateFile(newdoc, olddoc)
   135  		if err != nil {
   136  			return err
   137  		}
   138  		_, err = io.Copy(file, c.Request().Body)
   139  		if cerr := file.Close(); cerr != nil && err == nil {
   140  			return cerr
   141  		}
   142  		return err
   143  	}
   144  
   145  	if err := s.HandleFileUpload(inst, c.Param("id"), create); err != nil {
   146  		inst.Logger().WithNamespace("replicator").Infof("Error on file upload: %s", err)
   147  		return wrapErrors(err)
   148  	}
   149  	return c.NoContent(http.StatusNoContent)
   150  }
   151  
   152  // ReuploadHandler is used to try sending again files
   153  func ReuploadHandler(c echo.Context) error {
   154  	inst := middlewares.GetInstance(c)
   155  	sharingID := c.Param("sharing-id")
   156  	s, err := sharing.FindSharing(inst, sharingID)
   157  	if err != nil {
   158  		return wrapErrors(err)
   159  	}
   160  	sharing.PushUploadJob(s, inst)
   161  	return c.NoContent(http.StatusNoContent)
   162  }
   163  
   164  // EndInitial is used for ending the initial sync phase of a sharing
   165  func EndInitial(c echo.Context) error {
   166  	inst := middlewares.GetInstance(c)
   167  	sharingID := c.Param("sharing-id")
   168  	s, err := sharing.FindSharing(inst, sharingID)
   169  	if err != nil {
   170  		return wrapErrors(err)
   171  	}
   172  	if err := s.EndInitial(inst); err != nil {
   173  		return wrapErrors(err)
   174  	}
   175  	return c.NoContent(http.StatusNoContent)
   176  }
   177  
   178  // replicatorRoutes sets the routing for the replicator
   179  func replicatorRoutes(router *echo.Group) {
   180  	group := router.Group("", checkSharingPermissions)
   181  	group.POST("/:sharing-id/_revs_diff", RevsDiff, checkSharingWritePermissions)
   182  	group.POST("/:sharing-id/_bulk_docs", BulkDocs, checkSharingWritePermissions)
   183  	group.GET("/:sharing-id/io.cozy.files/:id", GetFolder, checkSharingReadPermissions)
   184  	group.PUT("/:sharing-id/io.cozy.files/:id/metadata", SyncFile, checkSharingWritePermissions)
   185  	group.PUT("/:sharing-id/io.cozy.files/:id", FileHandler, checkSharingWritePermissions)
   186  	group.POST("/:sharing-id/reupload", ReuploadHandler, checkSharingReadPermissions)
   187  	group.DELETE("/:sharing-id/initial", EndInitial, checkSharingWritePermissions)
   188  }
   189  
   190  func checkSharingReadPermissions(next echo.HandlerFunc) echo.HandlerFunc {
   191  	return func(c echo.Context) error {
   192  		sharingID := c.Param("sharing-id")
   193  		requestPerm, err := middlewares.GetPermission(c)
   194  		if err != nil {
   195  			middlewares.GetInstance(c).Logger().WithNamespace("replicator").
   196  				Infof("Invalid permission: %s", err)
   197  			return err
   198  		}
   199  		if !requestPerm.Permissions.AllowID("GET", consts.Sharings, sharingID) {
   200  			middlewares.GetInstance(c).Logger().WithNamespace("replicator").
   201  				Infof("Not allowed (%s)", sharingID)
   202  			return echo.NewHTTPError(http.StatusForbidden)
   203  		}
   204  		return next(c)
   205  	}
   206  }
   207  
   208  func checkSharingWritePermissions(next echo.HandlerFunc) echo.HandlerFunc {
   209  	return func(c echo.Context) error {
   210  		if err := hasSharingWritePermissions(c); err != nil {
   211  			return err
   212  		}
   213  		return next(c)
   214  	}
   215  }
   216  
   217  func hasSharingWritePermissions(c echo.Context) error {
   218  	sharingID := c.Param("sharing-id")
   219  	requestPerm, err := middlewares.GetPermission(c)
   220  	if err != nil {
   221  		middlewares.GetInstance(c).Logger().WithNamespace("replicator").
   222  			Infof("Invalid permission: %s", err)
   223  		return err
   224  	}
   225  	if !requestPerm.Permissions.AllowID("POST", consts.Sharings, sharingID) {
   226  		middlewares.GetInstance(c).Logger().WithNamespace("replicator").
   227  			Infof("Not allowed (%s)", sharingID)
   228  		return echo.NewHTTPError(http.StatusForbidden)
   229  	}
   230  	return nil
   231  }
   232  
   233  func checkSharingPermissions(next echo.HandlerFunc) echo.HandlerFunc {
   234  	return func(c echo.Context) error {
   235  		sharingID := c.Param("sharing-id")
   236  		requestPerm, err := middlewares.GetPermission(c)
   237  		if err != nil {
   238  			middlewares.GetInstance(c).Logger().WithNamespace("replicator").
   239  				Infof("Invalid permission: %s", err)
   240  			return err
   241  		}
   242  		if !requestPerm.Permissions.AllowID("GET", consts.Sharings, sharingID) {
   243  			middlewares.GetInstance(c).Logger().WithNamespace("replicator").
   244  				Infof("Not allowed (%s)", sharingID)
   245  			return echo.NewHTTPError(http.StatusForbidden)
   246  		}
   247  		return next(c)
   248  	}
   249  }
   250  
   251  func requestMember(c echo.Context, s *sharing.Sharing) (*sharing.Member, error) {
   252  	requestPerm, err := middlewares.GetPermission(c)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	return s.FindMemberByInboundClientID(requestPerm.SourceID)
   257  }