diff --git a/src/Vervis/API.hs b/src/Vervis/API.hs index 9797f9a..50321c9 100644 --- a/src/Vervis/API.hs +++ b/src/Vervis/API.hs @@ -25,12 +25,12 @@ module Vervis.API , createPatchTrackerC , createRepositoryC , createTicketTrackerC - --, followC + , followC , inviteC , offerTicketC --, offerDepC - --, resolveC - --, undoC + , resolveC + , undoC --, pushCommitsC ) where @@ -102,6 +102,7 @@ import Vervis.Darcs import Vervis.Data.Actor import Vervis.Data.Collab import Vervis.Data.Discussion +import Vervis.Data.Follow import Vervis.Data.Ticket import Vervis.FedURI import Vervis.Fetch @@ -116,13 +117,13 @@ import Vervis.Path import Vervis.Persist.Actor import Vervis.Persist.Collab import Vervis.Persist.Discussion +import Vervis.Persist.Follow import Vervis.Persist.Ticket import Vervis.Recipient import Vervis.RemoteActorStore import Vervis.Settings import Vervis.Query import Vervis.Ticket -import Vervis.WorkItem import Vervis.Web.Delivery import Vervis.Web.Repo @@ -1666,16 +1667,6 @@ createTicketTrackerC (Entity pidUser personUser) senderActor maybeCap localRecip } update obiidAccept [OutboxItemActivity =. persistJSONObjectFromDoc doc] -{- -data Followee - = FolloweePerson (KeyHashid Person) - | FolloweeGroup (KeyHashid Group) - | FolloweeRepo (KeyHashid Repo) - | FolloweeDeck (KeyHashid Deck) - | FolloweeLoom (KeyHashid Loom) - | FolloweeTicket (KeyHashid Deck) (KeyHashid TicketDeck) - | FolloweeCloth (KeyHashid Loom) (KeyHashid TicketLoom) - followC :: Entity Person -> Actor @@ -1690,158 +1681,130 @@ followC -> AP.Action URIMode -> AP.Follow URIMode -> ExceptT Text Handler OutboxItemId -followC (Entity pidSender personSender) _senderActor maybeCap localRecips remoteRecips fwdHosts action follow@(AP.Follow uObject muContext hide) = do +followC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips remoteRecips fwdHosts action follow = do + + -- Check input verifyNothingE maybeCap "Capability not needed" + (followee, hide) <- parseFollow follow + case followee of + Left (FolloweeActor (LocalActorPerson personID)) + | personID == senderPersonID -> + throwE "Trying to follow yourself" + _ -> pure () + + -- Verify that followee's actor is addressed + case followee of + Left f -> do + actorByHash <- hashLocalActor $ followeeActor f + unless (actorIsAddressed localRecips actorByHash) $ + throwE "Followee's actor not addressed by the Follow" + Right (h, luActor, luObject) -> + verifyRemoteAddressed remoteRecips $ ObjURI h luActor + now <- liftIO getCurrentTime - senderHash <- encodeKeyHashid pidSender - mfollowee <- do - let ObjURI h luObject = uObject - local <- hostIsLocal h - if local - then Just <$> do - route <- - fromMaybeE - (decodeRouteLocal luObject) - "Follow object isn't a valid route" - followee <- - fromMaybeE - (parseFollowee route) - "Follow object isn't a followee route" - let actor = followeeActor followee - unless (actorRecips actor == localRecips) $ - throwE "Follow object isn't the recipient" - case followee of - FolloweePerson p | p == senderHash -> - throwE "User trying to follow themselves" - _ -> return () - return (followee, actor) - else do - unless (localRecips == RecipientRoutes [] [] [] [] []) $ - throwE "Follow object is remote but local recips listed" - return Nothing - (obiidFollow, doc, remotesHttp) <- runDBExcept $ do - let actorSenderID = personActor personSender - actorSender <- lift $ getJust actorSenderID - let ibidSender = actorInbox actorSender - obidSender = actorOutbox actorSender - obiidFollow <- lift $ insertEmptyOutboxItem obidSender now - luFollow <- lift $ updateOutboxItem (LocalActorPerson pidSender) obiidFollow action - case mfollowee of - Nothing -> lift $ insert_ $ FollowRemoteRequest pidSender uObject muContext (not hide) obiidFollow - Just (followee, actorRecip) -> do - (actorRecipID, mfsid, unread) <- getFollowee followee - actorRecipDB <- lift $ getJust actorRecipID - let obidRecip = actorOutbox actorRecipDB - obiidAccept <- lift $ insertAcceptToOutbox senderHash luFollow actorRecip obidRecip - let ibidRecip = actorInbox actorRecipDB - fsid = fromMaybe (actorFollowers actorRecipDB) mfsid - deliverFollowLocal now actorSenderID fsid unread obiidFollow obiidAccept ibidRecip - lift $ deliverAcceptLocal now obiidAccept ibidSender - remotesHttp <- lift $ deliverRemoteDB fwdHosts obiidFollow remoteRecips [] - return (obiidFollow, doc, remotesHttp) - lift $ forkWorker "Outbox POST handler: async HTTP delivery" $ deliverRemoteHttp' fwdHosts obiidFollow doc remotesHttp - return obiidFollow + senderHash <- encodeKeyHashid senderPersonID + + (followID, deliverHttpFollow, maybeDeliverHttpAccept) <- runDBExcept $ do + + -- If followee is local, find it in our DB + followeeDB <- bitraverse getFollowee pure followee + + -- Insert Follow activity to author's outbox + followID <- lift $ insertEmptyOutboxItem (actorOutbox senderActor) now + luFollow <- lift $ updateOutboxItem (LocalActorPerson senderPersonID) followID action + + -- Deliver the Follow activity to local recipients, and schedule + -- delivery for unavailable remote recipients + deliverHttpFollow <- do + sieve <- do + (actors, stages) <- + case followeeDB of + Left (actorByKey, _, _) -> do + actorByHash <- hashLocalActor actorByKey + return + ( [actorByHash] + , [localActorFollowers actorByHash] + ) + Right _ -> pure ([], []) + let stages' = LocalStagePersonFollowers senderHash : stages + return $ makeRecipientSet actors stages' + let localRecipsFinal = localRecipSieve sieve False localRecips + deliverActivityDB + (LocalActorPerson senderHash) (personActor senderPerson) + localRecipsFinal remoteRecips fwdHosts followID action + + maybeDeliverHttpAccept <- + case followeeDB of + Right (h, luActor, luObject) -> lift $ do + + -- For remote followee, just remember the request in our DB + let uObject = ObjURI h luObject + muContext = + if luActor == luObject + then Nothing + else Just $ ObjURI h luActor + insert_ $ FollowRemoteRequest senderPersonID uObject muContext (not hide) followID + return Nothing + + Left (actorByKey, actorID, maybeFollowerSetID) -> Just <$> do + + -- Verify followee's actor has received the Accept + verifyActorHasItem actorID followID "Followee's actor didn't receive the Follow" + + -- Insert an Accept activity to followee's outbox + actor <- lift $ getJust actorID + acceptID <- lift $ insertEmptyOutboxItem (actorOutbox actor) now + let acceptActors = [LocalActorPerson senderHash] + acceptStages = [] + actionAccept <- prepareAccept luFollow acceptActors acceptStages + _luAccept <- lift $ updateOutboxItem actorByKey acceptID actionAccept + + -- Insert author to followee's followers collection + let fsid = + fromMaybe (actorFollowers actor) maybeFollowerSetID + mfid <- + lift $ insertUnique $ + Follow (personActor senderPerson) fsid (not hide) followID acceptID + _ <- fromMaybeE mfid "Already following this object" + + -- Deliver the Accept activity to local recipients, and + -- schedule delivery for unavailable remote recipients + let localRecipsAccept = makeRecipientSet acceptActors acceptStages + actorByHash <- hashLocalActor actorByKey + deliverActivityDB + actorByHash actorID localRecipsAccept [] [] + acceptID actionAccept + + -- Return instructions for HTTP delivery to remote recipients + return (followID, deliverHttpFollow, maybeDeliverHttpAccept) + + -- Launch asynchronous HTTP delivery of Follow and Accept + lift $ do + forkWorker "followC: async HTTP Follow delivery" deliverHttpFollow + for_ maybeDeliverHttpAccept $ + forkWorker "followC: async HTTP Accept delivery" + + return followID + where - parseFollowee (PersonR p) = Just $ FolloweePerson p - parseFollowee (GroupR g) = Just $ FolloweeGroup g - parseFollowee (RepoR r) = Just $ FolloweeRepo r - parseFollowee (DeckR d) = Just $ FolloweeDeck d - parseFollowee (LoomR l) = Just $ FolloweeLoom l - parseFollowee (TicketR d t) = Just $ FolloweeTicket d t - parseFollowee (ClothR l c) = Just $ FolloweeCloth l c - parseFollowee _ = Nothing - followeeActor (FolloweePerson p) = LocalActorPerson p - followeeActor (FolloweeGroup g) = LocalActorGroup g - followeeActor (FolloweeRepo r) = LocalActorRepo r - followeeActor (FolloweeDeck d) = LocalActorDeck d - followeeActor (FolloweeLoom l) = LocalActorLoom l - followeeActor (FolloweeTicket d _) = LocalActorDeck d - followeeActor (FolloweeCloth l _) = LocalActorLoom l - - getFollowee (FolloweePerson personHash) = do - personID <- decodeKeyHashidE personHash "Follow object: No such person hash" - (,Nothing,True) . personActor <$> getE personID "Follow object: No such person in DB" - getFollowee (FolloweeGroup groupHash) = do - groupID <- decodeKeyHashidE groupHash "Follow object: No such group hash" - (,Nothing,False) . groupActor <$> getE groupID "Follow object: No such group in DB" - getFollowee (FolloweeRepo repoHash) = do - repoID <- decodeKeyHashidE repoHash "Follow object: No such repo hash" - (,Nothing,False) . repoActor <$> getE repoID "Follow object: No such repo in DB" - getFollowee (FolloweeDeck deckHash) = do - deckID <- decodeKeyHashidE deckHash "Follow object: No such deck hash" - (,Nothing,False) . deckActor <$> getE deckID "Follow object: No such deck in DB" - getFollowee (FolloweeLoom loomHash) = do - loomID <- decodeKeyHashidE loomHash "Follow object: No such loom hash" - (,Nothing,False) . loomActor <$> getE loomID "Follow object: No such loom in DB" - getFollowee (FolloweeTicket deckHash ticketHash) = do - deckID <- decodeKeyHashidE deckHash "Follow object: No such deck hash" - actor <- deckActor <$> getE deckID "Follow object: No such deck in DB" - ticketID <- decodeKeyHashidE ticketHash "Follow object: No such ticket hash" - (_, _, Entity _ ticket, _, _) <- do - mticket <- lift $ getTicket deckID ticketID - fromMaybeE mticket "Follow object: No such ticket in DB" - return (actor, Just $ ticketFollowers ticket, False) - getFollowee (FolloweeCloth loomHash clothHash) = do - loomID <- decodeKeyHashidE loomHash "Follow object: No such loom hash" - actor <- loomActor <$> getE loomID "Follow object: No such loom in DB" - clothID <- decodeKeyHashidE clothHash "Follow object: No such cloth hash" - (_, _, Entity _ ticket, _, _, _) <- do - mticket <- lift $ getCloth loomID clothID - fromMaybeE mticket "Follow object: No such cloth in DB" - return (actor, Just $ ticketFollowers ticket, False) - - insertAcceptToOutbox senderHash luFollow actorRecip obidRecip = do - now <- liftIO getCurrentTime - summary <- - renderHTML <$> - withUrlRenderer - [hamlet| -
-
- #{username2text $ personUsername personSender}
- 's follow request accepted by #
-
- #{localUriPath $ objUriLocal uObject}
- |]
- hLocal <- asksSite siteInstanceHost
- encodeRouteLocal <- getEncodeRouteLocal
+ prepareAccept luFollow actors stages = do
encodeRouteHome <- getEncodeRouteHome
- let recips = [encodeRouteHome $ PersonR senderHash]
- accept mluAct = Doc hLocal Activity
- { activityId = mluAct
- , activityActor = objUriLocal uObject
- , activityCapability = Nothing
- , activitySummary = Just summary
- , activityAudience = Audience recips [] [] [] [] []
- , activityFulfills = []
- , activitySpecific = AcceptActivity Accept
- { acceptObject = ObjURI hLocal luFollow
- , acceptResult = Nothing
- }
+ hLocal <- asksSite siteInstanceHost
+ let recips =
+ map encodeRouteHome $
+ map renderLocalActor actors ++
+ map renderLocalStage stages
+ return AP.Action
+ { AP.actionCapability = Nothing
+ , AP.actionSummary = Nothing
+ , AP.actionAudience = Audience recips [] [] [] [] []
+ , AP.actionFulfills = []
+ , AP.actionSpecific = AP.AcceptActivity AP.Accept
+ { AP.acceptObject = ObjURI hLocal luFollow
+ , AP.acceptResult = Nothing
}
- obiid <- insert OutboxItem
- { outboxItemOutbox = obidRecip
- , outboxItemActivity =
- persistJSONObjectFromDoc $ accept Nothing
- , outboxItemPublished = now
}
- obikhid <- encodeKeyHashid obiid
- let luAct = encodeRouteLocal $ actorOutboxItem actorRecip obikhid
- doc = accept $ Just luAct
- update obiid [OutboxItemActivity =. persistJSONObjectFromDoc doc]
- return obiid
-
- deliverFollowLocal now aidSender fsid unread obiidF obiidA ibidRecip = do
- mfid <- lift $ insertUnique $ Follow aidSender fsid (not hide) obiidF obiidA
- _ <- fromMaybeE mfid "Already following this object"
- ibiid <- lift $ insert $ InboxItem unread now
- lift $ insert_ $ InboxItemLocal ibidRecip obiidF ibiid
-
- deliverAcceptLocal now obiidAccept ibidAuthor = do
- ibiid <- insert $ InboxItem True now
- insert_ $ InboxItemLocal ibidAuthor obiidAccept ibiid
--}
inviteC
:: Entity Person
@@ -2042,13 +2005,6 @@ inviteC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips re
routes <- lookup p $ recipPeople localRecips
guard $ routePerson routes
- verifyRemoteAddressed remoteRecips u =
- fromMaybeE (verify u) "Given remote entity not addressed"
- where
- verify (ObjURI h lu) = do
- lus <- lookup h remoteRecips
- guard $ lu `elem` lus
-
insertCollab resource recipient inviteID = do
collabID <- insert Collab
case resource of
@@ -2740,279 +2696,377 @@ insertAcceptOnTicketStatus shrUser wi (WorkItemDetail _ ctx author) obiidResolve
resolveC
:: Entity Person
- -> Maybe HTML
- -> Audience URIMode
- -> Resolve URIMode
+ -> Actor
+ -> Maybe
+ (Either
+ (LocalActorBy Key, LocalActorBy KeyHashid, OutboxItemId)
+ FedURI
+ )
+ -> RecipientRoutes
+ -> [(Host, NonEmpty LocalURI)]
+ -> [Host]
+ -> AP.Action URIMode
+ -> AP.Resolve URIMode
-> ExceptT Text Handler OutboxItemId
-resolveC (Entity pidUser personUser) summary audience (Resolve uObject) = do
- error "resolveC temporarily disabled"
+resolveC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips remoteRecips fwdHosts action (AP.Resolve uObject) = do
-{-
+ -- Check input
+ maybeLocalWorkItem <-
+ nameExceptT "Resolve object" $ either Just (const Nothing) <$> do
+ routeOrRemote <- parseFedURI uObject
+ bitraverse
+ (\ r -> do
+ wiByHash <-
+ fromMaybeE (parseWorkItem r) "Not a work item route"
+ unhashWorkItemE wiByHash "Work item invalid keyhashid"
+ )
+ pure
+ routeOrRemote
+ capID <- fromMaybeE maybeCap "No capability provided"
+ -- Verify that the work item's tracker is addressed
+ for_ maybeLocalWorkItem $ \ wi -> do
+ trackerByHash <- hashLocalActor $ workItemActor wi
+ unless (actorIsAddressed localRecips trackerByHash) $
+ throwE "Work item's tracker not addressed by the Resolve"
- let shrUser = sharerIdent sharerUser
- object <- parseWorkItem "Resolve object" uObject
- ParsedAudience localRecips remoteRecips blinded fwdHosts <- do
- mrecips <- parseAudience audience
- fromMaybeE mrecips "Offer Ticket with no recipients"
- federation <- asksSite $ appFederation . appSettings
- unless (federation || null remoteRecips) $
- throwE "Federation disabled, but remote recipients specified"
- verifyHosterRecip localRecips "Parent" object
+ senderHash <- encodeKeyHashid senderPersonID
now <- liftIO getCurrentTime
- ticketDetail <- runWorkerExcept $ getWorkItemDetail "Object" object
- (obiid, doc, remotesHttp, maybeAccept) <- runDBExcept $ do
- (obiidResolve, docResolve, luResolve) <- lift $ insertResolveToOutbox shrUser now (personOutbox personUser) blinded
- remotesHttpResolve <- do
- wiFollowers <- askWorkItemFollowers
- let sieve =
- let (actors, colls) =
- workItemRecipSieve wiFollowers ticketDetail
- in makeRecipientSet
- actors
- (LocalPersonCollectionSharerFollowers shrUser :
- colls
- )
- moreRemoteRecips <-
- lift $
- deliverLocal'
- True
- (LocalActorSharer shrUser)
- (personInbox personUser)
- obiidResolve
- (localRecipSieve sieve False localRecips)
- unless (federation || null moreRemoteRecips) $
- throwE "Federation disabled, but recipient collection remote members found"
- lift $ deliverRemoteDB fwdHosts obiidResolve remoteRecips moreRemoteRecips
- maccept <-
- case widIdent ticketDetail of
- Right _ -> return Nothing
- Left (wi, ltid) -> Just <$> do
- mhoster <-
- lift $ runMaybeT $
- case wi of
- WorkItemSharerTicket shr _ _ -> do
- sid <- MaybeT $ getKeyBy $ UniqueSharer shr
- p <- MaybeT (getValBy $ UniquePersonIdent sid)
- return (personOutbox p, personInbox p)
- WorkItemProjectTicket shr prj _ -> do
- sid <- MaybeT $ getKeyBy $ UniqueSharer shr
- j <- MaybeT $ getValBy $ UniqueProject prj sid
- a <- lift $ getJust $ projectActor j
- return (actorOutbox a, actorInbox a)
- WorkItemRepoProposal shr rp _ -> do
- sid <- MaybeT $ getKeyBy $ UniqueSharer shr
- r <- MaybeT (getValBy $ UniqueRepo rp sid)
- return (repoOutbox r, repoInbox r)
- (obidHoster, ibidHoster) <- fromMaybeE mhoster "Ticket hoster not in DB"
- obiidAccept <- lift $ insertEmptyOutboxItem obidHoster now
- lift $ insertResolve ltid obiidResolve obiidAccept
- (docAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
- lift $ insertAcceptOnTicketStatus shrUser wi ticketDetail obiidResolve obiidAccept
- knownRemoteRecipsAccept <-
- lift $
- deliverLocal'
- False
- (workItemActor wi)
- ibidHoster
- obiidAccept
- localRecipsAccept
- lift $ (obiidAccept,docAccept,fwdHostsAccept,) <$>
- deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
- return (obiidResolve, docResolve, remotesHttpResolve, maccept)
- lift $ do
- forkWorker "resolveC: async HTTP Resolve delivery" $ deliverRemoteHttp' fwdHosts obiid doc remotesHttp
- for_ maybeAccept $ \ (obiidAccept, docAccept, fwdHostsAccept, remotesHttpAccept) ->
- forkWorker "resolveC: async HTTP Accept delivery" $ deliverRemoteHttp' fwdHostsAccept obiidAccept docAccept remotesHttpAccept
- return obiid
- where
- insertResolveToOutbox shrUser now obid blinded = do
- hLocal <- asksSite siteInstanceHost
- obiid <- insertEmptyOutboxItem obid now
- encodeRouteLocal <- getEncodeRouteLocal
- obikhid <- encodeKeyHashid obiid
- let luAct = encodeRouteLocal $ SharerOutboxItemR shrUser obikhid
- doc = Doc hLocal Activity
- { activityId = Just luAct
- , activityActor = encodeRouteLocal $ SharerR shrUser
- , activityCapability = Nothing
- , activitySummary = summary
- , activityAudience = blinded
- , activitySpecific = ResolveActivity $ Resolve uObject
- }
- update obiid [OutboxItemActivity =. persistJSONObjectFromDoc doc]
- return (obiid, doc, luAct)
- insertResolve ltid obiidResolve obiidAccept = do
+ (resolveID, deliverHttpResolve, maybeDeliverHttpAccept) <- runDBExcept $ do
+
+ workItemDB <- for maybeLocalWorkItem $ \ wi -> do
+
+ -- Find the work item and its tracker in DB, and verify the work
+ -- item isn't already resolved
+ (resource, actor, ticketID) <-
+ case wi of
+ WorkItemTicket deckID taskID -> do
+ maybeTicket <- lift $ getTicket deckID taskID
+ (Entity _ deck, _task, Entity ticketID _, _author, resolve) <-
+ fromMaybeE maybeTicket "No such ticket in DB"
+ verifyNothingE resolve "Ticket already resolved"
+ actor <- lift $ getJustEntity $ deckActor deck
+ return (GrantResourceDeck deckID, actor, ticketID)
+ WorkItemCloth loomID clothID -> do
+ maybeCloth <- lift $ getCloth loomID clothID
+ (Entity _ loom, _cloth, Entity ticketID _, _author, resolve, _merge) <-
+ fromMaybeE maybeCloth "No such MR in DB"
+ verifyNothingE resolve "MR already resolved"
+ actor <- lift $ getJustEntity $ loomActor loom
+ return (GrantResourceLoom loomID, actor, ticketID)
+
+ -- Verify the sender is authorized by the tracker to resolve work
+ -- items
+ capability <-
+ case capID of
+ Left (capActor, _, capItem) -> return (capActor, capItem)
+ Right _ -> throwE "Capability is a remote URI, i.e. not authored by the local tracker"
+ verifyCapability capability (Left senderPersonID) resource
+
+ return (wi, actor, ticketID)
+
+ -- Insert Resolve to sender's outbox
+ resolveID <- lift $ insertEmptyOutboxItem (actorOutbox senderActor) now
+ luResolve <- lift $ updateOutboxItem (LocalActorPerson senderPersonID) resolveID action
+
+ -- Deliver the Resolve activity to local recipients, and schedule
+ -- delivery for unavailable remote recipients
+ deliverHttpResolve <- do
+ sieve <- do
+ (actors, stages) <-
+ case maybeLocalWorkItem of
+ Nothing -> pure ([], [])
+ Just (WorkItemTicket deckID taskID) -> do
+ deckHash <- encodeKeyHashid deckID
+ taskHash <- encodeKeyHashid taskID
+ return
+ ( [LocalActorDeck deckHash]
+ , [ LocalStageDeckFollowers deckHash
+ , LocalStageTicketFollowers deckHash taskHash
+ ]
+ )
+ Just (WorkItemCloth loomID clothID) -> do
+ loomHash <- encodeKeyHashid loomID
+ clothHash <- encodeKeyHashid clothID
+ return
+ ( [LocalActorLoom loomHash]
+ , [ LocalStageLoomFollowers loomHash
+ , LocalStageClothFollowers loomHash clothHash
+ ]
+ )
+ let stages' = LocalStagePersonFollowers senderHash : stages
+ return $ makeRecipientSet actors stages'
+ let localRecipsFinal = localRecipSieve sieve False localRecips
+ deliverActivityDB
+ (LocalActorPerson senderHash) (personActor senderPerson)
+ localRecipsFinal remoteRecips fwdHosts resolveID action
+
+ -- Verify that the tracker has received the Resolve, resolve the work
+ -- item in DB, and publish Accept
+ maybeDeliverHttpAccept <- for workItemDB $ \ (wi, Entity trackerActorID trackerActor, ticketID) -> do
+
+ -- Verify tracker received the Resolve
+ verifyActorHasItem
+ trackerActorID
+ resolveID
+ "Local tracker didn't receive the Resolve"
+
+ -- Mark work item in DB as resolved by the Resolve
+ acceptID <-
+ lift $ insertEmptyOutboxItem (actorOutbox trackerActor) now
+ lift $ insertResolve ticketID resolveID acceptID
+
+ -- Insert an Accept activity to tracker's outbox
+ trackerStages <-
+ case wi of
+ WorkItemTicket deckID taskID -> do
+ deckHash <- encodeKeyHashid deckID
+ taskHash <- encodeKeyHashid taskID
+ return
+ [ LocalStageDeckFollowers deckHash
+ , LocalStageTicketFollowers deckHash taskHash
+ ]
+ WorkItemCloth loomID clothID -> do
+ loomHash <- encodeKeyHashid loomID
+ clothHash <- encodeKeyHashid clothID
+ return
+ [ LocalStageLoomFollowers loomHash
+ , LocalStageClothFollowers loomHash clothHash
+ ]
+ let acceptActors = [LocalActorPerson senderHash]
+ acceptStages =
+ LocalStagePersonFollowers senderHash : trackerStages
+ actionAccept <- prepareAccept luResolve acceptActors acceptStages
+ let trackerByKey = workItemActor wi
+ _ <- lift $ updateOutboxItem trackerByKey acceptID actionAccept
+
+ -- Deliver the Accept activity to local recipients, and schedule
+ -- delivery for unavailable remote recipients
+ let localRecipsAccept = makeRecipientSet acceptActors acceptStages
+ trackerByHash <- hashLocalActor trackerByKey
+ deliverActivityDB
+ trackerByHash trackerActorID localRecipsAccept [] []
+ acceptID actionAccept
+
+ -- Return instructions for HTTP delivery of Resolve and Accept to
+ -- remote recipients
+ return
+ ( resolveID
+ , deliverHttpResolve
+ , maybeDeliverHttpAccept
+ )
+
+ -- Launch asynchronous HTTP delivery of Resolve and Accept
+ lift $ do
+ forkWorker "resolveC: async HTTP Resolve delivery" deliverHttpResolve
+ for_ maybeDeliverHttpAccept $
+ forkWorker "resolveC: async HTTP Accept delivery"
+
+ return resolveID
+
+ where
+
+ insertResolve ticketID resolveID acceptID = do
trid <- insert TicketResolve
- { ticketResolveTicket = ltid
- , ticketResolveAccept = obiidAccept
+ { ticketResolveTicket = ticketID
+ , ticketResolveAccept = acceptID
}
insert_ TicketResolveLocal
{ ticketResolveLocalTicket = trid
- , ticketResolveLocalActivity = obiidResolve
+ , ticketResolveLocalActivity = resolveID
+ }
+
+ prepareAccept luResolve actors stages = do
+ encodeRouteHome <- getEncodeRouteHome
+ hLocal <- asksSite siteInstanceHost
+ let recips =
+ map encodeRouteHome $
+ map renderLocalActor actors ++
+ map renderLocalStage stages
+ return AP.Action
+ { AP.actionCapability = Nothing
+ , AP.actionSummary = Nothing
+ , AP.actionAudience = Audience recips [] [] [] [] []
+ , AP.actionFulfills = []
+ , AP.actionSpecific = AP.AcceptActivity AP.Accept
+ { AP.acceptObject = ObjURI hLocal luResolve
+ , AP.acceptResult = Nothing
+ }
}
- tid <- localTicketTicket <$> getJust ltid
- update tid [TicketStatus =. TSClosed]
--}
undoC
:: Entity Person
- -> Maybe HTML
- -> Audience URIMode
- -> Undo URIMode
+ -> Actor
+ -> Maybe
+ (Either
+ (LocalActorBy Key, LocalActorBy KeyHashid, OutboxItemId)
+ FedURI
+ )
+ -> RecipientRoutes
+ -> [(Host, NonEmpty LocalURI)]
+ -> [Host]
+ -> AP.Action URIMode
+ -> AP.Undo URIMode
-> ExceptT Text Handler OutboxItemId
-undoC (Entity _pidUser personUser) summary audience undo@(Undo uObject) = do
- error "undoC temporarily disabled"
+undoC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips remoteRecips fwdHosts action (AP.Undo uObject) = do
-{-
+ -- Check input
+ undone <-
+ first (\ (actor, _, item) -> (actor, item)) <$>
+ parseActivityURI uObject
-
- let shrUser = sharerIdent sharerUser
- object <- parseActivity uObject
- ParsedAudience localRecips remoteRecips blinded fwdHosts <- do
- mrecips <- parseAudience audience
- fromMaybeE mrecips "Undo with no recipients"
- federation <- asksSite $ appFederation . appSettings
- unless (federation || null remoteRecips) $
- throwE "Federation disabled, but remote recipients specified"
now <- liftIO getCurrentTime
- (obiid, doc, _lu, mwi) <- runDBExcept $ do
- (obiidUndo, docUndo, luUndo) <- lift $ insertUndoToOutbox shrUser now (personOutbox personUser) blinded
- mltid <- fmap join $ runMaybeT $ do
- object' <- MaybeT $ getActivity object
- deleteFollow shrUser object' <|> deleteResolve object'
- mwi <- lift $ traverse getWorkItem mltid
- return (obiidUndo, docUndo, luUndo, mwi)
- mticketDetail <-
- for mwi $ \ wi ->
- (wi,) <$> runWorkerExcept (getWorkItemDetail "Object" $ Left wi)
- wiFollowers <- askWorkItemFollowers
- let sieve =
- case mticketDetail of
- Nothing -> makeRecipientSet [] [LocalPersonCollectionSharerFollowers shrUser]
- Just (_wi, ticketDetail) ->
- let (actors, colls) =
- workItemRecipSieve wiFollowers ticketDetail
- in makeRecipientSet
- actors
- (LocalPersonCollectionSharerFollowers shrUser :
- colls
- )
- (remotes, maybeAccept) <- runDBExcept $ do
- remotesHttpUndo <- do
- moreRemoteRecips <-
- lift $
- deliverLocal'
- True
- (LocalActorSharer shrUser)
- (personInbox personUser)
- obiid
- (localRecipSieve sieve True localRecips)
- unless (federation || null moreRemoteRecips) $
- throwE "Federation disabled, but recipient collection remote members found"
- lift $ deliverRemoteDB fwdHosts obiid remoteRecips moreRemoteRecips
- maccept <- for mticketDetail $ \ (wi, ticketDetail) -> do
- mhoster <-
+ senderHash <- encodeKeyHashid senderPersonID
+
+ (undoID, deliverHttpUndo, maybeDeliverHttpAccept) <- runDBExcept $ do
+
+ -- Find the undone activity in our DB
+ undoneDB <- do
+ a <- getActivity undone
+ fromMaybeE a "Can't find undone in DB"
+
+ -- See if the undone activity is a Follow/Resolve on a local target
+ -- If it is, verify the relevant actor is addressed, verify
+ -- permissions, and perform the actual undoing in the DB
+ maybeUndoLocal <- do
+ maybeUndo <-
lift $ runMaybeT $
- case wi of
- WorkItemSharerTicket shr _ _ -> do
- sid <- MaybeT $ getKeyBy $ UniqueSharer shr
- p <- MaybeT (getValBy $ UniquePersonIdent sid)
- return (personOutbox p, personInbox p)
- WorkItemProjectTicket shr prj _ -> do
- sid <- MaybeT $ getKeyBy $ UniqueSharer shr
- j <- MaybeT $ getValBy $ UniqueProject prj sid
- a <- lift $ getJust $ projectActor j
- return (actorOutbox a, actorInbox a)
- WorkItemRepoProposal shr rp _ -> do
- sid <- MaybeT $ getKeyBy $ UniqueSharer shr
- r <- MaybeT (getValBy $ UniqueRepo rp sid)
- return (repoOutbox r, repoInbox r)
- (obidHoster, ibidHoster) <- fromMaybeE mhoster "Ticket hoster not in DB"
- obiidAccept <- lift $ insertEmptyOutboxItem obidHoster now
- (docAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
- lift $ insertAcceptOnTicketStatus shrUser wi ticketDetail obiid obiidAccept
- knownRemoteRecipsAccept <-
- lift $
- deliverLocal'
- False
- (workItemActor wi)
- ibidHoster
- obiidAccept
- localRecipsAccept
- lift $ (obiidAccept,docAccept,fwdHostsAccept,) <$>
- deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
- return (remotesHttpUndo, maccept)
+ Left <$> MaybeT (tryUnfollow undoneDB) <|>
+ Right <$> MaybeT (tryUnresolve undoneDB)
+ case maybeUndo of
+ Nothing -> pure Nothing
+ Just (Left (updateDB, actorID, Left followerSetID)) -> do
+ actorByKey <- lift $ getLocalActor actorID
+ unless (actorByKey == LocalActorPerson senderPersonID) $
+ throwE "Tryin to undo a Follow of someone else"
+ (fByKey, fActorID, _) <- do
+ followee <- lift $ getFollowee' followerSetID
+ getFollowee followee
+ fByHash <- hashLocalActor fByKey
+ unless (actorIsAddressed localRecips fByHash) $
+ throwE "Followee's actor not addressed by the Undo"
+ lift updateDB
+ fActor <- lift $ getJust fActorID
+ return $ Just
+ ( fByKey
+ , Entity fActorID fActor
+ , makeRecipientSet
+ [fByHash]
+ [LocalStagePersonFollowers senderHash]
+ , [LocalActorPerson senderHash]
+ , []
+ )
+ Just (Left (updateDB, actorID, Right uTarget)) -> do
+ actorByKey <- lift $ getLocalActor actorID
+ unless (actorByKey == LocalActorPerson senderPersonID) $
+ throwE "Trying to undo a Follow of someone else"
+ verifyRemoteAddressed remoteRecips uTarget
+ lift updateDB
+ return Nothing
+ Just (Right (updateDB, ticketID)) -> do
+ wiByKey <- lift $ getWorkItem ticketID
+ wiByHash <- hashWorkItem wiByKey
+ let resource = workItemResource wiByKey
+ actorByKey = workItemActor wiByKey
+ actorByHash = workItemActor wiByHash
+ unless (actorIsAddressed localRecips actorByHash) $
+ throwE "Work item's actor not addressed by the Undo"
+ capID <- fromMaybeE maybeCap "No capability provided"
+ capability <-
+ case capID of
+ Left (capActor, _, capItem) -> return (capActor, capItem)
+ Right _ -> throwE "Capability is a remote URI, i.e. not authored by the local tracker"
+ verifyCapability capability (Left senderPersonID) resource
+ lift updateDB
+ actorID <- do
+ maybeActor <- lift $ getLocalActorEntity actorByKey
+ case localActorID <$> maybeActor of
+ Nothing -> error "Actor entity not in DB"
+ Just aid -> pure aid
+ actor <- lift $ getJust actorID
+ return $ Just
+ ( actorByKey
+ , Entity actorID actor
+ , makeRecipientSet
+ [actorByHash]
+ [ localActorFollowers actorByHash
+ , workItemFollowers wiByHash
+ , LocalStagePersonFollowers senderHash
+ ]
+ , [LocalActorPerson senderHash]
+ , [ localActorFollowers actorByHash
+ , workItemFollowers wiByHash
+ , LocalStagePersonFollowers senderHash
+ ]
+ )
+
+ -- Insert the Undo activity to author's outbox
+ undoID <- lift $ insertEmptyOutboxItem (actorOutbox senderActor) now
+ luUndo <- lift $ updateOutboxItem (LocalActorPerson senderPersonID) undoID action
+
+ -- Deliver the Undo activity to local recipients, and schedule delivery
+ -- for unavailable remote recipients
+ deliverHttpUndo <- do
+ let sieve =
+ case maybeUndoLocal of
+ Nothing ->
+ makeRecipientSet
+ [] [LocalStagePersonFollowers senderHash]
+ Just (_, _, s, _, _) -> s
+ localRecipsFinal = localRecipSieve sieve False localRecips
+ deliverActivityDB
+ (LocalActorPerson senderHash) (personActor senderPerson)
+ localRecipsFinal remoteRecips fwdHosts undoID action
+
+ maybeDeliverHttpAccept <- for maybeUndoLocal $ \ (actorByKey, Entity actorID actor, _, acceptActors, acceptStages) -> do
+
+ -- Verify the relevant actor has received the Undp
+ verifyActorHasItem actorID undoID "Actor didn't receive the Undo"
+
+ -- Insert an Accept activity to actor's outbox
+ acceptID <- lift $ insertEmptyOutboxItem (actorOutbox actor) now
+ actionAccept <- prepareAccept luUndo acceptActors acceptStages
+ _luAccept <- lift $ updateOutboxItem actorByKey acceptID actionAccept
+
+ -- Deliver the Accept activity to local recipients, and schedule
+ -- delivery for unavailable remote recipients
+ let localRecipsAccept = makeRecipientSet acceptActors acceptStages
+ actorByHash <- hashLocalActor actorByKey
+ deliverActivityDB
+ actorByHash actorID localRecipsAccept [] []
+ acceptID actionAccept
+
+ -- Return instructions for HTTP delivery to remote recipients
+ return (undoID, deliverHttpUndo, maybeDeliverHttpAccept)
+
+ -- Launch asynchronous HTTP delivery of Undo and Accept
lift $ do
- forkWorker "undoC: async HTTP Undo delivery" $
- deliverRemoteHttp' fwdHosts obiid doc remotes
- for_ maybeAccept $ \ (obiidAccept, docAccept, fwdHostsAccept, remotesHttpAccept) ->
- forkWorker "undoC: async HTTP Accept delivery" $
- deliverRemoteHttp' fwdHostsAccept obiidAccept docAccept remotesHttpAccept
- return obiid
+ forkWorker "undoC: async HTTP Undo delivery" deliverHttpUndo
+ for_ maybeDeliverHttpAccept $
+ forkWorker "undoC: async HTTP Accept delivery"
+
+ return undoID
+
where
- insertUndoToOutbox shrUser now obid blinded = do
+
+ prepareAccept luUndo actors stages = do
+ encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
- obiid <- insertEmptyOutboxItem obid now
- encodeRouteLocal <- getEncodeRouteLocal
- obikhid <- encodeKeyHashid obiid
- let luAct = encodeRouteLocal $ SharerOutboxItemR shrUser obikhid
- doc = Doc hLocal Activity
- { activityId = Just luAct
- , activityActor = encodeRouteLocal $ SharerR shrUser
- , activityCapability = Nothing
- , activitySummary = summary
- , activityAudience = blinded
- , activitySpecific = UndoActivity $ Undo uObject
+ let recips =
+ map encodeRouteHome $
+ map renderLocalActor actors ++
+ map renderLocalStage stages
+ return AP.Action
+ { AP.actionCapability = Nothing
+ , AP.actionSummary = Nothing
+ , AP.actionAudience = Audience recips [] [] [] [] []
+ , AP.actionFulfills = []
+ , AP.actionSpecific = AP.AcceptActivity AP.Accept
+ { AP.acceptObject = ObjURI hLocal luUndo
+ , AP.acceptResult = Nothing
}
- update obiid [OutboxItemActivity =. persistJSONObjectFromDoc doc]
- return (obiid, doc, luAct)
-
- deleteFollow shr (Left (actor, obiid)) = do
- deleteFollowLocal <|> deleteFollowRemote <|> deleteFollowRequest
- return Nothing
- where
- deleteFollowLocal = do
- fid <- MaybeT $ lift $ getKeyBy $ UniqueFollowFollow obiid
- unless (actor == LocalActorSharer shr) $
- lift $ throwE "Undoing someone else's follow"
- lift $ lift $ delete fid
- deleteFollowRemote = do
- frid <- MaybeT $ lift $ getKeyBy $ UniqueFollowRemoteFollow obiid
- unless (actor == LocalActorSharer shr) $
- lift $ throwE "Undoing someone else's follow"
- lift $ lift $ delete frid
- deleteFollowRequest = do
- frrid <- MaybeT $ lift $ getKeyBy $ UniqueFollowRemoteRequestActivity obiid
- unless (actor == LocalActorSharer shr) $
- lift $ throwE "Undoing someone else's follow"
- lift $ lift $ delete frrid
- deleteFollow _ (Right _) = mzero
-
- deleteResolve (Left (_, obiid)) = do
- Entity trlid trl <- MaybeT $ lift $ getBy $ UniqueTicketResolveLocalActivity obiid
- lift $ lift $ do
- let trid = ticketResolveLocalTicket trl
- tr <- getJust trid
- delete trlid
- delete trid
- let ltid = ticketResolveTicket tr
- tid <- localTicketTicket <$> getJust ltid
- update tid [TicketStatus =. TSTodo]
- return $ Just ltid
- deleteResolve (Right ractid) = do
- Entity trrid trr <- MaybeT $ lift $ getBy $ UniqueTicketResolveRemoteActivity ractid
- lift $ lift $ do
- let trid = ticketResolveRemoteTicket trr
- tr <- getJust trid
- delete trrid
- delete trid
- let ltid = ticketResolveTicket tr
- tid <- localTicketTicket <$> getJust ltid
- update tid [TicketStatus =. TSTodo]
- return $ Just ltid
--}
+ }
pushCommitsC
:: Entity Person
diff --git a/src/Vervis/Client.hs b/src/Vervis/Client.hs
index ff6c134..280364d 100644
--- a/src/Vervis/Client.hs
+++ b/src/Vervis/Client.hs
@@ -86,7 +86,6 @@ import Vervis.Model
import Vervis.Recipient
import Vervis.RemoteActorStore
import Vervis.Ticket
-import Vervis.WorkItem
makeServerInput
:: (MonadSite m, SiteEnv m ~ App)
@@ -782,7 +781,7 @@ applyPatches
-> ExceptT Text Handler (Maybe HTML, [Aud URIMode], Apply URIMode)
applyPatches senderHash uObject = do
- bundle <- parseProposalBundle "Apply object" uObject
+ bundle <- parseBundleRoute "Apply object" uObject
mrInfo <-
bifor bundle
(\ (loomID, clothID, _) -> do
diff --git a/src/Vervis/Data/Actor.hs b/src/Vervis/Data/Actor.hs
index 095daa4..27f0af3 100644
--- a/src/Vervis/Data/Actor.hs
+++ b/src/Vervis/Data/Actor.hs
@@ -20,6 +20,9 @@ module Vervis.Data.Actor
, stampRoute
, parseStampRoute
, localActorID
+ , parseLocalURI
+ , parseFedURI
+ , parseLocalActorE
)
where
@@ -104,3 +107,18 @@ localActorID (LocalActorGroup (Entity _ g)) = groupActor g
localActorID (LocalActorRepo (Entity _ r)) = repoActor r
localActorID (LocalActorDeck (Entity _ d)) = deckActor d
localActorID (LocalActorLoom (Entity _ l)) = loomActor l
+
+parseLocalURI :: LocalURI -> ExceptT Text Handler (Route App)
+parseLocalURI lu = fromMaybeE (decodeRouteLocal lu) "Not a valid route"
+
+parseFedURI :: FedURI -> ExceptT Text Handler (Either (Route App) FedURI)
+parseFedURI u@(ObjURI h lu) = do
+ hl <- hostIsLocal h
+ if hl
+ then Left <$> parseLocalURI lu
+ else pure $ Right u
+
+parseLocalActorE :: Route App -> ExceptT Text Handler (LocalActorBy Key)
+parseLocalActorE route = do
+ actorByHash <- fromMaybeE (parseLocalActor route) "Not an actor route"
+ unhashLocalActorE actorByHash "Invalid actor keyhashid"
diff --git a/src/Vervis/Data/Discussion.hs b/src/Vervis/Data/Discussion.hs
index 23b686b..332d0a4 100644
--- a/src/Vervis/Data/Discussion.hs
+++ b/src/Vervis/Data/Discussion.hs
@@ -39,26 +39,12 @@ import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
+import Vervis.Data.Actor
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Recipient
-parseLocalURI :: LocalURI -> ExceptT Text Handler (Route App)
-parseLocalURI lu = fromMaybeE (decodeRouteLocal lu) "Not a valid route"
-
-parseFedURI :: FedURI -> ExceptT Text Handler (Either (Route App) FedURI)
-parseFedURI u@(ObjURI h lu) = do
- hl <- hostIsLocal h
- if hl
- then Left <$> parseLocalURI lu
- else pure $ Right u
-
-parseLocalActorE :: Route App -> ExceptT Text Handler (LocalActorBy Key)
-parseLocalActorE route = do
- actorByHash <- fromMaybeE (parseLocalActor route) "Not an actor route"
- unhashLocalActorE actorByHash "Invalid actor keyhashid"
-
parseCommentId
:: Route App -> ExceptT Text Handler (LocalActorBy Key, LocalMessageId)
parseCommentId (PersonMessageR p m) =
diff --git a/src/Vervis/Data/Follow.hs b/src/Vervis/Data/Follow.hs
new file mode 100644
index 0000000..15f9985
--- /dev/null
+++ b/src/Vervis/Data/Follow.hs
@@ -0,0 +1,84 @@
+{- This file is part of Vervis.
+ -
+ - Written in 2022 by fr33domlover