diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index a628feb726..3d61f34725 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -3,6 +3,7 @@ package svc import ( "context" "errors" + "fmt" "net/http" "net/url" "slices" @@ -91,6 +92,10 @@ type ListPermissionsQueryOptions struct { // NewDriveItemPermissionsService creates a new DriveItemPermissionsService func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache cache.IdentityCache, config *config.Config) (DriveItemPermissionsService, error) { + publicBaseURL, err := url.Parse(config.Spaces.WebDavBase) + if err != nil { + return DriveItemPermissionsService{}, fmt.Errorf("could not parse graph.spaces.webdav_base: %w", err) + } return DriveItemPermissionsService{ BaseGraphService: BaseGraphService{ logger: &log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemService").Logger()}, @@ -98,6 +103,7 @@ func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Sele identityCache: identityCache, config: config, availableRoles: unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(config.UnifiedRoles.AvailableRoles...)), + publicBaseURL: publicBaseURL, }, }, nil } @@ -405,7 +411,7 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID driveItems := make(driveItemsByResourceID, 1) // we can use the statResponse to build the drive item before fetching the shares - item, err := cs3ResourceToDriveItem(s.logger, statResponse.GetInfo()) + item, err := cs3ResourceToDriveItem(s.logger, s.publicBaseURL, statResponse.GetInfo()) if err != nil { return collectionOfPermissions, err } diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go index 4b2c8edd2c..bebd28ea0d 100644 --- a/services/graph/pkg/service/v0/base.go +++ b/services/graph/pkg/service/v0/base.go @@ -49,6 +49,7 @@ type BaseGraphService struct { identityCache cache.IdentityCache config *config.Config availableRoles []*libregraph.UnifiedRoleDefinition + publicBaseURL *url.URL } 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 refStr, _ := storagespace.FormatReference(ref) return nil, fmt.Errorf("could not stat %s: %s", refStr, res.GetStatus().GetMessage()) } - return cs3ResourceToDriveItem(g.logger, res.GetInfo()) + return cs3ResourceToDriveItem(g.logger, g.publicBaseURL, res.GetInfo()) } func (g BaseGraphService) CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaboration.ReceivedShare) ([]libregraph.DriveItem, error) { diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go index da89e25bad..83908cb619 100644 --- a/services/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -206,7 +206,7 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { return } - files, err := formatDriveItems(g.logger, lRes.GetInfos()) + files, err := formatDriveItems(g.logger, g.publicBaseURL, lRes.GetInfos()) if err != nil { g.logger.Error().Err(err).Msg("error encoding response as json") errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) @@ -271,7 +271,7 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.GetStatus().GetMessage()) return } - driveItem, err := cs3ResourceToDriveItem(g.logger, res.GetInfo()) + driveItem, err := cs3ResourceToDriveItem(g.logger, g.publicBaseURL, res.GetInfo()) if err != nil { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return @@ -339,7 +339,7 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) { return } - files, err := formatDriveItems(g.logger, res.GetInfos()) + files, err := formatDriveItems(g.logger, g.publicBaseURL, res.GetInfos()) if err != nil { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return @@ -416,10 +416,10 @@ func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.Resource return item, nil } -func formatDriveItems(logger *log.Logger, mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) { +func formatDriveItems(logger *log.Logger, publicBaseURL *url.URL, mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) { responses := make([]*libregraph.DriveItem, 0, len(mds)) for i := range mds { - res, err := cs3ResourceToDriveItem(logger, mds[i]) + res, err := cs3ResourceToDriveItem(logger, publicBaseURL, mds[i]) if err != nil { return nil, err } @@ -433,7 +433,7 @@ func cs3TimestampToTime(t *types.Timestamp) time.Time { return time.Unix(int64(t.GetSeconds()), int64(t.GetNanos())) } -func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) { +func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) { size := new(int64) *size = int64(res.GetSize()) // TODO lurking overflow: make size of libregraph drive item use uint64 @@ -442,6 +442,10 @@ func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInf Size: size, } + webURL := *publicBaseURL + webURL.Path = path.Join(webURL.Path, "f", storagespace.FormatResourceID(res.GetId())) + driveItem.WebUrl = libregraph.PtrString(webURL.String()) + if name := path.Base(res.GetPath()); name != "" { driveItem.Name = &name } diff --git a/services/graph/pkg/service/v0/driveitems_weburl_test.go b/services/graph/pkg/service/v0/driveitems_weburl_test.go new file mode 100644 index 0000000000..3d8cab7254 --- /dev/null +++ b/services/graph/pkg/service/v0/driveitems_weburl_test.go @@ -0,0 +1,44 @@ +package svc + +import ( + "net/url" + "testing" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/opencloud-eu/opencloud/pkg/log" +) + +func TestCS3ResourceToDriveItemPopulatesWebUrl(t *testing.T) { + logger := log.NewLogger() + res := &provider.ResourceInfo{ + Id: &provider.ResourceId{ + StorageId: "storage-1", + SpaceId: "space-1", + OpaqueId: "item-1", + }, + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + } + + t.Run("public base URL without path", func(t *testing.T) { + base, err := url.Parse("https://example.com") + require.NoError(t, err) + + item, err := cs3ResourceToDriveItem(&logger, base, res) + require.NoError(t, err) + require.NotNil(t, item.WebUrl) + assert.Equal(t, "https://example.com/f/storage-1$space-1%21item-1", *item.WebUrl) + }) + + t.Run("public base URL with path prefix", func(t *testing.T) { + base, err := url.Parse("https://example.com/cloud") + require.NoError(t, err) + + item, err := cs3ResourceToDriveItem(&logger, base, res) + require.NoError(t, err) + require.NotNil(t, item.WebUrl) + assert.Equal(t, "https://example.com/cloud/f/storage-1$space-1%21item-1", *item.WebUrl) + }) +} diff --git a/services/graph/pkg/service/v0/follow.go b/services/graph/pkg/service/v0/follow.go index 6ab6447b82..7a255451a1 100644 --- a/services/graph/pkg/service/v0/follow.go +++ b/services/graph/pkg/service/v0/follow.go @@ -94,7 +94,7 @@ func (g Graph) FollowDriveItem(w http.ResponseWriter, r *http.Request) { } } - driveItem, err := cs3ResourceToDriveItem(g.logger, statRes.GetInfo()) + driveItem, err := cs3ResourceToDriveItem(g.logger, g.publicBaseURL, statRes.GetInfo()) if err != nil { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index 6c9880ba28..ecce7f69e1 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "os" "strconv" "time" @@ -154,12 +155,18 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx cache.IdentityCacheWithGroupsTTL(time.Duration(options.Config.Spaces.GroupsCacheTTL)), ) + publicBaseURL, err := url.Parse(options.Config.Spaces.WebDavBase) + if err != nil { + return Graph{}, fmt.Errorf("could not parse graph.spaces.webdav_base: %w", err) + } + baseGraphService := BaseGraphService{ logger: &options.Logger, identityCache: identityCache, gatewaySelector: options.GatewaySelector, config: options.Config, availableRoles: unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(options.Config.UnifiedRoles.AvailableRoles...)), + publicBaseURL: publicBaseURL, } drivesDriveItemService, err := NewDrivesDriveItemService(options.Logger, options.GatewaySelector)