feat(collaboration): close the CheckFileInfo gaps for the office apps

This commit is contained in:
Florian Schade committed 2026-10-15 11:42:06 +02:00
1 parent 4086fad778
commit 09dba5bcaa
4 files changed
+217 -3

No files matched your search

@@ -1272,6 +1272,13 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
logger.Error().Err(err).Msg("CheckFileInfo: error getting scopes from the context")
}
}
// the space root id must not reach a public share, like the parent folder link above
breadcrumbBrandURL := ""
if spaceRoot := statRes.GetInfo().GetSpace().GetRoot(); !isPublicShare && spaceRoot != nil {
brandURL := *ocURL
brandURL.Path = path.Join(ocURL.Path, "f", storagespace.FormatResourceID(spaceRoot))
breadcrumbBrandURL = brandURL.String()
}
lastModifiedTime := time.Now().UTC().Format(lastModifiedTimeFormat)
if mtime := statRes.GetInfo().GetMtime(); mtime != nil {
lastModifiedTime = utils.TSToTime(mtime).UTC().Format(lastModifiedTimeFormat)
@@ -1287,7 +1294,10 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
// to get the folder we actually need to do a GetPath() request
fileinfo.KeyBreadcrumbFolderName: breadcrumbFolderName,
fileinfo.KeyBreadcrumbFolderURL: parentFolderURL.String(),
fileinfo.KeyBreadcrumbBrandName: statRes.GetInfo().GetSpace().GetName(),
fileinfo.KeyBreadcrumbBrandURL: breadcrumbBrandURL,
fileinfo.KeyCloseURL: parentFolderURL.String(),
fileinfo.KeyHostViewURL: createHostUrl("view", ocURL, f.cfg.App.Name, statRes.GetInfo()),
fileinfo.KeyHostEditURL: createHostUrl("write", ocURL, f.cfg.App.Name, statRes.GetInfo()),
fileinfo.KeyFileSharingURL: createShareUrl(privateLinkURL),
@@ -1325,6 +1335,13 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
infoMap[fileinfo.KeySize] = size
}
infoMap[fileinfo.KeyReadOnly] = wopiContext.ViewMode != appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE
// only the collabora integration in web handles UI_Edit, the other office apps would post into the void
if strings.ToLower(f.cfg.App.Product) == "collabora" {
infoMap[fileinfo.KeyEditModePostMessage] = true
}
switch wopiContext.ViewMode {
case appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE:
infoMap[fileinfo.KeyUserCanWrite] = true
@@ -1337,9 +1354,12 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
infoMap[fileinfo.KeyDisableExport] = true // only for collabora
infoMap[fileinfo.KeyDisableCopy] = true // only for collabora
infoMap[fileinfo.KeyDisablePrint] = true
// a viewer role opens the document view only as well, the watermark is for secure view
if !isPublicShare && wopiContext.ViewOnlyToken != "" {
infoMap[fileinfo.KeyWatermarkText] = f.watermarkText(user) // only for collabora
// a viewer role opens the document view only as well, lock and watermark are for secure view
if wopiContext.ViewOnlyToken != "" {
infoMap[fileinfo.KeyIsUserLocked] = true // only for collabora
if !isPublicShare {
infoMap[fileinfo.KeyWatermarkText] = f.watermarkText(user) // only for collabora
}
}
}
@@ -1762,6 +1762,7 @@ var _ = Describe("FileConnector", func() {
FileVersionURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=versions",
HostEditURL: "https://cloud.opencloud.test/external-test/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write",
HostViewURL: "https://cloud.opencloud.test/external-test/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=view",
CloseURL: "https://cloud.opencloud.test/f/storageid$spaceid%21parentopaqueid",
}
response, err := fc.CheckFileInfo(ctx)
@@ -1838,6 +1839,18 @@ var _ = Describe("FileConnector", func() {
EnableInsertRemoteFile: true,
EnableRemoteLinkPicker: true,
IsAnonymousUser: true,
Version: "v162738490",
LastModifiedTime: "1970-07-08T08:30:49.0000000Z",
ReadOnly: true,
SupportsUpdate: true,
BreadcrumbFolderName: "/path/to",
BreadcrumbFolderURL: "https://cloud.opencloud.test",
CloseURL: "https://cloud.opencloud.test",
FileSharingURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=sharing",
FileVersionURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=versions",
HostEditURL: "https://cloud.opencloud.test/external-collabora/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write",
HostViewURL: "https://cloud.opencloud.test/external-collabora/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=view",
EditModePostMessage: true,
}
response, err := fc.CheckFileInfo(ctx)
@@ -1944,6 +1957,8 @@ var _ = Describe("FileConnector", func() {
FileVersionURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=versions",
HostEditURL: "https://cloud.opencloud.test/external-onlyoffice/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write",
PostMessageOrigin: "https://cloud.opencloud.test",
CloseURL: "https://cloud.opencloud.test/s/ABC123",
ReadOnly: true,
}
response, err := fc.CheckFileInfo(ctx)
@@ -2035,6 +2050,19 @@ var _ = Describe("FileConnector", func() {
EnableInsertRemoteFile: true,
EnableRemoteLinkPicker: true,
IsAdminUser: true,
Version: "v162738490",
LastModifiedTime: "1970-07-08T08:30:49.0000000Z",
ReadOnly: true,
SupportsUpdate: true,
BreadcrumbFolderName: "/path/to",
BreadcrumbFolderURL: "https://cloud.opencloud.test/f/storageid$spaceid%21parentopaqueid",
CloseURL: "https://cloud.opencloud.test/f/storageid$spaceid%21parentopaqueid",
FileSharingURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=sharing",
FileVersionURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=versions",
HostEditURL: "https://cloud.opencloud.test/external-collabora/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write",
HostViewURL: "https://cloud.opencloud.test/external-collabora/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=view",
EditModePostMessage: true,
IsUserLocked: true,
UserExtraInfo: &fileinfo.UserExtraInfo{
Mail: "shaft@example.com",
},
@@ -2128,6 +2156,18 @@ var _ = Describe("FileConnector", func() {
EnableInsertRemoteFile: true,
EnableRemoteLinkPicker: true,
IsAdminUser: true,
Version: "v162738490",
LastModifiedTime: "1970-07-08T08:30:49.0000000Z",
ReadOnly: true,
SupportsUpdate: true,
BreadcrumbFolderName: "/path/to",
BreadcrumbFolderURL: "https://cloud.opencloud.test/f/storageid$spaceid%21parentopaqueid",
CloseURL: "https://cloud.opencloud.test/f/storageid$spaceid%21parentopaqueid",
FileSharingURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=sharing",
FileVersionURL: "https://cloud.opencloud.test/f/storageid$spaceid%21opaqueid?details=versions",
HostEditURL: "https://cloud.opencloud.test/external-collabora/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write",
HostViewURL: "https://cloud.opencloud.test/external-collabora/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=view",
EditModePostMessage: true,
UserExtraInfo: &fileinfo.UserExtraInfo{
Mail: "shaft@example.com",
},
@@ -2218,6 +2258,7 @@ var _ = Describe("FileConnector", func() {
HostEditURL: "https://cloud.opencloud.test/external-onlyoffice/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write",
PostMessageOrigin: "https://cloud.opencloud.test",
EnableInsertRemoteImage: true,
CloseURL: "https://cloud.opencloud.test/f/storageid$spaceid%21parentopaqueid",
}
// change wopi app provider
@@ -2320,6 +2361,87 @@ var _ = Describe("FileConnector", func() {
Expect(lastModified).To(BeTemporally("~", time.Now().UTC(), time.Minute))
})
})
Describe("Breadcrumb brand", func() {
statFileInSpace := func(ctx context.Context) {
gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
Status: status.NewOK(ctx),
Info: &providerv1beta1.ResourceInfo{
Owner: &userv1beta1.UserId{
Idp: "customIdp",
OpaqueId: "aabbcc",
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
},
Size: uint64(998877),
Path: "/path/to/test.txt",
Id: &providerv1beta1.ResourceId{
StorageId: "storageid",
OpaqueId: "opaqueid",
SpaceId: "spaceid",
},
ParentId: &providerv1beta1.ResourceId{
StorageId: "storageid",
OpaqueId: "parentopaqueid",
SpaceId: "spaceid",
},
Space: &providerv1beta1.StorageSpace{
Name: "Project X",
Root: &providerv1beta1.ResourceId{
StorageId: "storageid",
OpaqueId: "spaceid",
SpaceId: "spaceid",
},
},
},
}, nil)
}
BeforeEach(func() {
cfg.App.Name = "Collabora"
cfg.App.Product = "Collabora"
})
It("names and links the space for an authenticated user", func() {
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
ctx = ctxpkg.ContextSetUser(ctx, &userv1beta1.User{
Id: &userv1beta1.UserId{
Idp: "example.com",
OpaqueId: "aabbcc",
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
},
DisplayName: "Pet Shaft",
})
gatewayClient.On("CheckPermission", mock.Anything, mock.Anything).Return(
&permissions.CheckPermissionResponse{Status: status.NewOK(ctx)},
nil,
)
statFileInSpace(ctx)
response, err := fc.CheckFileInfo(ctx)
Expect(err).ToNot(HaveOccurred())
body := response.Body.(*fileinfo.Collabora)
Expect(body.BreadcrumbBrandName).To(Equal("Project X"))
Expect(body.BreadcrumbBrandURL).To(Equal("https://cloud.opencloud.test/f/storageid$spaceid%21spaceid"))
})
It("names the space but keeps its link from a public share", func() {
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
ctx = ctxpkg.ContextSetUser(ctx, &userv1beta1.User{
Opaque: &typesv1beta1.Opaque{
Map: map[string]*typesv1beta1.OpaqueEntry{
"public-share-role": {Decoder: "plain", Value: []byte("viewer")},
},
},
})
statFileInSpace(ctx)
response, err := fc.CheckFileInfo(ctx)
Expect(err).ToNot(HaveOccurred())
body := response.Body.(*fileinfo.Collabora)
Expect(body.BreadcrumbBrandName).To(Equal("Project X"))
Expect(body.BreadcrumbBrandURL).To(BeEmpty())
})
})
})
Describe("GetAvatar", func() {
@@ -27,6 +27,14 @@ type Collabora struct {
PostMessageOrigin string `json:"PostMessageOrigin,omitempty"`
// copied from MS WOPI
Size int64 `json:"Size"`
// The file version, it changes with every change of the file and never repeats.
Version string `json:"Version,omitempty"`
// The last modification of the file as UTC time in ISO 8601 round-trip format.
LastModifiedTime string `json:"LastModifiedTime,omitempty"`
// If set to true, the file cannot be changed by this user.
ReadOnly bool `json:"ReadOnly"`
// If set to true, PutFile and PutRelativeFile are available.
SupportsUpdate bool `json:"SupportsUpdate"`
// The ID of file (like the wopi/files/ID) can be a non-existing file. In that case, the file will be created from a template when the template (eg. an OTT file) is specified as TemplateSource in the CheckFileInfo response. The TemplateSource is supposed to be an URL like https://somewhere/accessible/file.ott that is accessible by the Online. For the actual saving of the content, normal PutFile mechanism will be used.
TemplateSource string `json:"TemplateSource,omitempty"`
// copied from MS WOPI
@@ -38,6 +46,34 @@ type Collabora struct {
// copied from MS WOPI
UserFriendlyName string `json:"UserFriendlyName,omitempty"`
//
// URL properties
//
// The page to navigate to when the editor closes or fails unrecoverably.
CloseURL string `json:"CloseUrl,omitempty"`
// The page that lets the user share the file.
FileSharingURL string `json:"FileSharingUrl,omitempty"`
// The page that shows the version history of the file.
FileVersionURL string `json:"FileVersionUrl,omitempty"`
// The host page that opens the file in edit mode.
HostEditURL string `json:"HostEditUrl,omitempty"`
// The host page that opens the file in view mode.
HostViewURL string `json:"HostViewUrl,omitempty"`
//
// Breadcrumb properties
//
// The name shown as the brand part of the breadcrumb, the space name.
BreadcrumbBrandName string `json:"BreadcrumbBrandName,omitempty"`
// The page to navigate to from the breadcrumb brand.
BreadcrumbBrandURL string `json:"BreadcrumbBrandUrl,omitempty"`
// The name of the folder that contains the file.
BreadcrumbFolderName string `json:"BreadcrumbFolderName,omitempty"`
// The page to navigate to from the breadcrumb folder.
BreadcrumbFolderURL string `json:"BreadcrumbFolderUrl,omitempty"`
//
// Extended response properties
//
@@ -64,6 +100,8 @@ type Collabora struct {
DisableInactiveMessages bool `json:"DisableInactiveMessages,omitempty"`
// Indicate that the integration wants to handle the downloading of pdf for printing or svg for slideshows or exported document, because it cannot rely on browser’s support for downloading.
DownloadAsPostMessage bool `json:"DownloadAsPostMessage,omitempty"`
// If set to true, a UI_Edit postMessage is sent when the user switches to edit mode.
EditModePostMessage bool `json:"EditModePostMessage,omitempty"`
// Similar to download as, doctype extensions can be provided for save-as. In this case the new file is loaded in the integration instead of downloaded.
SaveAsPostmessage bool `json:"SaveAsPostmessage,omitempty"`
// If set to true, it allows the document owner (the one with OwnerId =UserId) to send a closedocument message (see protocol.txt)
@@ -72,6 +110,8 @@ type Collabora struct {
IsAdminUser bool `json:"IsAdminUser"`
// If set to true, some functionality of Collabora which is supposed to be shown to authenticated users only is hidden
IsAnonymousUser bool `json:"IsAnonymousUser,omitempty"`
// If set to true, the feature lock configured in Collabora restricts the commands available to this user.
IsUserLocked bool `json:"IsUserLocked,omitempty"`
// JSON object that contains additional info about the user, namely the avatar image.
// Shared among all views in collaborative editing sessions.
@@ -111,6 +151,12 @@ func (cinfo *Collabora) SetProperties(props map[string]any) {
cinfo.PostMessageOrigin = value.(string)
case KeySize:
cinfo.Size = value.(int64)
case KeyVersion:
cinfo.Version = value.(string)
case KeyLastModifiedTime:
cinfo.LastModifiedTime = value.(string)
case KeyReadOnly:
cinfo.ReadOnly = value.(bool)
case KeyTemplateSource:
cinfo.TemplateSource = value.(string)
case KeyUserCanWrite:
@@ -122,6 +168,17 @@ func (cinfo *Collabora) SetProperties(props map[string]any) {
case KeyUserFriendlyName:
cinfo.UserFriendlyName = value.(string)
case KeyCloseURL:
cinfo.CloseURL = value.(string)
case KeyFileSharingURL:
cinfo.FileSharingURL = value.(string)
case KeyFileVersionURL:
cinfo.FileVersionURL = value.(string)
case KeyHostEditURL:
cinfo.HostEditURL = value.(string)
case KeyHostViewURL:
cinfo.HostViewURL = value.(string)
case KeyEnableInsertRemoteImage:
cinfo.EnableInsertRemoteImage = value.(bool)
case KeyEnableInsertRemoteFile:
@@ -144,6 +201,8 @@ func (cinfo *Collabora) SetProperties(props map[string]any) {
cinfo.DisableInactiveMessages = value.(bool)
case KeyDownloadAsPostMessage:
cinfo.DownloadAsPostMessage = value.(bool)
case KeyEditModePostMessage:
cinfo.EditModePostMessage = value.(bool)
case KeySaveAsPostmessage:
cinfo.SaveAsPostmessage = value.(bool)
case KeyEnableOwnerTermination:
@@ -157,6 +216,8 @@ func (cinfo *Collabora) SetProperties(props map[string]any) {
cinfo.IsAdminUser = value.(bool)
case KeyIsAnonymousUser:
cinfo.IsAnonymousUser = value.(bool)
case KeyIsUserLocked:
cinfo.IsUserLocked = value.(bool)
case KeyEnableShare:
cinfo.EnableShare = value.(bool)
@@ -170,6 +231,16 @@ func (cinfo *Collabora) SetProperties(props map[string]any) {
cinfo.UserCanRename = value.(bool)
case KeyBreadcrumbDocName:
cinfo.BreadcrumbDocName = value.(string)
case KeySupportsUpdate:
cinfo.SupportsUpdate = value.(bool)
case KeyBreadcrumbBrandName:
cinfo.BreadcrumbBrandName = value.(string)
case KeyBreadcrumbBrandURL:
cinfo.BreadcrumbBrandURL = value.(string)
case KeyBreadcrumbFolderName:
cinfo.BreadcrumbFolderName = value.(string)
case KeyBreadcrumbFolderURL:
cinfo.BreadcrumbFolderURL = value.(string)
}
}
}
@@ -51,6 +51,7 @@ const (
KeyIsAnonymousUser = "IsAnonymousUser"
KeyIsEduUser = "IsEduUser"
KeyIsAdminUser = "IsAdminUser"
KeyIsUserLocked = "IsUserLocked"
KeyLicenseCheckForEditIsEnabled = "LicenseCheckForEditIsEnabled"
KeyUserFriendlyName = "UserFriendlyName"
KeyUserInfo = "UserInfo"