Skip to content

Commit a75d89d

Browse files
committed
feat(graph): populate driveItem.webUrl per Libre Graph spec
The Libre Graph spec declares webUrl on driveItem but cs3ResourceToDriveItem never set it. Build the /f/<resource-id> permalink off the configured public base URL (same source drives.go reads for drive.webUrl) and parse it once at service construction so callers can use it as a plain field.
1 parent ef6da04 commit a75d89d

6 files changed

Lines changed: 71 additions & 9 deletions

File tree

services/graph/pkg/service/v0/api_driveitem_permissions.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package svc
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"net/http"
78
"net/url"
89
"slices"
@@ -91,13 +92,18 @@ type ListPermissionsQueryOptions struct {
9192

9293
// NewDriveItemPermissionsService creates a new DriveItemPermissionsService
9394
func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache cache.IdentityCache, config *config.Config) (DriveItemPermissionsService, error) {
95+
publicBaseURL, err := url.Parse(config.Spaces.WebDavBase)
96+
if err != nil {
97+
return DriveItemPermissionsService{}, fmt.Errorf("could not parse graph.spaces.webdav_base: %w", err)
98+
}
9499
return DriveItemPermissionsService{
95100
BaseGraphService: BaseGraphService{
96101
logger: &log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemService").Logger()},
97102
gatewaySelector: gatewaySelector,
98103
identityCache: identityCache,
99104
config: config,
100105
availableRoles: unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(config.UnifiedRoles.AvailableRoles...)),
106+
publicBaseURL: publicBaseURL,
101107
},
102108
}, nil
103109
}
@@ -405,7 +411,7 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID
405411

406412
driveItems := make(driveItemsByResourceID, 1)
407413
// we can use the statResponse to build the drive item before fetching the shares
408-
item, err := cs3ResourceToDriveItem(s.logger, statResponse.GetInfo())
414+
item, err := cs3ResourceToDriveItem(s.logger, s.publicBaseURL, statResponse.GetInfo())
409415
if err != nil {
410416
return collectionOfPermissions, err
411417
}

services/graph/pkg/service/v0/base.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type BaseGraphService struct {
4949
identityCache cache.IdentityCache
5050
config *config.Config
5151
availableRoles []*libregraph.UnifiedRoleDefinition
52+
publicBaseURL *url.URL
5253
}
5354

5455
func (g BaseGraphService) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId, countOnly bool) ([]libregraph.Permission, int, error) {
@@ -81,7 +82,7 @@ func (g BaseGraphService) getDriveItem(ctx context.Context, ref *storageprovider
8182
refStr, _ := storagespace.FormatReference(ref)
8283
return nil, fmt.Errorf("could not stat %s: %s", refStr, res.GetStatus().GetMessage())
8384
}
84-
return cs3ResourceToDriveItem(g.logger, res.GetInfo())
85+
return cs3ResourceToDriveItem(g.logger, g.publicBaseURL, res.GetInfo())
8586
}
8687

8788
func (g BaseGraphService) CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaboration.ReceivedShare) ([]libregraph.DriveItem, error) {

services/graph/pkg/service/v0/driveitems.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
206206
return
207207
}
208208

209-
files, err := formatDriveItems(g.logger, lRes.GetInfos())
209+
files, err := formatDriveItems(g.logger, g.publicBaseURL, lRes.GetInfos())
210210
if err != nil {
211211
g.logger.Error().Err(err).Msg("error encoding response as json")
212212
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -271,7 +271,7 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) {
271271
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage())
272272
return
273273
}
274-
driveItem, err := cs3ResourceToDriveItem(g.logger, res.GetInfo())
274+
driveItem, err := cs3ResourceToDriveItem(g.logger, g.publicBaseURL, res.GetInfo())
275275
if err != nil {
276276
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
277277
return
@@ -339,7 +339,7 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) {
339339
return
340340
}
341341

342-
files, err := formatDriveItems(g.logger, res.GetInfos())
342+
files, err := formatDriveItems(g.logger, g.publicBaseURL, res.GetInfos())
343343
if err != nil {
344344
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
345345
return
@@ -416,10 +416,10 @@ func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.Resource
416416
return item, nil
417417
}
418418

