-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnegating.go
More file actions
264 lines (248 loc) · 9.25 KB
/
negating.go
File metadata and controls
264 lines (248 loc) · 9.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package processing
import (
vocab "github.com/go-ap/activitypub"
"github.com/go-ap/errors"
)
// TODO(marius): add more valid types
var validUndoActivityTypes = vocab.ActivityVocabularyTypes{
vocab.CreateType, /* vocab.UndoType, vocab.DeleteType,*/
vocab.LikeType, vocab.DislikeType,
vocab.BlockType, vocab.FollowType,
vocab.AnnounceType,
}
// ValidateClientNegatingActivity
func (p P) ValidateClientNegatingActivity(act *vocab.Activity) error {
if vocab.IsNil(act.Object) {
return InvalidActivityObject("is nil")
}
if ob, err := p.DereferenceItem(act.Object); err != nil {
return err
} else {
act.Object = ob
}
return vocab.OnActivity(act.Object, func(objAct *vocab.Activity) error {
if !act.Actor.GetLink().Equals(objAct.Actor.GetLink(), false) {
return errors.NotValidf("The %s activity has a different actor than its object: %s, expected %s", act.Type, act.Actor.GetLink(), objAct.Actor.GetLink())
}
if !validUndoActivityTypes.Match(objAct.Type) {
return errors.NotValidf("Object Activity has wrong type %s, expected one of %v", objAct.Type, validUndoActivityTypes)
}
return nil
})
}
// NegatingActivity processes matching activities
//
// https://www.w3.org/TR/activitystreams-vocabulary/#h-motivations-undo
//
// The Negating Activity use case primarily deals with the ability to redact previously completed activities.
// See 5.5 Inverse Activities and "Undo" for more information:
// https://www.w3.org/TR/activitystreams-vocabulary/#inverse
func (p P) NegatingActivity(act *vocab.Activity) (*vocab.Activity, error) {
if vocab.IsNil(act.Object) {
return act, errors.NotValidf("Missing object for %s Activity", act.Type)
}
if vocab.IsNil(act.Actor) {
return act, errors.NotValidf("Missing actor for %s Activity", act.Type)
}
if !vocab.UndoType.Match(act.Type) {
return act, errors.NotValidf("Activity has wrong type %s, expected %s", act.Type, vocab.UndoType)
}
return p.UndoActivity(act)
}
// UndoActivity
//
// https://www.w3.org/TR/activitypub/#undo-activity-outbox
//
// The Undo activity is used to undo a previous activity. See the Activity Vocabulary documentation on
// Inverse Activities and "Undo". For example, Undo may be used to undo a previous Like, Follow, or Block.
// The undo activity and the activity being undone MUST both have the same actor.
// Side effects should be undone, to the extent possible. For example, if undoing a Like, any counter that had been
// incremented previously should be decremented appropriately.
// There are some exceptions where there is an existing and explicit "inverse activity" which should be used instead.
// Create based activities should instead use Delete, and Add activities should use Remove.
//
// https://www.w3.org/TR/activitypub/#undo-activity-inbox
//
// The Undo activity is used to undo the side effects of previous activities. See the ActivityStreams documentation
// on Inverse Activities and "Undo". The scope and restrictions of the Undo activity are the same as for the Undo
// activity in the context of client to server interactions, but applied to a federated context.
func (p P) UndoActivity(act *vocab.Activity) (*vocab.Activity, error) {
var err error
iri := act.GetLink()
if len(iri) == 0 {
iri, _ = p.createIDFn(act.Object, vocab.Outbox.IRI(act.Actor), nil)
}
err = vocab.OnActivity(act.Object, func(toUndo *vocab.Activity) error {
for _, to := range act.Bto {
if !toUndo.Bto.Contains(to.GetLink()) {
toUndo.Bto = append(toUndo.Bto, to)
}
}
for _, to := range act.BCC {
if !toUndo.BCC.Contains(to.GetLink()) {
toUndo.BCC = append(toUndo.BCC, to)
}
}
typ := toUndo.GetType()
switch {
case vocab.CreateType.Match(typ):
_, err = p.UndoCreateActivity(toUndo)
case vocab.DislikeType.Match(typ):
// TODO(marius): Dislikes should not trigger a removal from Likes/Liked collections
fallthrough
case vocab.LikeType.Match(typ):
_, err = p.UndoAppreciationActivity(toUndo)
case vocab.FollowType.Match(typ):
fallthrough
case vocab.BlockType.Match(typ):
fallthrough
case vocab.IgnoreType.Match(typ):
fallthrough
case vocab.FlagType.Match(typ):
_, err = p.UndoRelationshipManagementActivity(toUndo)
case vocab.AnnounceType.Match(typ):
_, err = p.UndoAnnounceActivity(toUndo)
}
return err
})
if err != nil {
return act, err
}
if p.IsLocal(act.Object) {
// NOTE(marius): remove the activity that we operated Undo on
if err = p.s.Delete(act.Object.GetLink()); err != nil && !errors.IsNotFound(err) {
return act, err
}
}
return act, nil
}
// UndoCreateActivity
//
// Removes the side effects of an existing Create activity
// Currently this means only removal of the Create object
func (p P) UndoCreateActivity(act *vocab.Activity) (*vocab.Activity, error) {
errs := make([]error, 0)
rem := act.GetLink()
allRec := act.Recipients()
removeFromCols := make(vocab.IRIs, 0)
if p.IsLocal(act.Actor) {
removeFromCols = append(removeFromCols, vocab.Outbox.IRI(act.Actor))
}
for _, rec := range allRec {
recIRI := rec.GetLink()
if recIRI == vocab.PublicNS || !p.IsLocalIRI(recIRI) {
continue
}
if !vocab.ValidCollectionIRI(recIRI) {
// if not a valid collection, then the current recIRI represents an actor, and we need their inbox
removeFromCols = append(removeFromCols, vocab.Inbox.IRI(recIRI))
}
}
for _, iri := range removeFromCols {
if err := p.s.RemoveFrom(iri, rem); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return act, errors.Annotatef(errors.Join(errs...), "failed to fully process Undo activity")
}
if p.IsLocal(act.Object) {
if err := p.s.Delete(act.Object.GetLink()); err != nil {
return act, nil
}
}
return act, nil
}
// UndoAppreciationActivity
//
// Removes the side effects of an existing Appreciation activity (Like or Dislike)
// Currently this means only removal of the Liked/Disliked object from the actor's `liked` collection and
// removal of the Like/Dislike Activity from the object's `likes` collection
func (p P) UndoAppreciationActivity(act *vocab.Activity) (*vocab.Activity, error) {
errs := make([]error, 0)
rem := act.GetLink()
allRec := act.Recipients()
removeFromCols := make(vocab.IRIs, 0)
if p.IsLocal(act.Actor) {
removeFromCols = append(removeFromCols, vocab.Outbox.IRI(act.Actor))
removeFromCols = append(removeFromCols, vocab.Liked.IRI(act.Actor))
}
if p.IsLocal(act.Object) {
removeFromCols = append(removeFromCols, vocab.Likes.IRI(act.Object))
}
for _, rec := range allRec {
recIRI := rec.GetLink()
if recIRI == vocab.PublicNS || !p.IsLocalIRI(recIRI) {
continue
}
if !vocab.ValidCollectionIRI(recIRI) {
// if not a valid collection, then the current recIRI represents an actor, and we need their inbox
removeFromCols = append(removeFromCols, vocab.Inbox.IRI(recIRI))
}
}
for _, iri := range removeFromCols {
if err := p.s.RemoveFrom(iri, rem); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return act, errors.Annotatef(errors.Join(errs...), "failed to fully process Undo activity")
}
return act, nil
}
// UndoRelationshipManagementActivity
//
// Removes the side effects of an existing RelationshipActivity activity (Follow, Block, Ignore, Flag)
// Currently this means the removal of the object from the collection corresponding to the original Activity type.
// Follow - removes the original object from the actor's followers collection.
// Block - removes the original object from the actor's blocked collection.
// Ignore - removes the original object from the actor's ignored collection.
// Flag - is a special case where there isn't a specific collection that needs to be operated on.
func (p *P) UndoRelationshipManagementActivity(toUndo *vocab.Activity) (*vocab.Activity, error) {
errs := make([]error, 0)
rem := toUndo.GetLink()
// NOTE(marius): we need to remove the toUndo activity from the Outbox of its actor.
if err := p.s.RemoveFrom(vocab.Outbox.Of(toUndo.Actor).GetLink(), rem); err != nil {
errs = append(errs, err)
}
// NOTE(marius): for all recipients we need to remove the activity from their Inbox'es.
for _, rec := range toUndo.Recipients() {
removeFrom := rec.GetLink()
if removeFrom == vocab.PublicNS || !p.IsLocalIRI(removeFrom) {
continue
}
if !vocab.ValidCollectionIRI(removeFrom) {
// NOTE(marius): if recipient is not a valid collection,
// then the current iri represents an actor, and we try to get their Inbox
removeFrom = vocab.Inbox.Of(rec).GetLink()
}
if err := p.s.RemoveFrom(removeFrom, rem); err != nil {
errs = append(errs, err)
}
}
removeFromCols := make(vocab.IRIs, 0)
switch toUndo.GetType() {
case vocab.FollowType:
if colIRI := vocab.Following.Of(toUndo.Actor).GetLink(); p.IsLocalIRI(colIRI) {
removeFromCols = append(removeFromCols, colIRI)
}
case vocab.BlockType:
if colIRI := BlockedCollection.Of(toUndo.Actor).GetLink(); p.IsLocalIRI(colIRI) {
removeFromCols = append(removeFromCols, colIRI)
}
case vocab.IgnoreType:
if colIRI := IgnoredCollection.Of(toUndo.Actor).GetLink(); p.IsLocalIRI(colIRI) {
removeFromCols = append(removeFromCols, colIRI)
}
}
rem = toUndo.Object.GetLink()
for _, iri := range removeFromCols {
if err := p.s.RemoveFrom(iri, rem); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return toUndo, errors.Annotatef(errors.Join(errs...), "failed to fully process Undo activity")
}
return toUndo, nil
}