mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-23 17:26:20 -06:00
[feature] Forward-compatibility with Approval objects
This commit is contained in:
parent
f9ff61ae4d
commit
1d831b7a3b
5 changed files with 449 additions and 123 deletions
|
|
@ -97,6 +97,11 @@ const (
|
||||||
// Not in the AS spec, just used internally to indicate
|
// Not in the AS spec, just used internally to indicate
|
||||||
// that we don't *yet* know what type of Object something is.
|
// that we don't *yet* know what type of Object something is.
|
||||||
ObjectUnknown = "Unknown"
|
ObjectUnknown = "Unknown"
|
||||||
|
|
||||||
|
// Extensions and unofficial additions.
|
||||||
|
ObjectLikeApproval = "LikeApproval"
|
||||||
|
ObjectReplyApproval = "ReplyApproval"
|
||||||
|
ObjectAnnounceApproval = "AnnounceApproval"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isActivity returns whether AS type name is of an Activity (NOT IntransitiveActivity).
|
// isActivity returns whether AS type name is of an Activity (NOT IntransitiveActivity).
|
||||||
|
|
|
||||||
|
|
@ -128,16 +128,13 @@ func ToPollOptionable(t vocab.Type) (PollOptionable, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAccept returns whether AS vocab type name
|
// IsAccept returns whether AS vocab type name
|
||||||
// is something that can be cast to Accept.
|
// is something that can be cast to Acceptable.
|
||||||
func IsAcceptable(typeName string) bool {
|
func IsAcceptable(typeName string) bool {
|
||||||
return typeName == ActivityAccept
|
return typeName == ActivityAccept
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToAcceptable safely tries to cast vocab.Type as vocab.ActivityStreamsAccept.
|
// ToAcceptable safely tries to cast vocab.Type as Acceptable.
|
||||||
//
|
func ToAcceptable(t vocab.Type) (Acceptable, bool) {
|
||||||
// TODO: Add additional "Accept" types here, eg., "ApproveReply" from
|
|
||||||
// https://codeberg.org/fediverse/fep/src/branch/main/fep/5624/fep-5624.md
|
|
||||||
func ToAcceptable(t vocab.Type) (vocab.ActivityStreamsAccept, bool) {
|
|
||||||
acceptable, ok := t.(vocab.ActivityStreamsAccept)
|
acceptable, ok := t.(vocab.ActivityStreamsAccept)
|
||||||
if !ok || !IsAcceptable(t.GetTypeName()) {
|
if !ok || !IsAcceptable(t.GetTypeName()) {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
|
@ -145,6 +142,28 @@ func ToAcceptable(t vocab.Type) (vocab.ActivityStreamsAccept, bool) {
|
||||||
return acceptable, true
|
return acceptable, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsApprovable returns whether AS vocab type name
|
||||||
|
// is something that can be cast to Approvable.
|
||||||
|
func IsApprovable(typeName string) bool {
|
||||||
|
switch typeName {
|
||||||
|
case ObjectLikeApproval,
|
||||||
|
ObjectReplyApproval,
|
||||||
|
ObjectAnnounceApproval:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToAcceptable safely tries to cast vocab.Type as Approvable.
|
||||||
|
func ToApprovable(t vocab.Type) (Approvable, bool) {
|
||||||
|
approvable, ok := t.(Approvable)
|
||||||
|
if !ok || !IsApprovable(t.GetTypeName()) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return approvable, true
|
||||||
|
}
|
||||||
|
|
||||||
// Activityable represents the minimum activitypub interface for representing an 'activity'.
|
// Activityable represents the minimum activitypub interface for representing an 'activity'.
|
||||||
// (see: IsActivityable() for types implementing this, though you MUST make sure to check
|
// (see: IsActivityable() for types implementing this, though you MUST make sure to check
|
||||||
// the typeName as this bare interface may be implementable by non-Activityable types).
|
// the typeName as this bare interface may be implementable by non-Activityable types).
|
||||||
|
|
@ -247,6 +266,19 @@ type PollOptionable interface {
|
||||||
// interface for representing an Accept.
|
// interface for representing an Accept.
|
||||||
type Acceptable interface {
|
type Acceptable interface {
|
||||||
Activityable
|
Activityable
|
||||||
|
|
||||||
|
WithTarget
|
||||||
|
WithResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approvable represents the minimum activitypub interface
|
||||||
|
// for a LikeApproval, ReplyApproval, or AnnounceApproval.
|
||||||
|
type Approvable interface {
|
||||||
|
vocab.Type
|
||||||
|
|
||||||
|
WithAttributedTo
|
||||||
|
WithObject
|
||||||
|
WithTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. (see: IsAttachmentable).
|
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. (see: IsAttachmentable).
|
||||||
|
|
@ -708,3 +740,9 @@ type WithApprovedBy interface {
|
||||||
GetGoToSocialApprovedBy() vocab.GoToSocialApprovedByProperty
|
GetGoToSocialApprovedBy() vocab.GoToSocialApprovedByProperty
|
||||||
SetGoToSocialApprovedBy(vocab.GoToSocialApprovedByProperty)
|
SetGoToSocialApprovedBy(vocab.GoToSocialApprovedByProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithVotersCount represents an activity or object the result property.
|
||||||
|
type WithResult interface {
|
||||||
|
GetActivityStreamsResult() vocab.ActivityStreamsResultProperty
|
||||||
|
SetActivityStreamsResult(vocab.ActivityStreamsResultProperty)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,48 +198,12 @@ func ResolveCollectionPage(ctx context.Context, body io.ReadCloser) (CollectionP
|
||||||
return ToCollectionPageIterator(t)
|
return ToCollectionPageIterator(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveAcceptable tries to resolve the given reader
|
|
||||||
// into an ActivityStreams Acceptable representation.
|
|
||||||
func ResolveAcceptable(
|
|
||||||
ctx context.Context,
|
|
||||||
body io.ReadCloser,
|
|
||||||
) (Acceptable, error) {
|
|
||||||
// Get "raw" map
|
|
||||||
// destination.
|
|
||||||
raw := getMap()
|
|
||||||
// Release.
|
|
||||||
defer putMap(raw)
|
|
||||||
|
|
||||||
// Decode data as JSON into 'raw' map
|
|
||||||
// and get the resolved AS vocab.Type.
|
|
||||||
// (this handles close of given body).
|
|
||||||
t, err := decodeType(ctx, body, raw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, gtserror.SetWrongType(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to cast as acceptable.
|
|
||||||
acceptable, ok := ToAcceptable(t)
|
|
||||||
if !ok {
|
|
||||||
err := gtserror.Newf("cannot resolve vocab type %T as acceptable", t)
|
|
||||||
return nil, gtserror.SetWrongType(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return acceptable, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// emptydest is an empty JSON decode
|
// emptydest is an empty JSON decode
|
||||||
// destination useful for "noop" decodes
|
// destination useful for "noop" decodes
|
||||||
// to check underlying reader is empty.
|
// to check underlying reader is empty.
|
||||||
var emptydest = &struct{}{}
|
var emptydest = &struct{}{}
|
||||||
|
|
||||||
// decodeType tries to read and parse the data
|
// decodeType is the package-internal version of DecodeType.
|
||||||
// at provided io.ReadCloser as a JSON ActivityPub
|
|
||||||
// type, failing if not parseable as JSON or not
|
|
||||||
// resolveable as one of our known AS types.
|
|
||||||
//
|
|
||||||
// NOTE: this function handles closing
|
|
||||||
// given body when it is finished with.
|
|
||||||
//
|
//
|
||||||
// The given map pointer will also be populated with
|
// The given map pointer will also be populated with
|
||||||
// the 'raw' JSON data, for further processing.
|
// the 'raw' JSON data, for further processing.
|
||||||
|
|
@ -284,3 +248,23 @@ func decodeType(
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeType tries to read and parse the data
|
||||||
|
// at provided io.ReadCloser as a JSON ActivityPub
|
||||||
|
// type, failing if not parseable as JSON or not
|
||||||
|
// resolveable as one of our known AS types.
|
||||||
|
//
|
||||||
|
// NOTE: this function handles closing
|
||||||
|
// given body when it is finished with.
|
||||||
|
func DecodeType(
|
||||||
|
ctx context.Context,
|
||||||
|
body io.ReadCloser,
|
||||||
|
) (vocab.Type, error) {
|
||||||
|
// Get "raw" map
|
||||||
|
// destination.
|
||||||
|
raw := getMap()
|
||||||
|
// Release.
|
||||||
|
defer putMap(raw)
|
||||||
|
|
||||||
|
return decodeType(ctx, body, raw)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,10 +119,10 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
replyURI = reply.URI // Definitely set.
|
replyURI = reply.URI // Definitely set.
|
||||||
inReplyToURI = reply.InReplyToURI // Definitely set.
|
inReplyToURI = reply.InReplyToURI // Definitely set.
|
||||||
inReplyTo = reply.InReplyTo // Might not be set.
|
inReplyTo = reply.InReplyTo // Might not be set.
|
||||||
acceptIRI = reply.ApprovedByURI // Might not be set.
|
approvedByURI = reply.ApprovedByURI // Might not be set.
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if we have a stored interaction request for parent status.
|
// Check if we have a stored interaction request for parent status.
|
||||||
|
|
@ -165,7 +165,7 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
// If it was, and it doesn't now claim to
|
// If it was, and it doesn't now claim to
|
||||||
// be approved, then we should just reject it
|
// be approved, then we should just reject it
|
||||||
// again, as nothing's changed since last time.
|
// again, as nothing's changed since last time.
|
||||||
if thisRejected && acceptIRI == "" {
|
if thisRejected && approvedByURI == "" {
|
||||||
|
|
||||||
// Nothing changed,
|
// Nothing changed,
|
||||||
// still rejected.
|
// still rejected.
|
||||||
|
|
@ -224,16 +224,17 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
|
|
||||||
// If this reply claims to be approved,
|
// If this reply claims to be approved,
|
||||||
// validate this by dereferencing the
|
// validate this by dereferencing the
|
||||||
// Accept and checking the return value.
|
// approval and checking the return value.
|
||||||
// No further checks are required.
|
// No further checks are required.
|
||||||
if acceptIRI != "" {
|
if approvedByURI != "" {
|
||||||
return d.isPermittedByAcceptIRI(
|
return d.isPermittedByApprovedByIRI(
|
||||||
ctx,
|
ctx,
|
||||||
|
gtsmodel.InteractionReply,
|
||||||
requestUser,
|
requestUser,
|
||||||
reply,
|
reply,
|
||||||
inReplyTo,
|
inReplyTo,
|
||||||
thisReq,
|
thisReq,
|
||||||
acceptIRI,
|
approvedByURI,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,7 +270,7 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
// Reply is permitted and match was *not* made
|
// Reply is permitted and match was *not* made
|
||||||
// based on inclusion in a followers/following
|
// based on inclusion in a followers/following
|
||||||
// collection. Just permit the reply full stop
|
// collection. Just permit the reply full stop
|
||||||
// as no approval / accept URI is necessary.
|
// as no explicit approval is necessary.
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +286,7 @@ func (d *Dereferencer) isPermittedReply(
|
||||||
// we can't verify the presence of a remote account
|
// we can't verify the presence of a remote account
|
||||||
// in one of another remote account's collections.
|
// in one of another remote account's collections.
|
||||||
//
|
//
|
||||||
// It's possible we'll get an Accept from the replied-
|
// It's possible we'll get an approval from the replied-
|
||||||
// to account later, and we can store this reply then.
|
// to account later, and we can store this reply then.
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
@ -385,30 +386,36 @@ func (d *Dereferencer) unpermittedByParent(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isPermittedByAcceptIRI checks whether the given acceptIRI
|
// isPermittedByApprovedByIRI checks whether the given URI
|
||||||
// permits the given reply to the given inReplyTo status.
|
// can be dereferenced, and whether it returns either an
|
||||||
// If yes, then thisReq will be updated to reflect the
|
// Accept activity or an approval object which permits the
|
||||||
// acceptance, if it's not nil.
|
// given reply to the given inReplyTo status.
|
||||||
func (d *Dereferencer) isPermittedByAcceptIRI(
|
//
|
||||||
|
// If yes, then thisReq will be updated to
|
||||||
|
// reflect the approval, if it's not nil.
|
||||||
|
func (d *Dereferencer) isPermittedByApprovedByIRI(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
interactionType gtsmodel.InteractionType,
|
||||||
requestUser string,
|
requestUser string,
|
||||||
reply *gtsmodel.Status,
|
reply *gtsmodel.Status,
|
||||||
inReplyTo *gtsmodel.Status,
|
inReplyTo *gtsmodel.Status,
|
||||||
thisReq *gtsmodel.InteractionRequest,
|
thisReq *gtsmodel.InteractionRequest,
|
||||||
acceptIRI string,
|
approvedByIRI string,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
permitted, err := d.isValidAccept(
|
permitted, err := d.isValidApprovedByIRI(
|
||||||
ctx,
|
ctx,
|
||||||
|
interactionType,
|
||||||
requestUser,
|
requestUser,
|
||||||
acceptIRI,
|
approvedByIRI, // approval iri
|
||||||
reply.URI,
|
inReplyTo.AccountURI, // actor
|
||||||
inReplyTo.AccountURI,
|
reply.URI, // object
|
||||||
|
reply.InReplyToURI, // target
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Error dereferencing means we couldn't
|
// Error dereferencing means we couldn't
|
||||||
// get the Accept right now or it wasn't
|
// get the approval right now or it wasn't
|
||||||
// valid, so we shouldn't store this status.
|
// valid, so we shouldn't store this status.
|
||||||
err := gtserror.Newf("undereferencable ApprovedByURI: %w", err)
|
err := gtserror.Newf("undereferencable approvedByURI: %w", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -418,12 +425,12 @@ func (d *Dereferencer) isPermittedByAcceptIRI(
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply is permitted by this Accept.
|
// Reply is permitted by this approval.
|
||||||
// If it was previously rejected or
|
// If it was previously rejected or
|
||||||
// pending approval, clear that now.
|
// pending approval, clear that now.
|
||||||
reply.PendingApproval = util.Ptr(false)
|
reply.PendingApproval = util.Ptr(false)
|
||||||
if thisReq != nil {
|
if thisReq != nil {
|
||||||
thisReq.URI = acceptIRI
|
thisReq.URI = approvedByIRI
|
||||||
thisReq.AcceptedAt = time.Now()
|
thisReq.AcceptedAt = time.Now()
|
||||||
thisReq.RejectedAt = time.Time{}
|
thisReq.RejectedAt = time.Time{}
|
||||||
err := d.state.DB.UpdateInteractionRequest(
|
err := d.state.DB.UpdateInteractionRequest(
|
||||||
|
|
@ -576,7 +583,7 @@ func (d *Dereferencer) isPermittedBoost(
|
||||||
// permitted but matched on a collection.
|
// permitted but matched on a collection.
|
||||||
//
|
//
|
||||||
// Check if we can dereference
|
// Check if we can dereference
|
||||||
// an Accept that grants approval.
|
// an IRI that grants approval.
|
||||||
|
|
||||||
if status.ApprovedByURI == "" {
|
if status.ApprovedByURI == "" {
|
||||||
// Status doesn't claim to be approved.
|
// Status doesn't claim to be approved.
|
||||||
|
|
@ -602,18 +609,20 @@ func (d *Dereferencer) isPermittedBoost(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boost claims to be approved, check
|
// Boost claims to be approved, check
|
||||||
// this by dereferencing the Accept and
|
// this by dereferencing the approvedBy
|
||||||
// inspecting the return value.
|
// and inspecting the return value.
|
||||||
permitted, err := d.isValidAccept(
|
permitted, err := d.isValidApprovedByIRI(
|
||||||
ctx,
|
ctx,
|
||||||
|
gtsmodel.InteractionAnnounce,
|
||||||
requestUser,
|
requestUser,
|
||||||
status.ApprovedByURI,
|
status.ApprovedByURI, // approval uri
|
||||||
status.URI,
|
boostOf.AccountURI, // actor
|
||||||
boostOf.AccountURI,
|
status.URI, // object
|
||||||
|
status.BoostOfURI, // target
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Error dereferencing means we couldn't
|
// Error dereferencing means we couldn't
|
||||||
// get the Accept right now or it wasn't
|
// get the approval right now or it wasn't
|
||||||
// valid, so we shouldn't store this status.
|
// valid, so we shouldn't store this status.
|
||||||
err := gtserror.Newf("undereferencable ApprovedByURI: %w", err)
|
err := gtserror.Newf("undereferencable ApprovedByURI: %w", err)
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -628,34 +637,36 @@ func (d *Dereferencer) isPermittedBoost(
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isValidAccept dereferences the activitystreams Accept at the
|
// isValidApprovedByIRI dereferences the activitystreams Accept or approval
|
||||||
// specified IRI, and checks the Accept for validity against the
|
// at the specified IRI, and checks the Accept or approval for validity
|
||||||
// provided expectedObject and expectedActor.
|
// against the provided expectedActor, expectedObject, and expectedTarget.
|
||||||
//
|
//
|
||||||
// Will return either (true, nil) if everything looked OK, an error
|
// Will return either (true, nil) if everything looked OK, an error
|
||||||
// if something went wrong internally during deref, or (false, nil)
|
// if something went wrong internally during deref, or (false, nil)
|
||||||
// if the dereferenced Accept did not meet expectations.
|
// if the dereferenced Accept/Approval did not meet expectations.
|
||||||
func (d *Dereferencer) isValidAccept(
|
func (d *Dereferencer) isValidApprovedByIRI(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
interactionType gtsmodel.InteractionType,
|
||||||
requestUser string,
|
requestUser string,
|
||||||
acceptIRIStr string, // Eg., "https://example.org/users/someone/accepts/01J2736AWWJ3411CPR833F6D03"
|
approvedByIRIStr string, // approval uri Eg., "https://example.org/users/someone/accepts/01J2736AWWJ3411CPR833F6D03"
|
||||||
expectObjectURIStr string, // Eg., "https://some.instance.example.org/users/someone_else/statuses/01J27414TWV9F7DC39FN8ABB5R"
|
expectActorURIStr string, // actor Eg., "https://example.org/users/someone"
|
||||||
expectActorURIStr string, // Eg., "https://example.org/users/someone"
|
expectObjectURIStr string, // object Eg., "https://some.instance.example.org/users/someone_else/statuses/01J27414TWV9F7DC39FN8ABB5R"
|
||||||
|
expectTargetURIStr string, // target Eg., "https://example.org/users/someone/statuses/01JM4REQTJ1BZ1R4BPYP1W4R9E"
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
l := log.
|
l := log.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
WithField("acceptIRI", acceptIRIStr)
|
WithField("approvedByIRI", approvedByIRIStr)
|
||||||
|
|
||||||
acceptIRI, err := url.Parse(acceptIRIStr)
|
approvedByIRI, err := url.Parse(approvedByIRIStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Real returnable error.
|
// Real returnable error.
|
||||||
err := gtserror.Newf("error parsing acceptIRI: %w", err)
|
err := gtserror.Newf("error parsing approvedByIRI: %w", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't make calls to the Accept IRI
|
// Don't make calls to the IRI if its
|
||||||
// if it's blocked, just return false.
|
// domain is blocked, just return false.
|
||||||
blocked, err := d.state.DB.IsDomainBlocked(ctx, acceptIRI.Host)
|
blocked, err := d.state.DB.IsDomainBlocked(ctx, approvedByIRI.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Real returnable error.
|
// Real returnable error.
|
||||||
err := gtserror.Newf("error checking domain block: %w", err)
|
err := gtserror.Newf("error checking domain block: %w", err)
|
||||||
|
|
@ -663,7 +674,7 @@ func (d *Dereferencer) isValidAccept(
|
||||||
}
|
}
|
||||||
|
|
||||||
if blocked {
|
if blocked {
|
||||||
l.Info("Accept host is blocked")
|
l.Info("approvedByIRI host is blocked")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -674,51 +685,52 @@ func (d *Dereferencer) isValidAccept(
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the call to resolve into an Acceptable.
|
// Make the call to the approvedByURI.
|
||||||
// Log any error encountered here but don't
|
// Log any error encountered here but don't
|
||||||
// return it as it's not *our* error.
|
// return it as it's not *our* error.
|
||||||
rsp, err := tsport.Dereference(ctx, acceptIRI)
|
rsp, err := tsport.Dereference(ctx, approvedByIRI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error dereferencing Accept: %v", err)
|
l.Errorf("error dereferencing approvedByIRI: %v", err)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptable, err := ap.ResolveAcceptable(ctx, rsp.Body)
|
// Try to parse response as an AP type.
|
||||||
|
t, err := ap.DecodeType(ctx, rsp.Body)
|
||||||
|
|
||||||
// Tidy up rsp body.
|
// Tidy up rsp body.
|
||||||
_ = rsp.Body.Close()
|
_ = rsp.Body.Close()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error resolving to Accept: %v", err)
|
l.Errorf("error resolving to type: %v", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the URI/ID of the Accept.
|
// Extract the URI/ID of the type.
|
||||||
acceptID := ap.GetJSONLDId(acceptable)
|
approvedByID := ap.GetJSONLDId(t)
|
||||||
acceptIDStr := acceptID.String()
|
approvedByIDStr := approvedByID.String()
|
||||||
|
|
||||||
// Check whether input URI and final returned URI
|
// Check whether input URI and final returned URI
|
||||||
// have changed (i.e. we followed some redirects).
|
// have changed (i.e. we followed some redirects).
|
||||||
rspURL := rsp.Request.URL
|
rspURL := rsp.Request.URL
|
||||||
rspURLStr := rspURL.String()
|
rspURLStr := rspURL.String()
|
||||||
if rspURLStr != acceptIRIStr {
|
if rspURLStr != approvedByIRIStr {
|
||||||
// If rspURLStr != acceptIRIStr, make sure final
|
// If rspURLStr != approvedByIRI, make sure final
|
||||||
// response URL is at least on the same host as
|
// response URL is at least on the same host as
|
||||||
// what we expected (ie., we weren't redirected
|
// what we expected (ie., we weren't redirected
|
||||||
// across domains), and make sure it's the same
|
// across domains), and make sure it's the same
|
||||||
// as the ID of the Accept we were returned.
|
// as the ID of the Accept we were returned.
|
||||||
switch {
|
switch {
|
||||||
case rspURL.Host != acceptIRI.Host:
|
case rspURL.Host != approvedByIRI.Host:
|
||||||
l.Errorf(
|
l.Errorf(
|
||||||
"final deref host %s did not match acceptIRI host",
|
"final deref host %s did not match approvedByIRI host",
|
||||||
rspURL.Host,
|
rspURL.Host,
|
||||||
)
|
)
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
||||||
case acceptIDStr != rspURLStr:
|
case approvedByIDStr != rspURLStr:
|
||||||
l.Errorf(
|
l.Errorf(
|
||||||
"final deref uri %s did not match returned Accept ID %s",
|
"final deref uri %s did not match returned ID %s",
|
||||||
rspURLStr, acceptIDStr,
|
rspURLStr, approvedByIDStr,
|
||||||
)
|
)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
@ -727,6 +739,52 @@ func (d *Dereferencer) isValidAccept(
|
||||||
// Response is superficially OK,
|
// Response is superficially OK,
|
||||||
// check in more detail now.
|
// check in more detail now.
|
||||||
|
|
||||||
|
// First try to parse type as Approval stamp.
|
||||||
|
if approvable, ok := ap.ToApprovable(t); ok {
|
||||||
|
return isValidApprovable(
|
||||||
|
ctx,
|
||||||
|
interactionType,
|
||||||
|
approvable,
|
||||||
|
approvedByID,
|
||||||
|
expectActorURIStr, // actor
|
||||||
|
expectObjectURIStr, // object
|
||||||
|
expectTargetURIStr, // target
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to parsing as a simple Accept.
|
||||||
|
if acceptable, ok := ap.ToAcceptable(t); ok {
|
||||||
|
return isValidAcceptable(
|
||||||
|
ctx,
|
||||||
|
acceptable,
|
||||||
|
approvedByID,
|
||||||
|
expectActorURIStr, // actor
|
||||||
|
expectObjectURIStr, // object
|
||||||
|
expectTargetURIStr, // target
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type wasn't something we
|
||||||
|
// could do anything with!
|
||||||
|
l.Errorf(
|
||||||
|
"%T at %s not approvable or acceptable",
|
||||||
|
t, approvedByIRIStr,
|
||||||
|
)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidAcceptable(
|
||||||
|
ctx context.Context,
|
||||||
|
acceptable ap.Acceptable,
|
||||||
|
acceptID *url.URL,
|
||||||
|
expectActorURIStr string, // actor Eg., "https://example.org/users/someone"
|
||||||
|
expectObjectURIStr string, // object Eg., "https://some.instance.example.org/users/someone_else/statuses/01J27414TWV9F7DC39FN8ABB5R"
|
||||||
|
expectTargetURIStr string, // target Eg., "https://example.org/users/someone/statuses/01JM4REQTJ1BZ1R4BPYP1W4R9E"
|
||||||
|
) (bool, error) {
|
||||||
|
l := log.
|
||||||
|
WithContext(ctx).
|
||||||
|
WithField("accept", acceptID.String())
|
||||||
|
|
||||||
// Extract the actor IRI and string from Accept.
|
// Extract the actor IRI and string from Accept.
|
||||||
actorIRIs := ap.GetActorIRIs(acceptable)
|
actorIRIs := ap.GetActorIRIs(acceptable)
|
||||||
actorIRI, actorIRIStr := extractIRI(actorIRIs)
|
actorIRI, actorIRIStr := extractIRI(actorIRIs)
|
||||||
|
|
@ -775,6 +833,113 @@ func (d *Dereferencer) isValidAccept(
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there's a Target set then verify it's
|
||||||
|
// what we expect it to be, ie., it should point
|
||||||
|
// back to the post that's being interacted with.
|
||||||
|
targetIRIs := ap.GetTargetIRIs(acceptable)
|
||||||
|
_, targetIRIStr := extractIRI(targetIRIs)
|
||||||
|
if targetIRIStr != "" && targetIRIStr != expectTargetURIStr {
|
||||||
|
l.Errorf(
|
||||||
|
"resolved Accept target IRI %s was not the same as expected target %s",
|
||||||
|
targetIRIStr, expectTargetURIStr,
|
||||||
|
)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything looks OK.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidApprovable(
|
||||||
|
ctx context.Context,
|
||||||
|
interactionType gtsmodel.InteractionType,
|
||||||
|
approvable ap.Approvable,
|
||||||
|
approvalID *url.URL,
|
||||||
|
expectActorURIStr string, // actor Eg., "https://example.org/users/someone"
|
||||||
|
expectObjectURIStr string, // object Eg., "https://some.instance.example.org/users/someone_else/statuses/01J27414TWV9F7DC39FN8ABB5R"
|
||||||
|
expectTargetURIStr string, // target Eg., "https://example.org/users/someone/statuses/01JM4REQTJ1BZ1R4BPYP1W4R9E"
|
||||||
|
) (bool, error) {
|
||||||
|
l := log.
|
||||||
|
WithContext(ctx).
|
||||||
|
WithField("approval", approvalID.String())
|
||||||
|
|
||||||
|
// Check that the type of the Approval
|
||||||
|
// matches the interaction it's approving.
|
||||||
|
switch tn := approvable.GetTypeName(); {
|
||||||
|
case (tn == ap.ObjectLikeApproval && interactionType == gtsmodel.InteractionLike),
|
||||||
|
(tn == ap.ObjectReplyApproval && interactionType == gtsmodel.InteractionReply),
|
||||||
|
(tn == ap.ObjectAnnounceApproval && interactionType == gtsmodel.InteractionAnnounce):
|
||||||
|
// All good baby!
|
||||||
|
default:
|
||||||
|
// There's a mismatch.
|
||||||
|
l.Errorf(
|
||||||
|
"approval type %s cannot approve %s",
|
||||||
|
tn, interactionType.String(),
|
||||||
|
)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the actor IRI and string from Approval.
|
||||||
|
actorIRIs := ap.GetAttributedTo(approvable)
|
||||||
|
actorIRI, actorIRIStr := extractIRI(actorIRIs)
|
||||||
|
switch {
|
||||||
|
case actorIRIStr == "":
|
||||||
|
l.Error("Approval missing attributedTo IRI")
|
||||||
|
return false, nil
|
||||||
|
|
||||||
|
// Ensure the Approval actor is on
|
||||||
|
// the instance hosting the Approval.
|
||||||
|
case actorIRI.Host != approvalID.Host:
|
||||||
|
l.Errorf(
|
||||||
|
"actor %s not on the same host as Approval",
|
||||||
|
actorIRIStr,
|
||||||
|
)
|
||||||
|
return false, nil
|
||||||
|
|
||||||
|
// Ensure the Approval actor is who we expect
|
||||||
|
// it to be, and not someone else trying to
|
||||||
|
// do an Approval for an interaction with a
|
||||||
|
// statusable they don't own.
|
||||||
|
case actorIRIStr != expectActorURIStr:
|
||||||
|
l.Errorf(
|
||||||
|
"actor %s was not the same as expected actor %s",
|
||||||
|
actorIRIStr, expectActorURIStr,
|
||||||
|
)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the object IRI string from Approval.
|
||||||
|
objectIRIs := ap.GetObjectIRIs(approvable)
|
||||||
|
_, objectIRIStr := extractIRI(objectIRIs)
|
||||||
|
switch {
|
||||||
|
case objectIRIStr == "":
|
||||||
|
l.Error("missing Approval object IRI")
|
||||||
|
return false, nil
|
||||||
|
|
||||||
|
// Ensure the Approval Object is what we expect
|
||||||
|
// it to be, ie., it's approving the interaction
|
||||||
|
// we need it to approve, and not something else.
|
||||||
|
case objectIRIStr != expectObjectURIStr:
|
||||||
|
l.Errorf(
|
||||||
|
"resolved Approval object IRI %s was not the same as expected object %s",
|
||||||
|
objectIRIStr, expectObjectURIStr,
|
||||||
|
)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a Target set then verify it's
|
||||||
|
// what we expect it to be, ie., it should point
|
||||||
|
// back to the post that's being interacted with.
|
||||||
|
targetIRIs := ap.GetTargetIRIs(approvable)
|
||||||
|
_, targetIRIStr := extractIRI(targetIRIs)
|
||||||
|
if targetIRIStr != "" && targetIRIStr != expectTargetURIStr {
|
||||||
|
l.Errorf(
|
||||||
|
"resolved Approval target IRI %s was not the same as expected target %s",
|
||||||
|
targetIRIStr, expectTargetURIStr,
|
||||||
|
)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Everything looks OK.
|
// Everything looks OK.
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ package federatingdb
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||||
|
|
@ -62,8 +63,8 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
activityID := ap.GetJSONLDId(accept)
|
acceptID := ap.GetJSONLDId(accept)
|
||||||
if activityID == nil {
|
if acceptID == nil {
|
||||||
// We need an ID.
|
// We need an ID.
|
||||||
const text = "Accept had no id property"
|
const text = "Accept had no id property"
|
||||||
return gtserror.NewErrorBadRequest(errors.New(text), text)
|
return gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||||
|
|
@ -87,12 +88,11 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
||||||
// handling the ones we know how to handle.
|
// handling the ones we know how to handle.
|
||||||
for _, object := range ap.ExtractObjects(accept) {
|
for _, object := range ap.ExtractObjects(accept) {
|
||||||
if asType := object.GetType(); asType != nil {
|
if asType := object.GetType(); asType != nil {
|
||||||
|
|
||||||
// Check and handle any vocab.Type objects.
|
// Check and handle any vocab.Type objects.
|
||||||
switch name := asType.GetTypeName(); name {
|
switch name := asType.GetTypeName(); {
|
||||||
|
|
||||||
// ACCEPT FOLLOW
|
// ACCEPT FOLLOW
|
||||||
case ap.ActivityFollow:
|
case name == ap.ActivityFollow:
|
||||||
if err := f.acceptFollowType(
|
if err := f.acceptFollowType(
|
||||||
ctx,
|
ctx,
|
||||||
asType,
|
asType,
|
||||||
|
|
@ -102,6 +102,51 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACCEPT TYPE-HINTED LIKE
|
||||||
|
//
|
||||||
|
// ie., a Like with just `id`
|
||||||
|
// and `type` properties set.
|
||||||
|
case name == ap.ActivityLike:
|
||||||
|
objIRI := ap.GetJSONLDId(asType)
|
||||||
|
if objIRI == nil {
|
||||||
|
log.Debugf(ctx, "could not retrieve id of inlined Accept object %s", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.acceptLikeIRI(
|
||||||
|
ctx,
|
||||||
|
acceptID,
|
||||||
|
accept,
|
||||||
|
objIRI.String(),
|
||||||
|
receivingAcct,
|
||||||
|
requestingAcct,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACCEPT TYPE-HINTED REPLY OR ANNOUNCE.
|
||||||
|
//
|
||||||
|
// ie., a statusable or Announce with
|
||||||
|
// just `id` and `type` properties set.
|
||||||
|
case name == ap.ActivityAnnounce,
|
||||||
|
ap.IsStatusable(name):
|
||||||
|
objIRI := ap.GetJSONLDId(asType)
|
||||||
|
if objIRI == nil {
|
||||||
|
log.Debugf(ctx, "could not retrieve id of inlined Accept object %s", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.acceptOtherIRI(
|
||||||
|
ctx,
|
||||||
|
acceptID,
|
||||||
|
accept,
|
||||||
|
objIRI,
|
||||||
|
receivingAcct,
|
||||||
|
requestingAcct,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// UNHANDLED
|
// UNHANDLED
|
||||||
default:
|
default:
|
||||||
log.Debugf(ctx, "unhandled object type: %s", name)
|
log.Debugf(ctx, "unhandled object type: %s", name)
|
||||||
|
|
@ -127,7 +172,8 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
||||||
case uris.IsLikePath(objIRI):
|
case uris.IsLikePath(objIRI):
|
||||||
if err := f.acceptLikeIRI(
|
if err := f.acceptLikeIRI(
|
||||||
ctx,
|
ctx,
|
||||||
activityID.String(),
|
acceptID,
|
||||||
|
accept,
|
||||||
objIRI.String(),
|
objIRI.String(),
|
||||||
receivingAcct,
|
receivingAcct,
|
||||||
requestingAcct,
|
requestingAcct,
|
||||||
|
|
@ -135,14 +181,15 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACCEPT OTHER (reply? boost?)
|
// ACCEPT OTHER (reply? announce?)
|
||||||
//
|
//
|
||||||
// Don't check on IsStatusesPath
|
// Don't check on IsStatusesPath
|
||||||
// as this may be a remote status.
|
// as this may be a remote status.
|
||||||
default:
|
default:
|
||||||
if err := f.acceptOtherIRI(
|
if err := f.acceptOtherIRI(
|
||||||
ctx,
|
ctx,
|
||||||
activityID,
|
acceptID,
|
||||||
|
accept,
|
||||||
objIRI,
|
objIRI,
|
||||||
receivingAcct,
|
receivingAcct,
|
||||||
requestingAcct,
|
requestingAcct,
|
||||||
|
|
@ -292,7 +339,8 @@ func (f *federatingDB) acceptFollowIRI(
|
||||||
|
|
||||||
func (f *federatingDB) acceptOtherIRI(
|
func (f *federatingDB) acceptOtherIRI(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
activityID *url.URL,
|
acceptID *url.URL,
|
||||||
|
accept vocab.ActivityStreamsAccept,
|
||||||
objectIRI *url.URL,
|
objectIRI *url.URL,
|
||||||
receivingAcct *gtsmodel.Account,
|
receivingAcct *gtsmodel.Account,
|
||||||
requestingAcct *gtsmodel.Account,
|
requestingAcct *gtsmodel.Account,
|
||||||
|
|
@ -309,7 +357,8 @@ func (f *federatingDB) acceptOtherIRI(
|
||||||
// objectIRI, proceed to accept it.
|
// objectIRI, proceed to accept it.
|
||||||
return f.acceptStoredStatus(
|
return f.acceptStoredStatus(
|
||||||
ctx,
|
ctx,
|
||||||
activityID,
|
acceptID,
|
||||||
|
accept,
|
||||||
status,
|
status,
|
||||||
receivingAcct,
|
receivingAcct,
|
||||||
requestingAcct,
|
requestingAcct,
|
||||||
|
|
@ -348,13 +397,15 @@ func (f *federatingDB) acceptOtherIRI(
|
||||||
// This may be a reply, or it may be a boost,
|
// This may be a reply, or it may be a boost,
|
||||||
// we can't know yet without dereferencing it,
|
// we can't know yet without dereferencing it,
|
||||||
// but let the processor worry about that.
|
// but let the processor worry about that.
|
||||||
|
//
|
||||||
|
// TODO: do something with type hinting here.
|
||||||
apObjectType := ap.ObjectUnknown
|
apObjectType := ap.ObjectUnknown
|
||||||
|
|
||||||
// Pass to the processor and let them handle side effects.
|
// Pass to the processor and let them handle side effects.
|
||||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||||
APObjectType: apObjectType,
|
APObjectType: apObjectType,
|
||||||
APActivityType: ap.ActivityAccept,
|
APActivityType: ap.ActivityAccept,
|
||||||
APIRI: activityID,
|
APIRI: acceptID,
|
||||||
APObject: objectIRI,
|
APObject: objectIRI,
|
||||||
Receiving: receivingAcct,
|
Receiving: receivingAcct,
|
||||||
Requesting: requestingAcct,
|
Requesting: requestingAcct,
|
||||||
|
|
@ -365,7 +416,8 @@ func (f *federatingDB) acceptOtherIRI(
|
||||||
|
|
||||||
func (f *federatingDB) acceptStoredStatus(
|
func (f *federatingDB) acceptStoredStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
activityID *url.URL,
|
acceptID *url.URL,
|
||||||
|
accept vocab.ActivityStreamsAccept,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
receivingAcct *gtsmodel.Account,
|
receivingAcct *gtsmodel.Account,
|
||||||
requestingAcct *gtsmodel.Account,
|
requestingAcct *gtsmodel.Account,
|
||||||
|
|
@ -391,9 +443,15 @@ func (f *federatingDB) acceptStoredStatus(
|
||||||
return gtserror.NewErrorForbidden(errors.New(text), text)
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the status as approved by this Accept URI.
|
// Extract appropriate approvedByURI from the Accept.
|
||||||
|
approvedByURI, err := approvedByURI(acceptID, accept)
|
||||||
|
if err != nil {
|
||||||
|
return gtserror.NewErrorForbidden(err, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the status as approved by this URI.
|
||||||
status.PendingApproval = util.Ptr(false)
|
status.PendingApproval = util.Ptr(false)
|
||||||
status.ApprovedByURI = activityID.String()
|
status.ApprovedByURI = approvedByURI.String()
|
||||||
if err := f.state.DB.UpdateStatus(
|
if err := f.state.DB.UpdateStatus(
|
||||||
ctx,
|
ctx,
|
||||||
status,
|
status,
|
||||||
|
|
@ -428,7 +486,8 @@ func (f *federatingDB) acceptStoredStatus(
|
||||||
|
|
||||||
func (f *federatingDB) acceptLikeIRI(
|
func (f *federatingDB) acceptLikeIRI(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
activityID string,
|
acceptID *url.URL,
|
||||||
|
accept vocab.ActivityStreamsAccept,
|
||||||
objectIRI string,
|
objectIRI string,
|
||||||
receivingAcct *gtsmodel.Account,
|
receivingAcct *gtsmodel.Account,
|
||||||
requestingAcct *gtsmodel.Account,
|
requestingAcct *gtsmodel.Account,
|
||||||
|
|
@ -482,9 +541,15 @@ func (f *federatingDB) acceptLikeIRI(
|
||||||
return gtserror.NewErrorForbidden(errors.New(text), text)
|
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the fave as approved by this Accept URI.
|
// Extract appropriate approvedByURI from the Accept.
|
||||||
|
approvedByURI, err := approvedByURI(acceptID, accept)
|
||||||
|
if err != nil {
|
||||||
|
return gtserror.NewErrorForbidden(err, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the fave as approved by this URI.
|
||||||
fave.PendingApproval = util.Ptr(false)
|
fave.PendingApproval = util.Ptr(false)
|
||||||
fave.ApprovedByURI = activityID
|
fave.ApprovedByURI = approvedByURI.String()
|
||||||
if err := f.state.DB.UpdateStatusFave(
|
if err := f.state.DB.UpdateStatusFave(
|
||||||
ctx,
|
ctx,
|
||||||
fave,
|
fave,
|
||||||
|
|
@ -507,3 +572,72 @@ func (f *federatingDB) acceptLikeIRI(
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// approvedByURI extracts the appropriate *url.URL
|
||||||
|
// to use as an interaction's approvedBy value by
|
||||||
|
// checking to see if the Accept has a result URL set.
|
||||||
|
// If that result URL exists, is an IRI (not a type),
|
||||||
|
// and is on the same host as the Accept ID, then the
|
||||||
|
// result URI will be returned. In all other cases,
|
||||||
|
// the Accept ID is returned unchanged.
|
||||||
|
//
|
||||||
|
// Error is only returned if the result URI is set
|
||||||
|
// but the host differs from the Accept ID host.
|
||||||
|
//
|
||||||
|
// TODO: This function should be updated at some point
|
||||||
|
// to check for inlined result type, and see if type is
|
||||||
|
// a LikeApproval, ReplyApproval, or AnnounceApproval,
|
||||||
|
// and check the attributedTo, object, and target of
|
||||||
|
// the approval as well. But this'll do for now.
|
||||||
|
func approvedByURI(
|
||||||
|
acceptID *url.URL,
|
||||||
|
accept vocab.ActivityStreamsAccept,
|
||||||
|
) (*url.URL, error) {
|
||||||
|
// Check if the Accept has a `result` property
|
||||||
|
// set on it (which should be an approval).
|
||||||
|
resultProp := accept.GetActivityStreamsResult()
|
||||||
|
if resultProp == nil {
|
||||||
|
// No result,
|
||||||
|
// use AcceptID.
|
||||||
|
return acceptID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultProp.Len() != 1 {
|
||||||
|
// Result was unexpected
|
||||||
|
// length, can't use this.
|
||||||
|
return acceptID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := resultProp.At(0)
|
||||||
|
if result == nil {
|
||||||
|
// Result entry
|
||||||
|
// was nil, huh!
|
||||||
|
return acceptID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.IsIRI() {
|
||||||
|
// Can't handle
|
||||||
|
// inlined yet.
|
||||||
|
return acceptID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resultIRI := result.GetIRI()
|
||||||
|
if resultIRI == nil {
|
||||||
|
// Result entry
|
||||||
|
// was nil, huh!
|
||||||
|
return acceptID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultIRI.Host != acceptID.Host {
|
||||||
|
// What the boobs is this?
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"host of result %s differed from host of Accept %s",
|
||||||
|
resultIRI, accept,
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the result IRI we've been
|
||||||
|
// given instead of the acceptID.
|
||||||
|
return resultIRI, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue