// 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 . 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, }, } }