diff options
Diffstat (limited to 'internal/gtsmodel')
-rw-r--r-- | internal/gtsmodel/accountsettings.go | 27 | ||||
-rw-r--r-- | internal/gtsmodel/interactionapproval.go | 55 | ||||
-rw-r--r-- | internal/gtsmodel/interactionpolicy.go | 314 | ||||
-rw-r--r-- | internal/gtsmodel/notification.go | 19 | ||||
-rw-r--r-- | internal/gtsmodel/status.go | 6 | ||||
-rw-r--r-- | internal/gtsmodel/statusfave.go | 2 |
6 files changed, 401 insertions, 22 deletions
diff --git a/internal/gtsmodel/accountsettings.go b/internal/gtsmodel/accountsettings.go index 109d90ad9..592a2330d 100644 --- a/internal/gtsmodel/accountsettings.go +++ b/internal/gtsmodel/accountsettings.go @@ -21,15 +21,20 @@ import "time" // AccountSettings models settings / preferences for a local, non-instance account. type AccountSettings struct { - AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // AccountID that owns this settings. - CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created. - UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated. - Privacy Visibility `bun:",nullzero"` // Default post privacy for this account - Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default? - Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in? - StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts). - Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set). - CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses. - EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed - HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections. + AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // AccountID that owns this settings. + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created. + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated. + Privacy Visibility `bun:",nullzero"` // Default post privacy for this account + Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default? + Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in? + StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts). + Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set). + CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses. + EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed + HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections. + InteractionPolicyDirect *InteractionPolicy `bun:""` // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy. + InteractionPolicyMutualsOnly *InteractionPolicy `bun:""` // Interaction policy to use for new mutuals only visibility statuses. If null, assume default policy. + InteractionPolicyFollowersOnly *InteractionPolicy `bun:""` // Interaction policy to use for new followers only visibility statuses. If null, assume default policy. + InteractionPolicyUnlocked *InteractionPolicy `bun:""` // Interaction policy to use for new unlocked visibility statuses. If null, assume default policy. + InteractionPolicyPublic *InteractionPolicy `bun:""` // Interaction policy to use for new public visibility statuses. If null, assume default policy. } diff --git a/internal/gtsmodel/interactionapproval.go b/internal/gtsmodel/interactionapproval.go new file mode 100644 index 000000000..f6a5da83b --- /dev/null +++ b/internal/gtsmodel/interactionapproval.go @@ -0,0 +1,55 @@ +// 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 + +import "time" + +// InteractionApproval refers to a single Accept activity sent +// *from this instance* in response to an interaction request, +// in order to approve it. +// +// Accepts originating from remote instances are not stored +// using this format; the URI of the remote Accept is instead +// just added to the *gtsmodel.StatusFave or *gtsmodel.Status. +type InteractionApproval struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated + AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id of the account that owns this accept/approval + Account *Account `bun:"-"` // account corresponding to accountID + InteractingAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id of the account that did the interaction that this Accept targets. + InteractingAccount *Account `bun:"-"` // account corresponding to targetAccountID + InteractionURI string `bun:",nullzero,notnull"` // URI of the target like, reply, or announce + InteractionType InteractionType `bun:",notnull"` // One of Like, Reply, or Announce. + URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI of the Accept. +} + +// Like / Reply / Announce +type InteractionType int + +const ( + // WARNING: DO NOT CHANGE THE ORDER OF THESE, + // as this will cause breakage of approvals! + // + // If you need to add new interaction types, + // add them *to the end* of the list. + + InteractionLike InteractionType = iota + InteractionReply + InteractionAnnounce +) diff --git a/internal/gtsmodel/interactionpolicy.go b/internal/gtsmodel/interactionpolicy.go new file mode 100644 index 000000000..ecb525b47 --- /dev/null +++ b/internal/gtsmodel/interactionpolicy.go @@ -0,0 +1,314 @@ +// 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 + +// PolicyResult represents the result of +// checking an Actor URI and interaction +// type against the conditions of an +// InteractionPolicy to determine if that +// interaction is permitted. +type PolicyResult int + +const ( + // Interaction is forbidden for this + // PolicyValue + interaction combination. + PolicyResultForbidden PolicyResult = iota + // Interaction is conditionally permitted + // for this PolicyValue + interaction combo, + // pending approval by the item owner. + PolicyResultWithApproval + // Interaction is permitted for this + // PolicyValue + interaction combination. + PolicyResultPermitted +) + +// 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("visibility " + v + " not recognized") + } +} + +// Returns the default interaction policy +// for a post with visibility of public. +func DefaultInteractionPolicyPublic() *InteractionPolicy { + // Anyone can like. + canLikeAlways := make(PolicyValues, 1) + canLikeAlways[0] = PolicyValuePublic + + // Unused, set empty. + canLikeWithApproval := make(PolicyValues, 0) + + // Anyone can reply. + canReplyAlways := make(PolicyValues, 1) + canReplyAlways[0] = PolicyValuePublic + + // Unused, set empty. + canReplyWithApproval := make(PolicyValues, 0) + + // Anyone can announce. + canAnnounceAlways := make(PolicyValues, 1) + canAnnounceAlways[0] = PolicyValuePublic + + // Unused, set empty. + canAnnounceWithApproval := make(PolicyValues, 0) + + return &InteractionPolicy{ + CanLike: PolicyRules{ + Always: canLikeAlways, + WithApproval: canLikeWithApproval, + }, + CanReply: PolicyRules{ + Always: canReplyAlways, + WithApproval: canReplyWithApproval, + }, + CanAnnounce: PolicyRules{ + Always: canAnnounceAlways, + WithApproval: canAnnounceWithApproval, + }, + } +} + +// Returns the default interaction policy +// for a post with visibility of unlocked. +func DefaultInteractionPolicyUnlocked() *InteractionPolicy { + // Same as public (for now). + return DefaultInteractionPolicyPublic() +} + +// Returns the default interaction policy for +// a post with visibility of followers only. +func DefaultInteractionPolicyFollowersOnly() *InteractionPolicy { + // Self, followers and mentioned can like. + canLikeAlways := make(PolicyValues, 3) + canLikeAlways[0] = PolicyValueAuthor + canLikeAlways[1] = PolicyValueFollowers + canLikeAlways[2] = PolicyValueMentioned + + // Unused, set empty. + canLikeWithApproval := make(PolicyValues, 0) + + // Self, followers and mentioned can reply. + canReplyAlways := make(PolicyValues, 3) + canReplyAlways[0] = PolicyValueAuthor + canReplyAlways[1] = PolicyValueFollowers + canReplyAlways[2] = PolicyValueMentioned + + // Unused, set empty. + canReplyWithApproval := make(PolicyValues, 0) + + // Only self can announce. + canAnnounceAlways := make(PolicyValues, 1) + canAnnounceAlways[0] = PolicyValueAuthor + + // Unused, set empty. + canAnnounceWithApproval := make(PolicyValues, 0) + + return &InteractionPolicy{ + CanLike: PolicyRules{ + Always: canLikeAlways, + WithApproval: canLikeWithApproval, + }, + CanReply: PolicyRules{ + Always: canReplyAlways, + WithApproval: canReplyWithApproval, + }, + CanAnnounce: PolicyRules{ + Always: canAnnounceAlways, + WithApproval: canAnnounceWithApproval, + }, + } +} + +// Returns the default interaction policy +// for a post with visibility of direct. +func DefaultInteractionPolicyDirect() *InteractionPolicy { + // Mentioned and self can always like. + canLikeAlways := make(PolicyValues, 2) + canLikeAlways[0] = PolicyValueAuthor + canLikeAlways[1] = PolicyValueMentioned + + // Unused, set empty. + canLikeWithApproval := make(PolicyValues, 0) + + // Mentioned and self can always reply. + canReplyAlways := make(PolicyValues, 2) + canReplyAlways[0] = PolicyValueAuthor + canReplyAlways[1] = PolicyValueMentioned + + // Unused, set empty. + canReplyWithApproval := make(PolicyValues, 0) + + // Only self can announce. + canAnnounceAlways := make(PolicyValues, 1) + canAnnounceAlways[0] = PolicyValueAuthor + + // Unused, set empty. + canAnnounceWithApproval := make(PolicyValues, 0) + + return &InteractionPolicy{ + CanLike: PolicyRules{ + Always: canLikeAlways, + WithApproval: canLikeWithApproval, + }, + CanReply: PolicyRules{ + Always: canReplyAlways, + WithApproval: canReplyWithApproval, + }, + CanAnnounce: PolicyRules{ + Always: canAnnounceAlways, + WithApproval: canAnnounceWithApproval, + }, + } +} diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go index 0f946ed0f..5cf6b061a 100644 --- a/internal/gtsmodel/notification.go +++ b/internal/gtsmodel/notification.go @@ -39,12 +39,15 @@ type NotificationType string // Notification Types const ( - NotificationFollow NotificationType = "follow" // NotificationFollow -- someone followed you - NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you - NotificationMention NotificationType = "mention" // NotificationMention -- someone mentioned you in their status - NotificationReblog NotificationType = "reblog" // NotificationReblog -- someone boosted one of your statuses - NotificationFave NotificationType = "favourite" // NotificationFave -- someone faved/liked one of your statuses - NotificationPoll NotificationType = "poll" // NotificationPoll -- a poll you voted in or created has ended - NotificationStatus NotificationType = "status" // NotificationStatus -- someone you enabled notifications for has posted a status. - NotificationSignup NotificationType = "admin.sign_up" // NotificationSignup -- someone has submitted a new account sign-up to the instance. + NotificationFollow NotificationType = "follow" // NotificationFollow -- someone followed you + NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you + NotificationMention NotificationType = "mention" // NotificationMention -- someone mentioned you in their status + NotificationReblog NotificationType = "reblog" // NotificationReblog -- someone boosted one of your statuses + NotificationFave NotificationType = "favourite" // NotificationFave -- someone faved/liked one of your statuses + NotificationPoll NotificationType = "poll" // NotificationPoll -- a poll you voted in or created has ended + NotificationStatus NotificationType = "status" // NotificationStatus -- someone you enabled notifications for has posted a status. + NotificationSignup NotificationType = "admin.sign_up" // NotificationSignup -- someone has submitted a new account sign-up to the instance. + NotificationPendingFave NotificationType = "pending.favourite" // Someone has faved a status of yours, which requires approval by you. + NotificationPendingReply NotificationType = "pending.reply" // Someone has replied to a status of yours, which requires approval by you. + NotificationPendingReblog NotificationType = "pending.reblog" // Someone has boosted a status of yours, which requires approval by you. ) diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index 3bbe82c08..221663ccd 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -66,9 +66,9 @@ type Status struct { ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!. Text string `bun:""` // Original text of the status without formatting Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s) - Boostable *bool `bun:",notnull"` // This status can be boosted/reblogged - Replyable *bool `bun:",notnull"` // This status can be replied to - Likeable *bool `bun:",notnull"` // This status can be liked/faved + InteractionPolicy *InteractionPolicy `bun:""` // InteractionPolicy for this status. If null then the default InteractionPolicy should be assumed for this status's Visibility. Always null for boost wrappers. + PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed. + ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to. } // GetID implements timeline.Timelineable{}. diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go index f81226f8b..644b3ca63 100644 --- a/internal/gtsmodel/statusfave.go +++ b/internal/gtsmodel/statusfave.go @@ -31,4 +31,6 @@ type StatusFave struct { StatusID string `bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // database id of the status that has been 'faved' Status *Status `bun:"-"` // the faved status URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI of this fave + PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then Like must be Approved by the like-ee before being fully distributed. + ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves this Like. } |