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 }