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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
|
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gtsmodel
// A policy URI is GoToSocial's internal representation of
// one ActivityPub URI for an Actor or a Collection of Actors,
// specific to the domain of enforcing interaction policies.
//
// A PolicyValue can be stored in the database either as one
// of the Value constants defined below (to save space), OR as
// a full-fledged ActivityPub URI.
//
// A PolicyValue should be translated to the canonical string
// value of the represented URI when federating an item, or
// from the canonical string value of the URI when receiving
// or retrieving an item.
//
// For example, if the PolicyValue `followers` was being
// federated outwards in an interaction policy attached to an
// item created by the actor `https://example.org/users/someone`,
// then it should be translated to their followers URI when sent,
// eg., `https://example.org/users/someone/followers`.
//
// Likewise, if GoToSocial receives an item with an interaction
// policy containing `https://example.org/users/someone/followers`,
// and the item was created by `https://example.org/users/someone`,
// then the followers URI would be converted to `followers`
// for internal storage.
type PolicyValue string
const (
// Stand-in for ActivityPub magic public URI,
// which encompasses every possible Actor URI.
PolicyValuePublic PolicyValue = "public"
// Stand-in for the Followers Collection of
// the item owner's Actor.
PolicyValueFollowers PolicyValue = "followers"
// Stand-in for the Following Collection of
// the item owner's Actor.
PolicyValueFollowing PolicyValue = "following"
// Stand-in for the Mutuals Collection of
// the item owner's Actor.
//
// (TODO: Reserved, currently unused).
PolicyValueMutuals PolicyValue = "mutuals"
// Stand-in for Actor URIs tagged in the item.
PolicyValueMentioned PolicyValue = "mentioned"
// Stand-in for the Actor URI of the item owner.
PolicyValueAuthor PolicyValue = "author"
)
// FeasibleForVisibility returns true if the PolicyValue could feasibly
// be set in a policy for an item with the given visibility, otherwise
// returns false.
//
// For example, PolicyValuePublic could not be set in a policy for an
// item with visibility FollowersOnly, but could be set in a policy
// for an item with visibility Public or Unlocked.
//
// This is not prescriptive, and should be used only to guide policy
// choices. Eg., if a remote instance wants to do something wacky like
// set "anyone can interact with this status" for a Direct visibility
// status, that's their business; our normal visibility filtering will
// prevent users on our instance from actually being able to interact
// unless they can see the status anyway.
func (p PolicyValue) FeasibleForVisibility(v Visibility) bool {
switch p {
// Mentioned and self Values are
// feasible for any visibility.
case PolicyValueAuthor,
PolicyValueMentioned:
return true
// Followers/following/mutual Values
// are only feasible for items with
// followers visibility and higher.
case PolicyValueFollowers,
PolicyValueFollowing:
return v == VisibilityFollowersOnly ||
v == VisibilityPublic ||
v == VisibilityUnlocked
// Public policy Value only feasible
// for items that are To or CC public.
case PolicyValuePublic:
return v == VisibilityUnlocked ||
v == VisibilityPublic
// Any other combo
// is probably fine.
default:
return true
}
}
type PolicyValues []PolicyValue
// PolicyPermission represents the permission
// state for a certain Actor URI and interaction
// type, in relation to a policy.
type PolicyPermission int
const (
// Interaction is forbidden for this
// PolicyValue + interaction combination.
PolicyPermissionForbidden PolicyPermission = iota
// Interaction is conditionally permitted
// for this PolicyValue + interaction combo,
// pending approval by the item owner.
PolicyPermissionWithApproval
// Interaction is permitted for this
// PolicyValue + interaction combination.
PolicyPermissionPermitted
)
// PolicyCheckResult encapsulates the results
// of checking a certain Actor URI + type
// of interaction against an interaction policy.
type PolicyCheckResult struct {
// Permission permitted /
// with approval / forbidden.
Permission PolicyPermission
// Value that this check matched on.
// Only set if Permission = permitted.
PermittedMatchedOn *PolicyValue
}
// MatchedOnCollection returns true if this policy check
// result turned up Permitted, and matched based on the
// requester's presence in a followers or following collection.
func (pcr *PolicyCheckResult) MatchedOnCollection() bool {
if !pcr.Permitted() {
// Not permitted at all
// so definitely didn't
// match on collection.
return false
}
if pcr.PermittedMatchedOn == nil {
return false
}
return *pcr.PermittedMatchedOn == PolicyValueFollowers ||
*pcr.PermittedMatchedOn == PolicyValueFollowing
}
// Permitted returns true if this policy
// check resulted in Permission = permitted.
func (pcr *PolicyCheckResult) Permitted() bool {
return pcr.Permission == PolicyPermissionPermitted
}
// Permitted returns true if this policy
// check resulted in Permission = with approval.
func (pcr *PolicyCheckResult) WithApproval() bool {
return pcr.Permission == PolicyPermissionWithApproval
}
// Permitted returns true if this policy
// check resulted in Permission = forbidden.
func (pcr *PolicyCheckResult) Forbidden() bool {
return pcr.Permission == PolicyPermissionForbidden
}
// An InteractionPolicy determines which
// interactions will be accepted for an
// item, and according to what rules.
type InteractionPolicy struct {
// Conditions in which a Like
// interaction will be accepted
// for an item with this policy.
CanLike PolicyRules
// Conditions in which a Reply
// interaction will be accepted
// for an item with this policy.
CanReply PolicyRules
// Conditions in which an Announce
// interaction will be accepted
// for an item with this policy.
CanAnnounce PolicyRules
}
// PolicyRules represents the rules according
// to which a certain interaction is permitted
// to various Actor and Actor Collection URIs.
type PolicyRules struct {
// Always is for PolicyValues who are
// permitted to do an interaction
// without requiring approval.
Always PolicyValues
// WithApproval is for PolicyValues who
// are conditionally permitted to do
// an interaction, pending approval.
WithApproval PolicyValues
}
// Returns the default interaction policy
// for the given visibility level.
func DefaultInteractionPolicyFor(v Visibility) *InteractionPolicy {
switch v {
case VisibilityPublic:
return DefaultInteractionPolicyPublic()
case VisibilityUnlocked:
return DefaultInteractionPolicyUnlocked()
case VisibilityFollowersOnly, VisibilityMutualsOnly:
return DefaultInteractionPolicyFollowersOnly()
case VisibilityDirect:
return DefaultInteractionPolicyDirect()
default:
panic("invalid visibility")
}
}
var defaultPolicyPublic = &InteractionPolicy{
CanLike: PolicyRules{
// Anyone can like.
Always: PolicyValues{
PolicyValuePublic,
},
WithApproval: make(PolicyValues, 0),
},
CanReply: PolicyRules{
// Anyone can reply.
Always: PolicyValues{
PolicyValuePublic,
},
WithApproval: make(PolicyValues, 0),
},
CanAnnounce: PolicyRules{
// Anyone can announce.
Always: PolicyValues{
PolicyValuePublic,
},
WithApproval: make(PolicyValues, 0),
},
}
// Returns the default interaction policy
// for a post with visibility of public.
func DefaultInteractionPolicyPublic() *InteractionPolicy {
return defaultPolicyPublic
}
// Returns the default interaction policy
// for a post with visibility of unlocked.
func DefaultInteractionPolicyUnlocked() *InteractionPolicy {
// Same as public (for now).
return defaultPolicyPublic
}
var defaultPolicyFollowersOnly = &InteractionPolicy{
CanLike: PolicyRules{
// Self, followers and
// mentioned can like.
Always: PolicyValues{
PolicyValueAuthor,
PolicyValueFollowers,
PolicyValueMentioned,
},
WithApproval: make(PolicyValues, 0),
},
CanReply: PolicyRules{
// Self, followers and
// mentioned can reply.
Always: PolicyValues{
PolicyValueAuthor,
PolicyValueFollowers,
PolicyValueMentioned,
},
WithApproval: make(PolicyValues, 0),
},
CanAnnounce: PolicyRules{
// Only self can announce.
Always: PolicyValues{
PolicyValueAuthor,
},
WithApproval: make(PolicyValues, 0),
},
}
// Returns the default interaction policy for
// a post with visibility of followers only.
func DefaultInteractionPolicyFollowersOnly() *InteractionPolicy {
return defaultPolicyFollowersOnly
}
var defaultPolicyDirect = &InteractionPolicy{
CanLike: PolicyRules{
// Mentioned and self
// can always like.
Always: PolicyValues{
PolicyValueAuthor,
PolicyValueMentioned,
},
WithApproval: make(PolicyValues, 0),
},
CanReply: PolicyRules{
// Mentioned and self
// can always reply.
Always: PolicyValues{
PolicyValueAuthor,
PolicyValueMentioned,
},
WithApproval: make(PolicyValues, 0),
},
CanAnnounce: PolicyRules{
// Only self can announce.
Always: PolicyValues{
PolicyValueAuthor,
},
WithApproval: make(PolicyValues, 0),
},
}
// Returns the default interaction policy
// for a post with visibility of direct.
func DefaultInteractionPolicyDirect() *InteractionPolicy {
return defaultPolicyDirect
}
|