mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-10-07 11:21:56 -04:00
Merge pull request #3668 from aduffeck/send-guestlink-emails
Send guestlink emails
This commit is contained in:
14 files changed
+267
-28
No files matched your search
@@ -35,6 +35,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.30.3
|
||||
github.com/go-resty/resty/v2 v2.17.2
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/gofrs/flock v0.13.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/go-cmp v0.7.0
|
||||
@@ -65,7 +66,7 @@ require (
|
||||
github.com/open-policy-agent/opa v1.19.1
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8
|
||||
github.com/opencloud-eu/reva/v2 v2.51.0
|
||||
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424
|
||||
github.com/opensearch-project/opensearch-go/v4 v4.7.3
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -229,7 +230,6 @@ require (
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gofrs/flock v0.13.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
|
||||
@@ -761,8 +761,8 @@ github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+l
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8 h1:VTkFLkLNna+T4RN6JUZnVQ1bqVAfPtMeORXbFzEYGKs=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
|
||||
github.com/opencloud-eu/reva/v2 v2.51.0 h1:PbvUtLlbCpS5em48cSGk2sHRdjVA7htRQVKjpckI2w4=
|
||||
github.com/opencloud-eu/reva/v2 v2.51.0/go.mod h1:ngLsDakKnt8zCFCeULhuSZFESGy5NIuSq5/JGeOhPdU=
|
||||
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424 h1:rvopdGQTsI0PT7P3MRamrDsO1yYIKJBOxJAbbYmc4P8=
|
||||
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424/go.mod h1:ngLsDakKnt8zCFCeULhuSZFESGy5NIuSq5/JGeOhPdU=
|
||||
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI=
|
||||
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
|
||||
@@ -290,6 +290,9 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
|
||||
"outgoing": cfg.EnableFederatedSharingOutgoing,
|
||||
"incoming": cfg.EnableFederatedSharingIncoming,
|
||||
},
|
||||
"guest_links": map[string]any{
|
||||
"enabled": cfg.Commons.EnableGuestLinks,
|
||||
},
|
||||
},
|
||||
"spaces": map[string]any{
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -91,6 +91,7 @@ func Server(cfg *config.Config) *cobra.Command {
|
||||
events.ScienceMeshInviteTokenGenerated{},
|
||||
events.SendEmailsEvent{},
|
||||
ocEvents.ResourceMention{},
|
||||
ocEvents.GuestTokenCreated{},
|
||||
}
|
||||
registeredEvents := make(map[string]events.Unmarshaller)
|
||||
for _, e := range evs {
|
||||
|
||||
@@ -49,6 +49,24 @@ Even though this share has been revoked you still might have access through othe
|
||||
Even though this share has been revoked you still might have access through other shares and/or space memberships.`),
|
||||
}
|
||||
|
||||
// Guest link templates
|
||||
GuestLinkShareCreated = MessageTemplate{
|
||||
textTemplate: _textTemplate,
|
||||
htmlTemplate: _htmlTemplate,
|
||||
// GuestLinkShareCreated email template, Subject field (resolves directly)
|
||||
Subject: l10n.Template(`{ShareSharer} shared '{ShareFolder}' with you`),
|
||||
// GuestLinkShareCreated email template, resolves via {{ .Greeting }}
|
||||
Greeting: l10n.Template(`Hello,`),
|
||||
// GuestLinkShareCreated email template, resolves via {{ .MessageBody }}
|
||||
MessageBody: l10n.Template(`{ShareSharer} has shared "{ShareFolder}" with you.
|
||||
|
||||
The link below is personal and can only be used once. Please do not forward it.
|
||||
|
||||
The link is only valid for 30 minutes.`),
|
||||
// GuestLinkShareCreated email template, resolves via {{ .CallToAction }}
|
||||
CallToAction: l10n.Template(`Click here to view it: {ShareLink}`),
|
||||
}
|
||||
|
||||
// Spaces templates
|
||||
SharedSpace = MessageTemplate{
|
||||
textTemplate: _textTemplate,
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
|
||||
ocevents "github.com/opencloud-eu/opencloud/pkg/events"
|
||||
"github.com/opencloud-eu/opencloud/services/notifications/pkg/channels"
|
||||
"github.com/opencloud-eu/opencloud/services/notifications/pkg/email"
|
||||
)
|
||||
|
||||
// handleGuestTokenCreated sends the guest link to the guest's email address.
|
||||
// Guests have no account, settings or locale, so the mail is always sent
|
||||
// instantly (the token is short-lived) in the default language.
|
||||
func (s eventsNotifier) handleGuestTokenCreated(e ocevents.GuestTokenCreated) {
|
||||
logger := s.logger.With().
|
||||
Str("event", "GuestTokenCreated").
|
||||
Str("itemid", e.ItemID.GetOpaqueId()).
|
||||
Logger()
|
||||
|
||||
if err := validate.Var(e.GranteeEmail, "required,email"); err != nil {
|
||||
logger.Error().Err(err).Msg("invalid guest email address")
|
||||
return
|
||||
}
|
||||
if e.Token == "" {
|
||||
logger.Error().Msg("guest token is empty")
|
||||
return
|
||||
}
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not select next gateway client")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, err := utils.GetServiceUserContextWithContext(context.Background(), gatewayClient, s.serviceAccountID, s.serviceAccountSecret)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not get service user context")
|
||||
return
|
||||
}
|
||||
|
||||
owner, err := utils.GetUserNoGroups(ctx, e.Sharer, gatewayClient)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not get user")
|
||||
return
|
||||
}
|
||||
|
||||
shareFolder := e.ResourceName
|
||||
if shareFolder == "" {
|
||||
resourceInfo, err := s.getResourceInfo(ctx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not stat resource")
|
||||
return
|
||||
}
|
||||
shareFolder = resourceInfo.GetName()
|
||||
}
|
||||
|
||||
shareLink, err := urlJoinPath(s.openCloudURL, "g", e.Token)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not create guest link")
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := email.RenderEmailTemplate(email.GuestLinkShareCreated, s.defaultLanguage, s.defaultLanguage,
|
||||
s.emailTemplatePath, s.translationPath,
|
||||
map[string]string{
|
||||
"ShareSharer": owner.GetDisplayName(),
|
||||
"ShareFolder": shareFolder,
|
||||
"ShareLink": shareLink,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("could not render the email")
|
||||
return
|
||||
}
|
||||
msg.Sender = owner.GetDisplayName()
|
||||
msg.Recipient = []string{e.GranteeEmail}
|
||||
|
||||
s.send(ctx, []*channels.Message{msg})
|
||||
}
|
||||
@@ -131,6 +131,8 @@ EventLoop:
|
||||
s.handleShareExpired(e, evt.ID)
|
||||
case events.ShareRemoved:
|
||||
s.handleShareRemoved(e, evt.ID)
|
||||
case ocEvents.GuestTokenCreated:
|
||||
s.handleGuestTokenCreated(e)
|
||||
case events.ScienceMeshInviteTokenGenerated:
|
||||
s.handleScienceMeshInviteTokenGenerated(e)
|
||||
case events.SendEmailsEvent:
|
||||
@@ -202,6 +204,10 @@ func (s eventsNotifier) ensureGranteeList(ctx context.Context, executant, u *use
|
||||
func (s eventsNotifier) getGranteeList(ctx context.Context, executant, u *user.UserId, g *group.GroupId) ([]*user.User, error) {
|
||||
switch {
|
||||
case u != nil:
|
||||
// guests aren't users, they are notified via GuestTokenCreated
|
||||
if u.GetType() == user.UserType_USER_TYPE_GUEST {
|
||||
return nil, nil
|
||||
}
|
||||
if s.disableEmails(ctx, u) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
ocEvents "github.com/opencloud-eu/opencloud/pkg/events"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
|
||||
@@ -98,7 +99,7 @@ var _ = Describe("Notifications", func() {
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
ch := make(chan events.Event)
|
||||
evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "",
|
||||
"", "", "", "", "", "",
|
||||
"", "", "", testOpenCloudURL, "", "",
|
||||
store.Create(), nil, nil)
|
||||
go evts.Run()
|
||||
|
||||
@@ -118,7 +119,7 @@ var _ = Describe("Notifications", func() {
|
||||
|
||||
Dr. S. Harer has shared "secrets of the board" with you.
|
||||
|
||||
Click here to view it: files/shares/with-me
|
||||
Click here to view it: https://cloud.example.com/files/shares/with-me
|
||||
|
||||
|
||||
---
|
||||
@@ -126,6 +127,7 @@ OpenCloud - a safe home for all your data
|
||||
https://opencloud.eu
|
||||
`,
|
||||
expectedSender: sharer.GetDisplayName(),
|
||||
expectedLink: testOpenCloudURL + "/files/shares/with-me",
|
||||
done: make(chan struct{}),
|
||||
}, events.Event{
|
||||
Event: events.ShareCreated{
|
||||
@@ -192,7 +194,7 @@ https://opencloud.eu
|
||||
|
||||
Dr. S. Harer has invited you to join "secret space".
|
||||
|
||||
Click here to view it: f/spaceid
|
||||
Click here to view it: https://cloud.example.com/f/spaceid
|
||||
|
||||
|
||||
---
|
||||
@@ -200,6 +202,7 @@ OpenCloud - a safe home for all your data
|
||||
https://opencloud.eu
|
||||
`,
|
||||
expectedSender: sharer.GetDisplayName(),
|
||||
expectedLink: testOpenCloudURL + "/f/spaceid",
|
||||
done: make(chan struct{}),
|
||||
}, events.Event{
|
||||
Event: events.SpaceShared{
|
||||
@@ -219,7 +222,7 @@ Dr. S. Harer has removed you from "secret space".
|
||||
|
||||
You might still have access through your other groups or direct membership.
|
||||
|
||||
Click here to check it: f/spaceid
|
||||
Click here to check it: https://cloud.example.com/f/spaceid
|
||||
|
||||
|
||||
---
|
||||
@@ -227,6 +230,7 @@ OpenCloud - a safe home for all your data
|
||||
https://opencloud.eu
|
||||
`,
|
||||
expectedSender: sharer.GetDisplayName(),
|
||||
expectedLink: testOpenCloudURL + "/f/spaceid",
|
||||
done: make(chan struct{}),
|
||||
}, events.Event{
|
||||
Event: events.SpaceUnshared{
|
||||
@@ -261,6 +265,79 @@ https://opencloud.eu
|
||||
ExpiredAt: time.Date(2023, 4, 17, 16, 42, 0, 0, time.UTC),
|
||||
},
|
||||
}),
|
||||
|
||||
Entry("Guest Token Created", testChannel{
|
||||
expectedReceipients: []string{"guest@example.com"},
|
||||
expectedSubject: "Dr. S. Harer shared 'guest folder' with you",
|
||||
expectedTextBody: `Hello,
|
||||
|
||||
Dr. S. Harer has shared "guest folder" with you.
|
||||
|
||||
The link below is personal and can only be used once. Please do not forward it.
|
||||
|
||||
The link is only valid for 30 minutes.
|
||||
|
||||
Click here to view it: https://cloud.example.com/g/v1.sharehash.secret
|
||||
|
||||
|
||||
---
|
||||
OpenCloud - a safe home for all your data
|
||||
https://opencloud.eu
|
||||
`,
|
||||
expectedHTMLBody: `<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="600px">
|
||||
<tr>
|
||||
<td width="20px"> </td>
|
||||
<td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">
|
||||
Hello,
|
||||
<br><br>
|
||||
Dr. S. Harer has shared "guest folder" with you.<br><br>The link below is personal and can only be used once. Please do not forward it.<br><br>The link is only valid for 30 minutes.
|
||||
<br><br>
|
||||
Click here to view it: <a href="https://cloud.example.com/g/v1.sharehash.secret">https://cloud.example.com/g/v1.sharehash.secret</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="20px"> </td>
|
||||
<td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">
|
||||
<footer>
|
||||
<br>
|
||||
<br>
|
||||
--- <br>
|
||||
OpenCloud - a safe home for all your data<br>
|
||||
<a href="https://opencloud.eu">https://opencloud.eu</a>
|
||||
</footer>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
expectedSender: sharer.GetDisplayName(),
|
||||
expectedLink: testOpenCloudURL + "/g/v1.sharehash.secret",
|
||||
done: make(chan struct{}),
|
||||
}, events.Event{
|
||||
Event: ocEvents.GuestTokenCreated{
|
||||
Sharer: sharer.GetId(),
|
||||
GranteeEmail: "guest@example.com",
|
||||
ItemID: resourceid,
|
||||
ResourceName: "guest folder",
|
||||
Token: "v1.sharehash.secret",
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -339,7 +416,7 @@ var _ = Describe("Notifications X-Site Scripting", func() {
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
ch := make(chan events.Event)
|
||||
evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "",
|
||||
"", "", "", "", "", "",
|
||||
"", "", "", testOpenCloudURL, "", "",
|
||||
store.Create(), nil, nil)
|
||||
go evts.Run()
|
||||
|
||||
@@ -359,7 +436,7 @@ var _ = Describe("Notifications X-Site Scripting", func() {
|
||||
|
||||
Dr. O'reilly has shared "<script>alert('secrets of the board');</script>" with you.
|
||||
|
||||
Click here to view it: files/shares/with-me
|
||||
Click here to view it: https://cloud.example.com/files/shares/with-me
|
||||
|
||||
|
||||
---
|
||||
@@ -380,7 +457,7 @@ https://opencloud.eu
|
||||
<br><br>
|
||||
Dr. O'reilly has shared "<script>alert('secrets of the board');</script>" with you.
|
||||
<br><br>
|
||||
Click here to view it: <a href="files/shares/with-me">files/shares/with-me</a>
|
||||
Click here to view it: <a href="https://cloud.example.com/files/shares/with-me">https://cloud.example.com/files/shares/with-me</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -410,7 +487,8 @@ https://opencloud.eu
|
||||
`,
|
||||
expectedSender: sharer.GetDisplayName(),
|
||||
|
||||
done: make(chan struct{}),
|
||||
expectedLink: testOpenCloudURL + "/files/shares/with-me",
|
||||
done: make(chan struct{}),
|
||||
}, events.Event{
|
||||
Event: events.ShareCreated{
|
||||
Sharer: sharer.GetId(),
|
||||
@@ -427,7 +505,7 @@ https://opencloud.eu
|
||||
|
||||
Dr. O'reilly has invited you to join "<script>alert('secret space');</script>".
|
||||
|
||||
Click here to view it: f/spaceid
|
||||
Click here to view it: https://cloud.example.com/f/spaceid
|
||||
|
||||
|
||||
---
|
||||
@@ -449,7 +527,7 @@ https://opencloud.eu
|
||||
<br><br>
|
||||
Dr. O'reilly has invited you to join "<script>alert('secret space');</script>".
|
||||
<br><br>
|
||||
Click here to view it: <a href="f/spaceid">f/spaceid</a>
|
||||
Click here to view it: <a href="https://cloud.example.com/f/spaceid">https://cloud.example.com/f/spaceid</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -477,7 +555,8 @@ https://opencloud.eu
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
done: make(chan struct{}),
|
||||
expectedLink: testOpenCloudURL + "/f/spaceid",
|
||||
done: make(chan struct{}),
|
||||
}, events.Event{
|
||||
Event: events.SpaceShared{
|
||||
Executant: sharer.GetId(),
|
||||
@@ -489,14 +568,18 @@ https://opencloud.eu
|
||||
)
|
||||
})
|
||||
|
||||
// NOTE: This is explictitly not testing the message itself. Should we?
|
||||
const testOpenCloudURL = "https://cloud.example.com"
|
||||
|
||||
type testChannel struct {
|
||||
expectedReceipients []string
|
||||
expectedSubject string
|
||||
expectedTextBody string
|
||||
expectedHTMLBody string
|
||||
expectedSender string
|
||||
done chan struct{}
|
||||
// expectedLink, if set, must be an absolute URL on the OpenCloud instance
|
||||
// and must be linked in the HTML body.
|
||||
expectedLink string
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (tc testChannel) SendMessage(ctx context.Context, m *channels.Message) error {
|
||||
@@ -506,9 +589,15 @@ func (tc testChannel) SendMessage(ctx context.Context, m *channels.Message) erro
|
||||
Expect(tc.expectedSubject).To(Equal(m.Subject))
|
||||
Expect(tc.expectedTextBody).To(Equal(m.TextBody))
|
||||
Expect(tc.expectedSender).To(Equal(m.Sender))
|
||||
Expect(m.HTMLBody).ToNot(BeEmpty())
|
||||
if tc.expectedHTMLBody != "" {
|
||||
Expect(tc.expectedHTMLBody).To(Equal(m.HTMLBody))
|
||||
}
|
||||
if tc.expectedLink != "" {
|
||||
Expect(tc.expectedLink).To(HavePrefix(testOpenCloudURL + "/"))
|
||||
Expect(m.TextBody).To(ContainSubstring(tc.expectedLink))
|
||||
Expect(m.HTMLBody).To(ContainSubstring(`<a href="` + tc.expectedLink + `">`))
|
||||
}
|
||||
tc.done <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
+6
@@ -198,6 +198,12 @@ func (h *Handler) Init(c *config.Config) {
|
||||
// h.c.Capabilities.FilesSharing.Federation.Outgoing is boolean
|
||||
// h.c.Capabilities.FilesSharing.Federation.Incoming is boolean
|
||||
|
||||
if h.c.Capabilities.FilesSharing.GuestLinks == nil {
|
||||
h.c.Capabilities.FilesSharing.GuestLinks = &ocs.CapabilitiesFilesSharingGuestLinks{}
|
||||
}
|
||||
|
||||
// h.c.Capabilities.FilesSharing.GuestLinks.Enabled is boolean
|
||||
|
||||
if h.c.Capabilities.FilesSharing.SearchMinLength == 0 {
|
||||
h.c.Capabilities.FilesSharing.SearchMinLength = 2
|
||||
}
|
||||
|
||||
+6
@@ -225,6 +225,7 @@ type CapabilitiesFilesSharing struct {
|
||||
DefaultPermissions int `json:"default_permissions" xml:"default_permissions" mapstructure:"default_permissions"`
|
||||
UserEnumeration *CapabilitiesFilesSharingUserEnumeration `json:"user_enumeration" xml:"user_enumeration" mapstructure:"user_enumeration"`
|
||||
Federation *CapabilitiesFilesSharingFederation `json:"federation" xml:"federation"`
|
||||
GuestLinks *CapabilitiesFilesSharingGuestLinks `json:"guest_links" xml:"guest_links" mapstructure:"guest_links"`
|
||||
Public *CapabilitiesFilesSharingPublic `json:"public" xml:"public"`
|
||||
User *CapabilitiesFilesSharingUser `json:"user" xml:"user"`
|
||||
// TODO: Remove next line once web defaults to resharing=false
|
||||
@@ -291,6 +292,11 @@ type CapabilitiesFilesSharingFederation struct {
|
||||
Incoming ocsBool `json:"incoming" xml:"incoming"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingGuestLinks holds the guest links capabilities
|
||||
type CapabilitiesFilesSharingGuestLinks struct {
|
||||
Enabled ocsBool `json:"enabled" xml:"enabled" mapstructure:"enabled"`
|
||||
}
|
||||
|
||||
// CapabilitiesNotifications holds a list of notification endpoints
|
||||
type CapabilitiesNotifications struct {
|
||||
Endpoints []string `json:"ocs-endpoints,omitempty" xml:"ocs-endpoints>element,omitempty" mapstructure:"endpoints"`
|
||||
|
||||
Generated
Vendored
+17
-4
@@ -53,7 +53,7 @@ func New(root string) (*Blobstore, error) {
|
||||
}
|
||||
|
||||
// Upload stores some data in the blobstore under the given key
|
||||
func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) error {
|
||||
func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) (err error) {
|
||||
if node.BlobID == "" {
|
||||
return ErrBlobIDEmpty
|
||||
}
|
||||
@@ -80,14 +80,27 @@ func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not open blob '%s' for writing", dest)
|
||||
}
|
||||
defer func() {
|
||||
if cerr := f.Close(); cerr != nil && err == nil {
|
||||
err = errors.Wrapf(cerr, "could not close blob '%s'", dest)
|
||||
}
|
||||
}()
|
||||
|
||||
w := bufio.NewWriter(f)
|
||||
_, err = w.ReadFrom(file)
|
||||
if err != nil {
|
||||
if _, err = w.ReadFrom(file); err != nil {
|
||||
return errors.Wrapf(err, "could not write blob '%s'", dest)
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
if err = w.Flush(); err != nil {
|
||||
return errors.Wrapf(err, "could not flush blob '%s'", dest)
|
||||
}
|
||||
|
||||
// Ensure data is synced to disk before returning
|
||||
if err = f.Sync(); err != nil {
|
||||
return errors.Wrapf(err, "could not sync blob '%s' to disk", dest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Download retrieves a blob from the blobstore for reading
|
||||
|
||||
Generated
Vendored
+17
-4
@@ -53,7 +53,7 @@ func New(root string) (*Blobstore, error) {
|
||||
}
|
||||
|
||||
// Upload stores some data in the blobstore under the given key
|
||||
func (bs *Blobstore) Upload(node *node.Node, source string) error {
|
||||
func (bs *Blobstore) Upload(node *node.Node, source string) (err error) {
|
||||
if node.BlobID == "" {
|
||||
return ErrBlobIDEmpty
|
||||
}
|
||||
@@ -80,14 +80,27 @@ func (bs *Blobstore) Upload(node *node.Node, source string) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not open blob '%s' for writing", dest)
|
||||
}
|
||||
defer func() {
|
||||
if cerr := f.Close(); cerr != nil && err == nil {
|
||||
err = errors.Wrapf(cerr, "could not close blob '%s'", dest)
|
||||
}
|
||||
}()
|
||||
|
||||
w := bufio.NewWriter(f)
|
||||
_, err = w.ReadFrom(file)
|
||||
if err != nil {
|
||||
if _, err = w.ReadFrom(file); err != nil {
|
||||
return errors.Wrapf(err, "could not write blob '%s'", dest)
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
if err = w.Flush(); err != nil {
|
||||
return errors.Wrapf(err, "could not flush blob '%s'", dest)
|
||||
}
|
||||
|
||||
// Ensure data is synced to disk before returning
|
||||
if err = f.Sync(); err != nil {
|
||||
return errors.Wrapf(err, "could not sync blob '%s' to disk", dest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Download retrieves a blob from the blobstore for reading
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -95,6 +95,7 @@ func (bs *Blobstore) Upload(n *node.Node, source, copyTarget string) error {
|
||||
}
|
||||
|
||||
if err := copyWithPeriodicSync(tempFile, sourceFile); err != nil {
|
||||
_ = tempFile.Close()
|
||||
return fmt.Errorf("failed to copy source file '%s' to temp file '%s' - %v", source, tempName, err)
|
||||
}
|
||||
|
||||
@@ -204,8 +205,7 @@ func copyWithPeriodicSync(dst, src *os.File) error {
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
_ = dst.Sync()
|
||||
return nil
|
||||
return dst.Sync()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Vendored
+1
-1
@@ -1362,7 +1362,7 @@ github.com/opencloud-eu/icap-client
|
||||
# github.com/opencloud-eu/libre-graph-api-go v1.0.8
|
||||
## explicit; go 1.23
|
||||
github.com/opencloud-eu/libre-graph-api-go
|
||||
# github.com/opencloud-eu/reva/v2 v2.51.0
|
||||
# github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424
|
||||
## explicit; go 1.26.0
|
||||
github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace
|
||||
github.com/opencloud-eu/reva/v2/cmd/revad/runtime
|
||||
|
||||
Reference in new issue
Block a user