@@ -120,6 +120,67 @@ func (g Graph) CreateUploadSession(w http.ResponseWriter, r *http.Request) {
120120 render.JSON(w, r, &uploadSession)
121121}
122122
123+ // GetDriveItemContent redirects (302) to the signed download URL for the item,
124+ // the same URL exposed via @microsoft.graph.downloadUrl.
125+ func (g Graph) GetDriveItemContent(w http.ResponseWriter, r *http.Request) {
126+ ctx := r.Context()
127+
128+ driveID, err := parseIDParam(r, "driveID")
129+ if err != nil {
130+ errorcode.RenderError(w, r, err)
131+ return
132+ }
133+ itemID, err := parseIDParam(r, "itemID")
134+ if err != nil {
135+ errorcode.RenderError(w, r, err)
136+ return
137+ }
138+ if driveID.GetStorageId() != itemID.GetStorageId() || driveID.GetSpaceId() != itemID.GetSpaceId() {
139+ errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Item does not exist")
140+ return
141+ }
142+
143+ user, ok := revactx.ContextGetUser(ctx)
144+ if !ok {
145+ errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context")
146+ return
147+ }
148+
149+ gatewayClient, err := g.gatewaySelector.Next()
150+ if err != nil {
151+ errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
152+ return
153+ }
154+ stat, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
155+ switch {
156+ case err != nil:
157+ errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
158+ return
159+ case stat.GetStatus().GetCode() == cs3rpc.Code_CODE_OK:
160+ // ok
161+ case stat.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND:
162+ errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, stat.GetStatus().GetMessage())
163+ return
164+ case stat.GetStatus().GetCode() == cs3rpc.Code_CODE_PERMISSION_DENIED:
165+ errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, stat.GetStatus().GetMessage())
166+ return
167+ case stat.GetStatus().GetCode() == cs3rpc.Code_CODE_UNAUTHENTICATED:
168+ errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, stat.GetStatus().GetMessage())
169+ return
170+ default:
171+ errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, stat.GetStatus().GetMessage())
172+ return
173+ }
174+
175+ downloadURL, err := g.signedDownloadURL(&itemID, user.GetId().GetOpaqueId())
176+ if err != nil {
177+ errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
178+ return
179+ }
180+
181+ http.Redirect(w, r, downloadURL, http.StatusFound)
182+ }
183+
123184type createUploadSessionRequest struct {
124185 DeferCommit bool `json:"deferCommit"`
125186 Item driveItemUploadableProperties `json:"item"`
@@ -138,6 +199,37 @@ type uploadSession struct {
138199 CS3Protocols []*gateway.FileUploadProtocol
139200}
140201
202+ func shouldSelect(r *http.Request, property string) bool {
203+ for _, v := range strings.Split(r.URL.Query().Get("$select"), ",") {
204+ if strings.TrimSpace(v) == property {
205+ return true
206+ }
207+ }
208+ return false
209+ }
210+
211+ // setDriveItemsDownloadURL adds @microsoft.graph.downloadUrl to file items when
212+ // selected via $select. items and infos must be parallel.
213+ func (g Graph) setDriveItemsDownloadURL(r *http.Request, items []*libregraph.DriveItem, infos []*storageprovider.ResourceInfo) {
214+ if g.downloadURLSigner == nil || !shouldSelect(r, "@microsoft.graph.downloadUrl") {
215+ return
216+ }
217+ user, ok := revactx.ContextGetUser(r.Context())
218+ if !ok {
219+ return
220+ }
221+ for i := range items {
222+ if items[i].File == nil {
223+ continue
224+ }
225+ u, err := g.signedDownloadURL(infos[i].GetId(), user.GetId().GetOpaqueId())
226+ if err != nil {
227+ continue
228+ }
229+ items[i].MicrosoftGraphDownloadUrl = &u
230+ }
231+ }
232+
141233// GetRootDriveChildren implements the Service interface.
142234func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
143235 g.logger.Info().Msg("Calling GetRootDriveChildren")
@@ -204,12 +296,13 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
204296 return
205297 }
206298
207- files, err := formatDriveItems(g.logger, g.publicBaseURL, lRes.GetInfos())
299+ files, err := g.formatDriveItems( lRes.GetInfos())
208300 if err != nil {
209301 g.logger.Error().Err(err).Msg("error encoding response as json")
210302 errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
211303 return
212304 }
305+ g.setDriveItemsDownloadURL(r, files, lRes.GetInfos())
213306
214307 render.Status(r, http.StatusOK)
215308 render.JSON(w, r, &ListResponse{Value: files})
@@ -269,11 +362,12 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) {
269362 errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage())
270363 return
271364 }
272- driveItem, err := cs3ResourceToDriveItem(g.logger, g.publicBaseURL, res.GetInfo())
365+ driveItem, err := g.cs3ResourceToDriveItem( res.GetInfo())
273366 if err != nil {
274367 errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
275368 return
276369 }
370+ g.setDriveItemsDownloadURL(r, []*libregraph.DriveItem{driveItem}, []*storageprovider.ResourceInfo{res.GetInfo()})
277371
278372 render.Status(r, http.StatusOK)
279373 render.JSON(w, r, &driveItem)
@@ -337,11 +431,12 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) {
337431 return
338432 }
339433
340- files, err := formatDriveItems(g.logger, g.publicBaseURL, res.GetInfos())
434+ files, err := g.formatDriveItems( res.GetInfos())
341435 if err != nil {
342436 errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
343437 return
344438 }
439+ g.setDriveItemsDownloadURL(r, files, res.GetInfos())
345440
346441 render.Status(r, http.StatusOK)
347442 render.JSON(w, r, &ListResponse{Value: files})
@@ -385,10 +480,13 @@ func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.Resource
385480 return item, nil
386481}
387482
388- func formatDriveItems(logger *log.Logger, publicBaseURL *url.URL, mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) {
483+ // TODO: formatDriveItems/cs3ResourceToDriveItem and the cs3ResourceToDriveItem*Facet
484+ // helpers live here; moving them now doesn't make sense, they are part of the
485+ // refactor in https://github.com/opencloud-eu/opencloud/pull/2659.
486+ func (g BaseGraphService) formatDriveItems(mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) {
389487 responses := make([]*libregraph.DriveItem, 0, len(mds))
390488 for i := range mds {
391- res, err := cs3ResourceToDriveItem(logger, publicBaseURL, mds[i])
489+ res, err := g. cs3ResourceToDriveItem(mds[i])
392490 if err != nil {
393491 return nil, err
394492 }
@@ -402,7 +500,7 @@ func cs3TimestampToTime(t *types.Timestamp) time.Time {
402500 return time.Unix(int64(t.GetSeconds()), int64(t.GetNanos()))
403501}
404502
405- func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
503+ func (g BaseGraphService) cs3ResourceToDriveItem( res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
406504 size := new(int64)
407505 *size = int64(res.GetSize()) // TODO lurking overflow: make size of libregraph drive item use uint64
408506
@@ -411,7 +509,7 @@ func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *sto
411509 Size: size,
412510 }
413511
414- webURL := *publicBaseURL
512+ webURL := *g. publicBaseURL
415513 webURL.Path = path.Join(webURL.Path, "f", storagespace.FormatResourceID(res.GetId()))
416514 driveItem.WebUrl = libregraph.PtrString(webURL.String())
417515
@@ -453,10 +551,10 @@ func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *sto
453551 }
454552
455553 if res.GetArbitraryMetadata() != nil {
456- driveItem.Audio = cs3ResourceToDriveItemAudioFacet(logger, res)
457- driveItem.Image = cs3ResourceToDriveItemImageFacet(logger, res)
458- driveItem.Location = cs3ResourceToDriveItemLocationFacet(logger, res)
459- driveItem.Photo = cs3ResourceToDriveItemPhotoFacet(logger, res)
554+ driveItem.Audio = cs3ResourceToDriveItemAudioFacet(g. logger, res)
555+ driveItem.Image = cs3ResourceToDriveItemImageFacet(g. logger, res)
556+ driveItem.Location = cs3ResourceToDriveItemLocationFacet(g. logger, res)
557+ driveItem.Photo = cs3ResourceToDriveItemPhotoFacet(g. logger, res)
460558 }
461559
462560 return driveItem, nil
0 commit comments