Skip to content

Commit 225fc5e

Browse files
committed
fix: Fixed shared drive owner notification on revocation
1 parent 4f0afbe commit 225fc5e

6 files changed

Lines changed: 105 additions & 22 deletions

File tree

model/sharing/invitation.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,6 @@ func (s *Sharing) CreateDriveShortcut(inst *instance.Instance, seen bool) error
336336
return err
337337
}
338338

339-
s.Active = true
340339
s.ShortcutID = fileDoc.DocID
341340
if err := couchdb.UpdateDoc(inst, s); err != nil {
342341
inst.Logger().Warnf("Cannot save shortcut id %s: %s", s.ShortcutID, err)

model/sharing/member.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,14 +1044,23 @@ func (s *Sharing) NotifyMemberRevocation(inst *instance.Instance, m *Member, c *
10441044
}
10451045
var token string
10461046
if s.Drive {
1047-
interact, err := permission.GetForShareInteract(inst, s.ID())
1048-
if err != nil {
1049-
return err
1050-
}
1051-
if code, ok := interact.Codes[m.Instance]; ok {
1052-
token = code
1053-
} else if code, ok := interact.Codes[m.Email]; ok {
1054-
token = code
1047+
if m.Status == MemberStatusOwner {
1048+
// Recipient notifying owner - use DriveToken from credentials
1049+
if c == nil || c.DriveToken == "" {
1050+
return ErrNoOAuthClient
1051+
}
1052+
token = c.DriveToken
1053+
} else {
1054+
// Owner notifying recipient - use interact codes
1055+
interact, err := permission.GetForShareInteract(inst, s.ID())
1056+
if err != nil {
1057+
return err
1058+
}
1059+
if code, ok := interact.Codes[m.Instance]; ok {
1060+
token = code
1061+
} else if code, ok := interact.Codes[m.Email]; ok {
1062+
token = code
1063+
}
10551064
}
10561065
} else {
10571066
if c.Client == nil || c.AccessToken == nil {

model/sharing/sharing.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ func (s *Sharing) RevokeRecipientByNotification(inst *instance.Instance, m *Memb
780780
// NoMoreRecipient cleans up the sharing if there is no more active recipient
781781
func (s *Sharing) NoMoreRecipient(inst *instance.Instance) error {
782782
if s.Drive {
783-
return nil
783+
return couchdb.UpdateDoc(inst, s)
784784
}
785785
for _, m := range s.Members {
786786
if m.Status == MemberStatusReady {

web/sharings/drives_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,54 @@ func TestSharedDriveRevocation(t *testing.T) {
20372037
})
20382038
}
20392039

2040+
// TestSharedDriveRecipientSelfRevocation tests that a recipient can revoke themselves
2041+
// from a shared drive and the owner is properly notified.
2042+
func TestSharedDriveRecipientSelfRevocation(t *testing.T) {
2043+
if testing.Short() {
2044+
t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
2045+
}
2046+
2047+
env := setupSharedDrivesEnv(t)
2048+
eA, eB, _ := env.createClients(t)
2049+
2050+
// Use the existing shared drive from setupSharedDrivesEnv where Betty is already a member
2051+
sharingID := env.firstSharingID
2052+
2053+
t.Run("VerifyBettyIsMember", func(t *testing.T) {
2054+
obj := eA.GET("/sharings/"+sharingID).
2055+
WithHeader("Authorization", "Bearer "+env.acmeToken).
2056+
Expect().Status(200).
2057+
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
2058+
Object()
2059+
2060+
// Betty should be a ready member (member index 1, after owner)
2061+
members := obj.Path("$.data.attributes.members").Array()
2062+
members.Value(1).Object().HasValue("status", "ready")
2063+
members.Value(1).Object().HasValue("name", "Betty")
2064+
})
2065+
2066+
t.Run("BettyRevokesHerself", func(t *testing.T) {
2067+
// Betty revokes herself from the sharing
2068+
eB.DELETE("/sharings/"+sharingID+"/recipients/self").
2069+
WithHeader("Authorization", "Bearer "+env.bettyToken).
2070+
Expect().Status(204)
2071+
})
2072+
2073+
t.Run("VerifyBettyIsRevokedOnOwnerSide", func(t *testing.T) {
2074+
// Wait for the revocation notification to be processed on owner's side
2075+
require.Eventually(t, func() bool {
2076+
obj := eA.GET("/sharings/"+sharingID).
2077+
WithHeader("Authorization", "Bearer "+env.acmeToken).
2078+
Expect().Status(200).
2079+
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
2080+
Object()
2081+
2082+
status := obj.Path("$.data.attributes.members[1].status").String().Raw()
2083+
return status == "revoked"
2084+
}, 5*time.Second, 100*time.Millisecond, "Betty's status should be revoked on owner's side")
2085+
})
2086+
}
2087+
20402088
// TestSharedDriveGroupDynamicMembership tests that when a contact is added to a group
20412089
// that is part of a shared drive, the new member automatically gets access to the drive
20422090
// without needing to accept a specific invitation.

web/sharings/revoke.go

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,25 @@ func RevokeGroup(c echo.Context) error {
8282
return c.NoContent(http.StatusNoContent)
8383
}
8484

85+
// getBearerToken extracts the Bearer token from the Authorization header
86+
func getBearerToken(c echo.Context) string {
87+
token := c.Request().Header.Get(echo.HeaderAuthorization)
88+
return strings.TrimPrefix(token, "Bearer ")
89+
}
90+
91+
// checkDriveSharingWritePermissions checks if the request has valid permissions for a Drive sharing.
92+
// For non-Drive sharings, it falls back to OAuth-based permission checking.
93+
func checkDriveSharingWritePermissions(c echo.Context, s *sharing.Sharing) error {
94+
if s.Drive {
95+
token := getBearerToken(c)
96+
if token == "" || len(s.Credentials) == 0 || token != s.Credentials[0].DriveToken {
97+
return echo.NewHTTPError(http.StatusForbidden)
98+
}
99+
return nil
100+
}
101+
return hasSharingWritePermissions(c)
102+
}
103+
85104
// RevocationRecipientNotif is used to inform a recipient that the sharing is revoked
86105
func RevocationRecipientNotif(c echo.Context) error {
87106
inst := middlewares.GetInstance(c)
@@ -90,16 +109,8 @@ func RevocationRecipientNotif(c echo.Context) error {
90109
if err != nil {
91110
return wrapErrors(err)
92111
}
93-
if s.Drive {
94-
token := c.Request().Header.Get(echo.HeaderAuthorization)
95-
token = strings.TrimPrefix(token, "Bearer ")
96-
if token == "" || len(s.Credentials) == 0 || token != s.Credentials[0].DriveToken {
97-
return echo.NewHTTPError(http.StatusForbidden)
98-
}
99-
} else {
100-
if err := hasSharingWritePermissions(c); err != nil {
101-
return err
102-
}
112+
if err := checkDriveSharingWritePermissions(c, s); err != nil {
113+
return err
103114
}
104115
if err = s.RevokeByNotification(inst); err != nil {
105116
return wrapErrors(err)
@@ -116,10 +127,26 @@ func RevocationOwnerNotif(c echo.Context) error {
116127
if err != nil {
117128
return wrapErrors(err)
118129
}
119-
member, err := requestMember(c, s)
130+
131+
// For Drive sharings, FindMemberByInteractCode validates the token and finds the member.
132+
// For non-Drive sharings, we check permissions first, then get the member via OAuth.
133+
var member *sharing.Member
134+
if s.Drive {
135+
token := getBearerToken(c)
136+
if token == "" {
137+
return echo.NewHTTPError(http.StatusForbidden)
138+
}
139+
member, err = s.FindMemberByInteractCode(inst, token)
140+
} else {
141+
if err := hasSharingWritePermissions(c); err != nil {
142+
return err
143+
}
144+
member, err = requestMember(c, s)
145+
}
120146
if err != nil {
121147
return wrapErrors(err)
122148
}
149+
123150
if err = s.RevokeRecipientByNotification(inst, member); err != nil {
124151
return wrapErrors(err)
125152
}

web/sharings/sharings.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ func Routes(router *echo.Group) {
10191019
router.DELETE("/:sharing-id/recipients/self/readonly", UpgradeToReadWrite, checkSharingWritePermissions) // On the recipient
10201020
router.DELETE("/:sharing-id", RevocationRecipientNotif) // On the recipient
10211021
router.DELETE("/:sharing-id/recipients/self", RevokeRecipientBySelf) // On the recipient
1022-
router.DELETE("/:sharing-id/answer", RevocationOwnerNotif, checkSharingWritePermissions) // On the sharer
1022+
router.DELETE("/:sharing-id/answer", RevocationOwnerNotif)
10231023
router.POST("/:sharing-id/public-key", ReceivePublicKey)
10241024

10251025
// Delegated routes for open sharing

0 commit comments

Comments
 (0)