419-
func formatDriveItems(logger *log.Logger, mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) {
419+
func formatDriveItems(logger *log.Logger, publicBaseURL *url.URL, mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) {
420420
responses := make([]*libregraph.DriveItem, 0, len(mds))
421421
for i := range mds {
422-
res, err := cs3ResourceToDriveItem(logger, mds[i])
422+
res, err := cs3ResourceToDriveItem(logger, publicBaseURL, mds[i])
423423
if err != nil {
424424
return nil, err
425425
}
@@ -433,7 +433,7 @@ func cs3TimestampToTime(t *types.Timestamp) time.Time {
433433
return time.Unix(int64(t.GetSeconds()), int64(t.GetNanos()))
434434
}
435435

436-
func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
436+
func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
437437
size := new(int64)
438438
*size = int64(res.GetSize()) // TODO lurking overflow: make size of libregraph drive item use uint64
439439

@@ -442,6 +442,10 @@ func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInf
442442
Size: size,
443443
}
444444

445+
webURL := *publicBaseURL
446+
webURL.Path = path.Join(webURL.Path, "f", storagespace.FormatResourceID(res.GetId()))
447+
driveItem.WebUrl = libregraph.PtrString(webURL.String())
448+
445449
if name := path.Base(res.GetPath()); name != "" {
446450
driveItem.Name = &name
447451
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package svc
2+
3+
import (
4+
"net/url"
5+
"testing"
6+
7+
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/opencloud-eu/opencloud/pkg/log"
12+
)
13+
14+
func TestCS3ResourceToDriveItemPopulatesWebUrl(t *testing.T) {
15+
logger := log.NewLogger()
16+
res := &provider.ResourceInfo{
17+
Id: &provider.ResourceId{
18+
StorageId: "storage-1",
19+
SpaceId: "space-1",
20+
OpaqueId: "item-1",
21+
},
22+
Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER,
23+
}
24+
25+
t.Run("public base URL without path", func(t *testing.T) {
26+
base, err := url.Parse("https://example.com")
27+
require.NoError(t, err)
28+
29+
item, err := cs3ResourceToDriveItem(&logger, base, res)
30+
require.NoError(t, err)
31+
require.NotNil(t, item.WebUrl)
32+
assert.Equal(t, "https://example.com/f/storage-1$space-1%21item-1", *item.WebUrl)
33+
})
34+
35+
t.Run("public base URL with path prefix", func(t *testing.T) {
36+
base, err := url.Parse("https://example.com/cloud")
37+
require.NoError(t, err)
38+
39+
item, err := cs3ResourceToDriveItem(&logger, base, res)
40+
require.NoError(t, err)
41+
require.NotNil(t, item.WebUrl)
42+
assert.Equal(t, "https://example.com/cloud/f/storage-1$space-1%21item-1", *item.WebUrl)
43+
})
44+
}

services/graph/pkg/service/v0/follow.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func (g Graph) FollowDriveItem(w http.ResponseWriter, r *http.Request) {
9494
}
9595
}
9696

97-
driveItem, err := cs3ResourceToDriveItem(g.logger, statRes.GetInfo())
97+
driveItem, err := cs3ResourceToDriveItem(g.logger, g.publicBaseURL, statRes.GetInfo())
9898
if err != nil {
9999
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
100100
return

services/graph/pkg/service/v0/service.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"net/http"
10+
"net/url"
1011
"os"
1112
"strconv"
1213
"time"
@@ -154,12 +155,18 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx
154155
cache.IdentityCacheWithGroupsTTL(time.Duration(options.Config.Spaces.GroupsCacheTTL)),
155156
)
156157

158+
publicBaseURL, err := url.Parse(options.Config.Spaces.WebDavBase)
159+
if err != nil {
160+
return Graph{}, fmt.Errorf("could not parse graph.spaces.webdav_base: %w", err)
161+
}
162+
157163
baseGraphService := BaseGraphService{
158164
logger: &options.Logger,
159165
identityCache: identityCache,
160166
gatewaySelector: options.GatewaySelector,
161167
config: options.Config,
162168
availableRoles: unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(options.Config.UnifiedRoles.AvailableRoles...)),
169+
publicBaseURL: publicBaseURL,
163170
}
164171

165172
drivesDriveItemService, err := NewDrivesDriveItemService(options.Logger, options.GatewaySelector)

0 commit comments

Comments
 (0)