Improve the AP async HTTP delivery API and per-actor key support

New iteration of the ActivityPub delivery implementation and interface.
Advantages over previous interface:

* When sending a ByteString body, the sender is explicitly passed as a
  parameter instead of JSON-parsing it out of the ByteString
* Clear 3 operations provided: Send, Resend and Forward
* Support for per-actor keys
* Actor-type-specific functions (e.g. deliverRemoteDB_D) removed
* Only the most high-level API is exposed to Activity handler code, making
  handler code more concise and clear

Also added in this patch:

* Foundation for per-actor key support
* 1 key per actor allowed in DB
* Disabled C2S and S2S handlers now un-exported for clarity
* Audience and capability parsing automatically done for all C2S handlers
* Audience and activity composition automatically done for Vervis.Client
  builder functions

Caveats:

* Actor documents still don't link to their per-actor keys; that should be the
  last piece to complete per-actor key support
* No moderation and anti-spam tools yet
* Delivery API doesn't yet have good integration of persistence layer, e.g.
  activity is separately encoded into bytestring for DB and for HTTP; this will
  be improved in the next iteration
* Periodic delivery now done in 3 separate steps, running sequentially; it
  simplifies the code, but may be changed for efficiency/robustness in the next
  iterations
* Periodic delivery collects per-actor keys in a
  1-DB-transaction-for-each-delivery fashion, rather than grabbing them in the
  big Esqueleto query (or keeping the signed output in the DB; this isn't done
  currently to allow for smooth actor key renewal)
* No support yet in the API for delivery where the actor key has already been
  fetched, rather than doing a DB transaction to grab it; such support would be
  just an optimization, so it's low-priority, but will be added in later
  iterations
This commit is contained in:
fr33domlover 2022-10-12 16:50:11 +00:00
parent 3c7b9f33e4
commit 32c87e3839
36 changed files with 2197 additions and 1584 deletions

View file

@ -40,6 +40,9 @@ actor-key-rotation:
amount: 1
unit: days
# Whether to use personal actor keys, or an instance-wide key
per-actor-keys: false
###############################################################################
# Development
###############################################################################

View file

@ -0,0 +1,5 @@
SigKey
actor ActorId
material ActorKey
UniqueSigKey actor

View file

@ -0,0 +1,121 @@
RemoteActor
RemoteActivity
Role
OutboxItem
Workflow
Forwarding
recipient RemoteActorId
activity RemoteActivityId
activityRaw ByteString
signature ByteString
forwarder ActorId
running Bool
UniqueForwarding recipient activity
ForwarderPerson
task ForwardingId
sender PersonId
UniqueForwarderPerson task
ForwarderGroup
task ForwardingId
sender GroupId
UniqueForwarderGroup task
ForwarderRepo
task ForwardingId
sender RepoId
UniqueForwarderRepo task
ForwarderLoom
task ForwardingId
sender LoomId
UniqueForwarderLoom task
ForwarderDeck
task ForwardingId
sender DeckId
UniqueForwarderDeck task
Person
username Username
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
actor ActorId
-- reviewFollow Bool
UniquePersonUsername username
UniquePersonLogin login
UniquePersonEmail email
UniquePersonActor actor
Group
actor ActorId
UniqueGroupActor actor
Repo
vcs VersionControlSystem
project DeckId Maybe
mainBranch Text
collabUser RoleId Maybe
collabAnon RoleId Maybe
actor ActorId
create OutboxItemId
loom LoomId Maybe
UniqueRepoActor actor
UniqueRepoCreate create
Deck
actor ActorId
workflow WorkflowId
nextTicket Int
wiki RepoId Maybe
collabUser RoleId Maybe
collabAnon RoleId Maybe
create OutboxItemId
UniqueDeckActor actor
UniqueDeckCreate create
Loom
nextTicket Int
actor ActorId
repo RepoId
create OutboxItemId
UniqueLoomActor actor
UniqueLoomRepo repo
UniqueLoomCreate create
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
Outbox
Inbox
FollowerSet

View file

@ -13,13 +13,13 @@
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.ActorKey
module Crypto.ActorKey
( ActorKey ()
, generateActorKey
, actorKeyRotator
, actorKeyPublicBin
, actorKeySign
-- , actorKeyVerify
, actorKeyVerify
)
where
@ -195,3 +195,7 @@ actorKeyPublicBin = fromEd25519 . actorKeyPublic
actorKeySign :: ActorKey -> ByteString -> Signature
actorKeySign (ActorKey sec pub) = Signature . convert . sign sec pub
actorKeyVerify :: ActorKey -> ByteString -> Signature -> Either String Bool
actorKeyVerify akey input (Signature sig) =
verifySignature (actorKeyPublicBin akey) input sig

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -17,6 +17,7 @@ module Data.Tuple.Local
( fst3
, fst4
, fst5
, fst6
, thd3
, fourth4
, fourth5
@ -32,6 +33,9 @@ fst4 (x, _, _, _) = x
fst5 :: (a, b, c, d, e) -> a
fst5 (x, _, _, _, _) = x
fst6 :: (a, b, c, d, e, f) -> a
fst6 (x, _, _, _, _, _) = x
thd3 :: (a, b, c) -> c
thd3 (_, _, z) = z

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -35,6 +35,7 @@ module Network.FedURI
, PageURI (..)
, RefURI (..)
, parseRefURI
, renderRefURI
)
where
@ -570,6 +571,9 @@ parseRefURI = toRefURI <=< toFullRefURI <=< parseFullURI
uriFromRefURI :: UriMode t => RefURI t -> URI
uriFromRefURI = fromFullURI . fromFullRefURI . fromRefURI
renderRefURI :: UriMode t => RefURI t -> Text
renderRefURI = renderFullURI . fromFullRefURI . fromRefURI
instance UriMode t => FromJSON (RefURI t) where
parseJSON = either fail return . toRefURI <=< parseJSON

File diff suppressed because it is too large Load diff

View file

@ -83,7 +83,9 @@ import Dvara
import Yesod.Mail.Send (runMailer)
import Control.Concurrent.ResultShare
import Crypto.ActorKey
import Data.KeyFile
import Development.PatchMediaType
import Network.FedURI
import Yesod.Hashids
import Yesod.MonadSite
@ -92,9 +94,8 @@ import Control.Concurrent.Local
import Data.List.NonEmpty.Local
import Web.Hashids.Local
import Vervis.ActorKey (generateActorKey, actorKeyRotator)
import Vervis.Darcs
import Vervis.Delivery
import Vervis.Web.Delivery
import Vervis.Foundation
import Vervis.Git
import Vervis.Hook
@ -122,8 +123,8 @@ import Vervis.Handler.Ticket
import Vervis.Migration (migrateDB)
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Path
import Vervis.Persist.Actor
import Vervis.Settings
import Vervis.Ssh (runSsh)
@ -160,8 +161,14 @@ makeFoundation appSettings = do
else loadFont "data/LinLibertineCut.svg"
appActorKeys <-
newTVarIO =<<
(,,) <$> generateActorKey <*> generateActorKey <*> pure True
if appPerActorKeys appSettings
then pure Nothing
else Just <$> do
keys <- (,,)
<$> generateActorKey
<*> generateActorKey
<*> pure True
newTVarIO keys
appInstanceMutex <- newInstanceMutex
@ -346,9 +353,9 @@ getAppSettings = loadYamlSettings [configSettingsYml] [] useEnv
develMain :: IO ()
develMain = develMainHelper getApplicationDev
actorKeyPeriodicRotator :: App -> IO ()
actorKeyPeriodicRotator :: App -> Maybe (IO ())
actorKeyPeriodicRotator app =
actorKeyRotator (appActorKeyRotation $ appSettings app) (appActorKeys app)
actorKeyRotator (appActorKeyRotation $ appSettings app) <$> appActorKeys app
deliveryRunner :: App -> IO ()
deliveryRunner app =
@ -399,7 +406,11 @@ appMain = do
app <- makeApplication foundation
-- Run actor signature key periodic generation thread
forkCheck $ actorKeyPeriodicRotator foundation
traverse_ forkCheck $ actorKeyPeriodicRotator foundation
-- If we're using per-actor keys, generate keys for local actors that don't
-- have a key and insert to DB
runWorker fillPerActorKeys foundation
-- Run periodic activity delivery retry runner
when (appFederation $ appSettings foundation) $

View file

@ -14,7 +14,9 @@
-}
module Vervis.Client
( --createThread
( makeServerInput
--, createThread
--, createReply
--, follow
--, followSharer
@ -28,7 +30,7 @@ module Vervis.Client
--, undoFollowTicket
--, undoFollowRepo
--, unresolve
offerPatches
, offerPatches
, offerMerge
, applyPatches
, createDeck
@ -85,6 +87,31 @@ import Vervis.RemoteActorStore
import Vervis.Ticket
import Vervis.WorkItem
makeServerInput
:: (MonadSite m, SiteEnv m ~ App)
=> Maybe FedURI
-> Maybe HTML
-> [Aud URIMode]
-> AP.SpecificActivity URIMode
-> m ( RecipientRoutes
, [(Host, NonEmpty LocalURI)]
, [Host]
, AP.Action URIMode
)
makeServerInput maybeCapURI maybeSummary audience specific = do
encodeRouteHome <- getEncodeRouteHome
let (recipientSet, remoteActors, fwdHosts, audLocal, audRemote) =
collectAudience audience
recips = map encodeRouteHome audLocal ++ audRemote
action = AP.Action
{ AP.actionCapability = maybeCapURI
, AP.actionSummary = maybeSummary
, AP.actionAudience = AP.Audience recips [] [] [] [] []
, AP.actionFulfills = []
, AP.actionSpecific = specific
}
return (recipientSet, remoteActors, fwdHosts, action)
{-
createThread
:: (MonadSite m, SiteEnv m ~ App)
@ -547,7 +574,7 @@ offerPatches
-> Maybe Text
-> PatchMediaType
-> NonEmpty Text
-> ExceptT Text Handler (Maybe HTML, AP.Audience URIMode, AP.Ticket URIMode)
-> ExceptT Text Handler (Maybe HTML, [Aud URIMode], AP.Ticket URIMode)
offerPatches senderHash title desc uTracker uTargetRepo maybeBranch typ diffs = do
tracker <- do
@ -567,7 +594,6 @@ offerPatches senderHash title desc uTracker uTargetRepo maybeBranch typ diffs =
descHtml <- ExceptT . pure $ renderPandocMarkdown desc
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
let audAuthor =
@ -583,10 +609,7 @@ offerPatches senderHash title desc uTracker uTargetRepo maybeBranch typ diffs =
[luTracker]
(maybeToList $ remoteActorFollowers remoteActor)
(_, _, _, audLocal, audRemote) =
collectAudience [audAuthor, audTracker]
recips = map encodeRouteHome audLocal ++ audRemote
audience = [audAuthor, audTracker]
luSender = encodeRouteLocal $ PersonR senderHash
ObjURI hTargetRepo luTargetRepo = uTargetRepo
@ -630,7 +653,7 @@ offerPatches senderHash title desc uTracker uTargetRepo maybeBranch typ diffs =
)
}
return (Nothing, AP.Audience recips [] [] [] [] [], ticket)
return (Nothing, audience, ticket)
offerMerge
:: KeyHashid Person
@ -641,7 +664,7 @@ offerMerge
-> Maybe Text
-> FedURI
-> Maybe Text
-> ExceptT Text Handler (Maybe HTML, AP.Audience URIMode, AP.Ticket URIMode)
-> ExceptT Text Handler (Maybe HTML, [Aud URIMode], AP.Ticket URIMode)
offerMerge senderHash title desc uTracker uTargetRepo maybeTargetBranch uOriginRepo maybeOriginBranch = do
tracker <- do
@ -661,7 +684,6 @@ offerMerge senderHash title desc uTracker uTargetRepo maybeTargetBranch uOriginR
descHtml <- ExceptT . pure $ renderPandocMarkdown desc
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
let audAuthor =
@ -677,10 +699,7 @@ offerMerge senderHash title desc uTracker uTargetRepo maybeTargetBranch uOriginR
[luTracker]
(maybeToList $ remoteActorFollowers remoteActor)
(_, _, _, audLocal, audRemote) =
collectAudience [audAuthor, audTracker]
recips = map encodeRouteHome audLocal ++ audRemote
audience = [audAuthor, audTracker]
ObjURI hTargetRepo luTargetRepo = uTargetRepo
ObjURI hOriginRepo luOriginRepo = uOriginRepo
@ -722,12 +741,12 @@ offerMerge senderHash title desc uTracker uTargetRepo maybeTargetBranch uOriginR
)
}
return (Nothing, AP.Audience recips [] [] [] [] [], ticket)
return (Nothing, audience, ticket)
applyPatches
:: KeyHashid Person
-> FedURI
-> ExceptT Text Handler (Maybe HTML, Audience URIMode, Apply URIMode)
-> ExceptT Text Handler (Maybe HTML, [Aud URIMode], Apply URIMode)
applyPatches senderHash uObject = do
bundle <- parseProposalBundle "Apply object" uObject
@ -818,27 +837,21 @@ applyPatches senderHash uObject = do
[luTracker]
(catMaybes [mluFollowers, Just luTicketFollowers])
(_, _, _, audLocal, audRemote) = collectAudience [audAuthor, audCloth]
audience = [audAuthor, audCloth]
recips = map encodeRouteHome audLocal ++ audRemote
return (Nothing, Audience recips [] [] [] [] [], Apply uObject target)
return (Nothing, audience, Apply uObject target)
createDeck
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> KeyHashid Person
-> Text
-> Text
-> m (Maybe HTML, Audience URIMode, AP.ActorDetail)
-> m (Maybe HTML, [Aud URIMode], AP.ActorDetail)
createDeck senderHash name desc = do
encodeRouteHome <- getEncodeRouteHome
let audAuthor =
AudLocal [] [LocalStagePersonFollowers senderHash]
(_, _, _, audLocal, audRemote) = collectAudience [audAuthor]
recips = map encodeRouteHome audLocal ++ audRemote
audience = [audAuthor]
detail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeTicketTracker
@ -847,7 +860,7 @@ createDeck senderHash name desc = do
, AP.actorSummary = Just desc
}
return (Nothing, AP.Audience recips [] [] [] [] [], detail)
return (Nothing, audience, detail)
createLoom
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -855,7 +868,7 @@ createLoom
-> Text
-> Text
-> KeyHashid Repo
-> m (Maybe HTML, Audience URIMode, AP.ActorDetail, NonEmpty FedURI)
-> m (Maybe HTML, [Aud URIMode], AP.ActorDetail, NonEmpty FedURI)
createLoom senderHash name desc repoHash = do
encodeRouteHome <- getEncodeRouteHome
@ -866,9 +879,7 @@ createLoom senderHash name desc repoHash = do
[LocalActorRepo repoHash]
[LocalStageRepoFollowers repoHash]
(_, _, _, audLocal, audRemote) = collectAudience [audAuthor, audRepo]
recips = map encodeRouteHome audLocal ++ audRemote
audience = [audAuthor, audRepo]
detail = AP.ActorDetail
{ AP.actorType = AP.ActorTypePatchTracker
@ -878,23 +889,19 @@ createLoom senderHash name desc repoHash = do
}
repo = encodeRouteHome $ RepoR repoHash
return (Nothing, AP.Audience recips [] [] [] [] [], detail, repo :| [])
return (Nothing, audience, detail, repo :| [])
createRepo
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> KeyHashid Person
-> Text
-> Text
-> m (Maybe HTML, Audience URIMode, AP.ActorDetail)
-> m (Maybe HTML, [Aud URIMode], AP.ActorDetail)
createRepo senderHash name desc = do
encodeRouteHome <- getEncodeRouteHome
let audAuthor =
AudLocal [] [LocalStagePersonFollowers senderHash]
(_, _, _, audLocal, audRemote) = collectAudience [audAuthor]
recips = map encodeRouteHome audLocal ++ audRemote
audience = [audAuthor]
detail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeRepo
@ -903,4 +910,4 @@ createRepo senderHash name desc = do
, AP.actorSummary = Just desc
}
return (Nothing, AP.Audience recips [] [] [] [] [], detail)
return (Nothing, audience, detail)

View file

@ -17,6 +17,8 @@ module Vervis.Data.Actor
( parseLocalActivityURI
, parseActivityURI
, activityRoute
, stampRoute
, parseStampRoute
)
where
@ -80,3 +82,17 @@ activityRoute (LocalActorGroup g) = GroupOutboxItemR g
activityRoute (LocalActorRepo r) = RepoOutboxItemR r
activityRoute (LocalActorDeck d) = DeckOutboxItemR d
activityRoute (LocalActorLoom l) = LoomOutboxItemR l
stampRoute :: LocalActorBy KeyHashid -> KeyHashid SigKey -> Route App
stampRoute (LocalActorPerson p) = PersonStampR p
stampRoute (LocalActorGroup g) = GroupStampR g
stampRoute (LocalActorRepo r) = RepoStampR r
stampRoute (LocalActorDeck d) = DeckStampR d
stampRoute (LocalActorLoom l) = LoomStampR l
parseStampRoute (PersonStampR p i) = Just (LocalActorPerson p, i)
parseStampRoute (GroupStampR g i) = Just (LocalActorGroup g, i)
parseStampRoute (RepoStampR r i) = Just (LocalActorRepo r, i)
parseStampRoute (DeckStampR d i) = Just (LocalActorDeck d, i)
parseStampRoute (LoomStampR l i) = Just (LocalActorLoom l, i)
parseStampRoute _ = Nothing

View file

@ -94,7 +94,7 @@ import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.Delivery
import Vervis.Web.Delivery
import Vervis.Federation.Auth
import Vervis.Foundation
import Vervis.Model

View file

@ -72,6 +72,7 @@ import Data.Time.Interval
import Network.HTTP.Signature hiding (requestHeaders)
import Yesod.HttpSignature
import Crypto.ActorKey
import Crypto.PublicVerifKey
import Database.Persist.JSON
import Network.FedURI
@ -94,7 +95,7 @@ import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.Data.Actor
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
@ -261,21 +262,14 @@ verifyActorSig (Verification malgo keyid input signature) = do
Right lu
_ -> throwE "Multiple ActivityPub-Actor headers"
verifySelfSig
:: LocalURI
verifySelfSigIK
:: TVar (ActorKey, ActorKey, Bool)
-> LocalActorBy Key
-> LocalRefURI
-> ByteString
-> Signature
-> ExceptT String Handler (LocalActorBy Key)
verifySelfSig luAuthor (LocalRefURI lruKey) input (Signature sig) = do
author <- do
route <-
fromMaybeE
(decodeRouteLocal luAuthor)
"Local author ID isn't a valid route"
fromMaybeE
(parseLocalActor route)
"Local author ID isn't an actor route"
-> ExceptT String Handler ()
verifySelfSigIK instanceKeys authorByKey (LocalRefURI lruKey) input sig = do
akey <- do
route <- do
luKey <-
@ -285,34 +279,82 @@ verifySelfSig luAuthor (LocalRefURI lruKey) input (Signature sig) = do
fromMaybeE
(decodeRouteLocal luKey)
"Local key ID isn't a valid route"
(akey1, akey2, _) <- liftIO . readTVarIO =<< getsYesod appActorKeys
(akey1, akey2, _) <- liftIO $ readTVarIO instanceKeys
case route of
ActorKey1R -> return akey1
ActorKey2R -> return akey2
_ -> throwE "Local key ID isn't an actor key route"
valid <-
ExceptT . pure $ verifySignature (actorKeyPublicBin akey) input sig
_ -> throwE "Local key ID isn't an instance key route"
valid <- ExceptT . pure $ actorKeyVerify akey input sig
unless valid $
throwE "Self sig verification says not valid"
localAuth <- unhashLocalActorE author "No such actor"
withExceptT T.unpack $ runDBExcept $ findLocalAuthInDB localAuth
return localAuth
withExceptT T.unpack $ runDBExcept $ findLocalAuthInDB authorByKey
where
findLocalAuthInDB (LocalActorPerson pid) = do
mp <- lift $ get pid
when (isNothing mp) $ throwE "No such person"
findLocalAuthInDB (LocalActorGroup gid) = do
mg <- lift $ get gid
when (isNothing mg) $ throwE "No such group"
findLocalAuthInDB (LocalActorRepo rid) = do
mr <- lift $ get rid
when (isNothing mr) $ throwE "No such repo"
findLocalAuthInDB (LocalActorDeck did) = do
md <- lift $ get did
when (isNothing md) $ throwE "No such deck"
findLocalAuthInDB (LocalActorLoom lid) = do
ml <- lift $ get lid
when (isNothing ml) $ throwE "No such loom"
findLocalAuthInDB actor = do
ma <- lift $ getLocalActorID actor
when (isNothing ma) $ throwE "No such actor in DB"
verifySelfSigAK
:: LocalActorBy Key
-> LocalRefURI
-> ByteString
-> Signature
-> ExceptT String Handler ()
verifySelfSigAK authorByKey (LocalRefURI lruKey) input sig = do
keyID <- do
luKey <-
case lruKey of
Left l -> return l
Right _ -> throwE "Local key ID has a fragment"
route <-
fromMaybeE
(decodeRouteLocal luKey)
"Local key ID isn't a valid route"
(holderByHash, keyHash) <-
fromMaybeE
(parseStampRoute route)
"Local key ID isn't an actor key route"
holderByKey <-
unhashLocalActorE
holderByHash
"Local key ID invalid holder keyhashid"
keyID <-
decodeKeyHashidE keyHash "Local key ID invalid sigkey keyhashid"
unless (holderByKey == authorByKey) $
throwE "Key belongs to someone else"
return keyID
akey <- withExceptT T.unpack $ runDBExcept $ do
actorID <- do
ma <- lift $ getLocalActorID authorByKey
fromMaybeE ma "No such actor in DB"
SigKey holderID akey <- getE keyID "No such key in DB"
unless (actorID == holderID) $ throwE "Key belongs to someone else"
return akey
valid <- ExceptT . pure $ actorKeyVerify akey input sig
unless valid $
throwE "Self sig verification says not valid"
verifySelfSig
:: LocalURI
-> LocalRefURI
-> ByteString
-> Signature
-> ExceptT String Handler (LocalActorBy Key)
verifySelfSig luAuthor lruKey input sig = do
authorByKey <- do
route <-
fromMaybeE
(decodeRouteLocal luAuthor)
"Local author ID isn't a valid route"
authorByHash <-
fromMaybeE
(parseLocalActor route)
"Local author ID isn't an actor route"
unhashLocalActorE authorByHash "No such actor"
maybeKeys <- asksSite appActorKeys
case maybeKeys of
Nothing -> verifySelfSigAK authorByKey lruKey input sig
Just keys -> verifySelfSigIK keys authorByKey lruKey input sig
return authorByKey
verifyForwardedSig
:: Host

View file

@ -73,7 +73,7 @@ import Vervis.Access
import Vervis.ActivityPub
import Vervis.Data.Actor
import Vervis.Data.Collab
import Vervis.Delivery
import Vervis.Web.Delivery
import Vervis.FedURI
import Vervis.Federation.Auth
import Vervis.Federation.Util
@ -163,22 +163,18 @@ personInviteF now recipHash author body mfwd luInvite invite = (,Nothing) <$> do
if inviteeIsRecip
then makeRecipientSet [] [LocalStagePersonFollowers recipHash]
else makeRecipientSet [] []
remoteRecips <-
insertRemoteActivityToLocalInboxes
False inviteID $
localRecipSieve'
sieve False False localRecips
(sig,) <$> deliverRemoteDB_P (actbBL body) inviteID personRecipID sig remoteRecips
forwardActivityDB
(actbBL body) localRecips sig (personActor personRecip)
(LocalActorPerson recipHash) sieve inviteID
-- Launch asynchronous HTTP forwarding of the Invite activity
case mhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
Just mremotesHttpFwd -> do
for_ mremotesHttpFwd $ \ (sig, remotes) ->
forkWorker "personInviteF inbox-forwarding" $
deliverRemoteHTTP_P now recipHash (actbBL body) sig remotes
Just maybeForwardHttpInvite -> do
for_ maybeForwardHttpInvite $
forkWorker "personInviteF inbox-forwarding"
return $
case mremotesHttpFwd of
case maybeForwardHttpInvite of
Nothing -> "Inserted to inbox, no inbox-forwarding to do"
Just _ -> "Inserted to inbox and ran inbox-forwarding of the Invite"
@ -282,26 +278,13 @@ topicInviteF now recipByHash author body mfwd luInvite invite = do
-- and schedule delivery for unavailable remote members of
-- them
for mfwd $ \ (localRecips, sig) -> do
let sieve =
makeRecipientSet [] [localActorFollowers $ grantResourceLocalActor recipByHash]
remoteRecips <-
insertRemoteActivityToLocalInboxes
False inviteID $
localRecipSieve'
sieve False False localRecips
case recipByKey of
GrantResourceRepo repoID -> do
repoHash <- encodeKeyHashid repoID
fwds <- deliverRemoteDB_R (actbBL body) inviteID repoID sig remoteRecips
return $ deliverRemoteHTTP_R now repoHash (actbBL body) sig fwds
GrantResourceDeck deckID -> do
deckHash <- encodeKeyHashid deckID
fwds <- deliverRemoteDB_D (actbBL body) inviteID deckID sig remoteRecips
return $ deliverRemoteHTTP_D now deckHash (actbBL body) sig fwds
GrantResourceLoom loomID -> do
loomHash <- encodeKeyHashid loomID
fwds <- deliverRemoteDB_L (actbBL body) inviteID loomID sig remoteRecips
return $ deliverRemoteHTTP_L now loomHash (actbBL body) sig fwds
let recipLocalActor =
grantResourceLocalActor recipByHash
sieve =
makeRecipientSet [] [localActorFollowers recipLocalActor]
forwardActivityDB
(actbBL body) localRecips sig recipActorID
recipLocalActor sieve inviteID
-- Launch asynchronous HTTP forwarding of the Invite activity
case mhttp of
@ -335,21 +318,6 @@ topicAcceptF
:: (PersistRecordBackend topic SqlBackend, ToBackendKey SqlBackend topic)
=> (topic -> ActorId)
-> (forall f. f topic -> GrantResourceBy f)
-> ( BL.ByteString
-> RemoteActivityId
-> Key topic
-> ByteString
-> [((InstanceId, Host), NonEmpty RemoteRecipient)]
-> ReaderT SqlBackend Handler
[((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, Key fwder))]
)
-> ( UTCTime
-> KeyHashid topic
-> BL.ByteString
-> ByteString
-> [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, Key fwder))]
-> Worker ()
)
-> UTCTime
-> KeyHashid topic
-> RemoteAuthor
@ -358,7 +326,7 @@ topicAcceptF
-> LocalURI
-> AP.Accept URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
topicAcceptF topicActor topicResource deliverRemoteDB deliverRemoteHTTP now recipHash author body mfwd luAccept accept = (,Nothing) <$> do
topicAcceptF topicActor topicResource now recipHash author body mfwd luAccept accept = (,Nothing) <$> do
-- Check input
acceptee <- parseAccept accept
@ -428,74 +396,54 @@ topicAcceptF topicActor topicResource deliverRemoteDB deliverRemoteHTTP now reci
-- Forward the Accept activity to relevant local stages, and
-- schedule delivery for unavailable remote members of them
maybeRemotesHttpFwdAccept <- lift $ for mfwd $ \ (localRecips, sig) -> do
let recipByHash = grantResourceLocalActor $ topicResource recipHash
maybeHttpFwdAccept <- lift $ for mfwd $ \ (localRecips, sig) -> do
let sieve =
makeRecipientSet [] [localActorFollowers $ grantResourceLocalActor $ topicResource recipHash]
remoteRecips <-
insertRemoteActivityToLocalInboxes
False acceptID $
localRecipSieve'
sieve False False localRecips
(sig,) <$> deliverRemoteDB (actbBL body) acceptID recipKey sig remoteRecips
makeRecipientSet [] [localActorFollowers recipByHash]
forwardActivityDB
(actbBL body) localRecips sig recipActorID recipByHash
sieve acceptID
remotesHttpGrant <- lift $ do
deliverHttpGrant <- do
-- Enable the Collab in our DB
grantID <- insertEmptyOutboxItem (actorOutbox recipActor) now
insert_ $ CollabEnable collabID grantID
grantID <- lift $ insertEmptyOutboxItem (actorOutbox recipActor) now
lift $ insert_ $ CollabEnable collabID grantID
-- Prepare a Grant activity and insert to topic's outbox
(docGrant, localRecipsGrant, remoteRecipsGrant, fwdHostsGrant) <-
insertGrantToOutbox inviteSender grantID
(actionGrant, localRecipsGrant, remoteRecipsGrant, fwdHostsGrant) <-
lift $ prepareGrant inviteSender
let recipByKey = grantResourceLocalActor $ topicResource recipKey
_luGrant <- lift $ updateOutboxItem recipByKey grantID actionGrant
-- Deliver the Grant to local recipients, and schedule delivery
-- for unavailable remote recipients
(grantID, docGrant, fwdHostsGrant,) <$> do
knownRemoteRecipsGrant <-
deliverLocal'
False
(grantResourceLocalActor $ topicResource recipHash)
recipActorID
grantID
localRecipsGrant
deliverRemoteDB'' fwdHostsGrant grantID remoteRecipsGrant knownRemoteRecipsGrant
deliverActivityDB
recipByHash recipActorID localRecipsGrant remoteRecipsGrant
fwdHostsGrant grantID actionGrant
return (maybeRemotesHttpFwdAccept, remotesHttpGrant)
return (maybeHttpFwdAccept, deliverHttpGrant)
-- Launch asynchronous HTTP forwarding of the Accept activity
case mhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
Just (mremotesHttpFwd, (grantID, docGrant, fwdHostsGrant, recipsGrant)) -> do
forkWorker "topicAcceptF Grant HTTP delivery" $
deliverRemoteHttp' fwdHostsGrant grantID docGrant recipsGrant
case mremotesHttpFwd of
Just (mhttpFwd, deliverHttpGrant) -> do
forkWorker "topicAcceptF Grant HTTP delivery" deliverHttpGrant
case mhttpFwd of
Nothing -> return "Sent a Grant, no inbox-forwarding to do"
Just (sig, remotes) -> do
forkWorker "topicAcceptF inbox-forwarding" $
deliverRemoteHTTP now recipHash (actbBL body) sig remotes
Just forwardHttpAccept -> do
forkWorker "topicAcceptF inbox-forwarding" forwardHttpAccept
return "Sent a Grant and ran inbox-forwarding of the Accept"
where
insertGrantToOutbox
:: Either (LocalActorBy Key) (FedURI, Maybe LocalURI)
-> OutboxItemId
-> ReaderT SqlBackend Handler
( AP.Doc AP.Activity URIMode
, RecipientRoutes
, [(Host, NonEmpty LocalURI)]
, [Host]
)
insertGrantToOutbox sender grantID = do
encodeRouteLocal <- getEncodeRouteLocal
prepareGrant sender = do
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
accepter <- getJust $ remoteAuthorId author
let topicByHash = grantResourceLocalActor $ topicResource recipHash
senderHash <- bitraverse hashLocalActor pure sender
grantHash <- encodeKeyHashid grantID
let audSender =
case senderHash of
@ -511,22 +459,19 @@ topicAcceptF topicActor topicResource deliverRemoteDB deliverRemoteHTTP now reci
collectAudience [audSender, audRecip, audTopic]
recips = map encodeRouteHome audLocal ++ audRemote
doc = AP.Doc hLocal AP.Activity
{ AP.activityId = Just $ encodeRouteLocal $ activityRoute topicByHash grantHash
, AP.activityActor = encodeRouteLocal $ renderLocalActor topicByHash
, AP.activityCapability = Nothing
, AP.activitySummary = Nothing
, AP.activityAudience = AP.Audience recips [] [] [] [] []
, AP.activityFulfills = [AP.acceptObject accept]
, AP.activitySpecific = AP.GrantActivity AP.Grant
action = AP.Action
{ AP.actionCapability = Nothing
, AP.actionSummary = Nothing
, AP.actionAudience = AP.Audience recips [] [] [] [] []
, AP.actionFulfills = [AP.acceptObject accept]
, AP.actionSpecific = AP.GrantActivity AP.Grant
{ AP.grantObject = Left AP.RoleAdmin
, AP.grantContext = encodeRouteHome $ renderLocalActor topicByHash
, AP.grantTarget = remoteAuthorURI author
}
}
update grantID [OutboxItemActivity =. persistJSONObjectFromDoc doc]
return (doc, recipientSet, remoteActors, fwdHosts)
return (action, recipientSet, remoteActors, fwdHosts)
repoAcceptF
:: UTCTime
@ -537,8 +482,7 @@ repoAcceptF
-> LocalURI
-> AP.Accept URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
repoAcceptF =
topicAcceptF repoActor GrantResourceRepo deliverRemoteDB_R deliverRemoteHTTP_R
repoAcceptF = topicAcceptF repoActor GrantResourceRepo
deckAcceptF
:: UTCTime
@ -549,8 +493,7 @@ deckAcceptF
-> LocalURI
-> AP.Accept URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
deckAcceptF =
topicAcceptF deckActor GrantResourceDeck deliverRemoteDB_D deliverRemoteHTTP_D
deckAcceptF = topicAcceptF deckActor GrantResourceDeck
loomAcceptF
:: UTCTime
@ -561,8 +504,7 @@ loomAcceptF
-> LocalURI
-> AP.Accept URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
loomAcceptF =
topicAcceptF loomActor GrantResourceLoom deliverRemoteDB_L deliverRemoteHTTP_L
loomAcceptF = topicAcceptF loomActor GrantResourceLoom
personGrantF
:: UTCTime
@ -616,21 +558,16 @@ personGrantF now recipHash author body mfwd luGrant grant = (,Nothing) <$> do
if targetIsRecip
then makeRecipientSet [] [LocalStagePersonFollowers recipHash]
else makeRecipientSet [] []
remoteRecips <-
insertRemoteActivityToLocalInboxes
False grantID $
localRecipSieve'
sieve False False localRecips
(sig,) <$> deliverRemoteDB_P (actbBL body) grantID personRecipID sig remoteRecips
forwardActivityDB
(actbBL body) localRecips sig (personActor personRecip)
(LocalActorPerson recipHash) sieve grantID
-- Launch asynchronous HTTP forwarding of the Invite activity
-- Launch asynchronous HTTP forwarding of the Grant activity
case mhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
Just mremotesHttpFwd -> do
for_ mremotesHttpFwd $ \ (sig, remotes) ->
forkWorker "personGrantF inbox-forwarding" $
deliverRemoteHTTP_P now recipHash (actbBL body) sig remotes
Just mhttpFwd -> do
for_ mhttpFwd $ forkWorker "personGrantF inbox-forwarding"
return $
case mremotesHttpFwd of
case mhttpFwd of
Nothing -> "Inserted to inbox, no inbox-forwarding to do"
Just _ -> "Inserted to inbox and ran inbox-forwarding of the Grant"

View file

@ -344,7 +344,7 @@ followF
iidAuthor = remoteAuthorInstance author
hAuthor = objUriAuthority $ remoteAuthorURI author
hostSection = ((iidAuthor, hAuthor), raInfo :| [])
(obiid, doc,) <$> deliverRemoteDB'' [] obiid [] [hostSection]
(obiid, doc,) <$> deliverRemoteDB [] obiid [] [hostSection]
else do
delete obiid
return $ Left "You're already a follower of me"
@ -698,7 +698,7 @@ sharerUndoF recipHash now author body mfwd luUndo (Undo uObj) = do
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$>
deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (result, mremotesHttpFwd, mremotesHttpAccept)
case mmmhttp of
Nothing -> return "Activity already in my inbox"
@ -802,7 +802,7 @@ projectUndoF recipHash now author body mfwd luUndo (Undo uObj) = do
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$>
deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (result, mremotesHttpFwd, mremotesHttpAccept)
case mmmhttp of
Nothing -> return "Activity already in my inbox"
@ -900,7 +900,7 @@ repoUndoF recipHash now author body mfwd luUndo (Undo uObj) = do
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$>
deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (result, mremotesHttpFwd, mremotesHttpAccept)
case mmmhttp of
Nothing -> return "Activity already in my inbox"

View file

@ -96,7 +96,7 @@ import Vervis.Cloth
import Vervis.Data.Actor
import Vervis.Data.Ticket
import Vervis.Darcs
import Vervis.Delivery
import Vervis.Web.Delivery
import Vervis.Federation.Auth
import Vervis.Federation.Util
import Vervis.FedURI
@ -107,6 +107,7 @@ import Vervis.Model
import Vervis.Model.Role
import Vervis.Model.Ticket
import Vervis.Path
import Vervis.Persist.Actor
import Vervis.Persist.Ticket
import Vervis.Query
import Vervis.Recipient
@ -358,60 +359,48 @@ deckOfferTicketF now recipDeckHash author body mfwd luOffer ticket uTarget = do
-- Find recipient deck in DB, returning 404 if doesn't exist because we're
-- in the deck's inbox post handler
maybeHttp <- lift $ runDB $ do
(recipDeckActorID, recipDeckActor) <- do
maybeHttp <- runDBExcept $ do
(recipDeckActorID, recipDeckActor) <- lift $ do
deck <- get404 recipDeckID
let actorID = deckActor deck
(actorID,) <$> getJust actorID
-- Insert the Offer to deck's inbox
mractid <- insertToInbox now author body (actorInbox recipDeckActor) luOffer False
mractid <- lift $ insertToInbox now author body (actorInbox recipDeckActor) luOffer False
for mractid $ \ offerID -> do
-- Forward the Offer activity to relevant local stages, and
-- schedule delivery for unavailable remote members of them
maybeHttpFwdOffer <- for mfwd $ \ (localRecips, sig) -> do
maybeHttpFwdOffer <- lift $ for mfwd $ \ (localRecips, sig) -> do
let sieve =
makeRecipientSet
[]
[LocalStageDeckFollowers recipDeckHash]
remoteRecips <-
insertRemoteActivityToLocalInboxes False offerID $
localRecipSieve' sieve False False localRecips
remoteRecipsHttp <-
deliverRemoteDB_D
(actbBL body) offerID recipDeckID sig remoteRecips
return $
deliverRemoteHTTP_D
now recipDeckHash (actbBL body) sig remoteRecipsHttp
forwardActivityDB
(actbBL body) localRecips sig recipDeckActorID
(LocalActorDeck recipDeckHash) sieve offerID
-- Insert the new ticket to our DB
acceptID <- insertEmptyOutboxItem (actorOutbox recipDeckActor) now
taskID <- insertTask now title desc source recipDeckID offerID acceptID
acceptID <- lift $ insertEmptyOutboxItem (actorOutbox recipDeckActor) now
taskID <- lift $ insertTask now title desc source recipDeckID offerID acceptID
-- Prepare an Accept activity and insert to deck's outbox
(docAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
insertAcceptToOutbox taskID acceptID
(actionAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
lift $ prepareAccept taskID
_luAccept <- lift $ updateOutboxItem (LocalActorDeck recipDeckID) acceptID actionAccept
-- Deliver the Accept to local recipients, and schedule delivery
-- for unavailable remote recipients
knownRemoteRecipsAccept <-
deliverLocal'
False (LocalActorDeck recipDeckHash) recipDeckActorID
acceptID localRecipsAccept
remoteRecipsHttpAccept <-
deliverRemoteDB''
fwdHostsAccept acceptID remoteRecipsAccept
knownRemoteRecipsAccept
deliverHttpAccept <-
deliverActivityDB
(LocalActorDeck recipDeckHash) recipDeckActorID
localRecipsAccept remoteRecipsAccept fwdHostsAccept
acceptID actionAccept
-- Return instructions for HTTP inbox-forwarding of the Offer
-- activity, and for HTTP delivery of the Accept activity to
-- remote recipients
return
( maybeHttpFwdOffer
, deliverRemoteHttp'
fwdHostsAccept acceptID docAccept remoteRecipsHttpAccept
)
return (maybeHttpFwdOffer, deliverHttpAccept)
-- Launch asynchronous HTTP forwarding of the Offer activity and HTTP
-- delivery of the Accept activity
@ -448,22 +437,11 @@ deckOfferTicketF now recipDeckHash author body mfwd luOffer ticket uTarget = do
}
insert $ TicketDeck tid deckID
insertAcceptToOutbox
:: TicketDeckId
-> OutboxItemId
-> ReaderT SqlBackend Handler
( AP.Doc AP.Activity URIMode
, RecipientRoutes
, [(Host, NonEmpty LocalURI)]
, [Host]
)
insertAcceptToOutbox taskID acceptID = do
prepareAccept taskID = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
taskHash <- encodeKeyHashid taskID
acceptHash <- encodeKeyHashid acceptID
ra <- getJust $ remoteAuthorId author
@ -479,26 +457,20 @@ deckOfferTicketF now recipDeckHash author body mfwd luOffer ticket uTarget = do
collectAudience [audSender, audTracker]
recips = map encodeRouteHome audLocal ++ audRemote
doc = AP.Doc hLocal AP.Activity
{ AP.activityId =
Just $ encodeRouteLocal $
DeckOutboxItemR recipDeckHash acceptHash
, AP.activityActor =
encodeRouteLocal $ DeckR recipDeckHash
, AP.activityCapability = Nothing
, AP.activitySummary = Nothing
, AP.activityAudience = AP.Audience recips [] [] [] [] []
, AP.activityFulfills = []
, AP.activitySpecific = AP.AcceptActivity AP.Accept
{ acceptObject = ObjURI hAuthor luOffer
, acceptResult =
action = AP.Action
{ AP.actionCapability = Nothing
, AP.actionSummary = Nothing
, AP.actionAudience = AP.Audience recips [] [] [] [] []
, AP.actionFulfills = []
, AP.actionSpecific = AP.AcceptActivity AP.Accept
{ AP.acceptObject = ObjURI hAuthor luOffer
, AP.acceptResult =
Just $ encodeRouteLocal $
TicketR recipDeckHash taskHash
}
}
update acceptID [OutboxItemActivity =. persistJSONObjectFromDoc doc]
return (doc, recipientSet, remoteActors, fwdHosts)
return (action, recipientSet, remoteActors, fwdHosts)
activityAlreadyInInbox hAct luAct inboxID = fmap isJust . runMaybeT $ do
instanceID <- MaybeT $ getKeyBy $ UniqueInstance hAct
@ -684,33 +656,28 @@ loomOfferTicketF now recipLoomHash author body mfwd luOffer ticket uTarget = do
return $ Right uClone
return $ Right $ maybeOriginRepo
maybeHttp <- lift $ runSiteDB $ do
maybeHttp <- runSiteDBExcept $ do
-- Insert the Offer to loom's inbox
mractid <- insertToInbox now author body (actorInbox recipLoomActor) luOffer False
mractid <- lift $ insertToInbox now author body (actorInbox recipLoomActor) luOffer False
for mractid $ \ offerID -> do
-- Forward the Offer activity to relevant local stages, and
-- schedule delivery for unavailable remote members of them
maybeHttpFwdOffer <- for mfwd $ \ (localRecips, sig) -> do
maybeHttpFwdOffer <- lift $ for mfwd $ \ (localRecips, sig) -> do
let sieve =
makeRecipientSet
[]
[LocalStageLoomFollowers recipLoomHash]
remoteRecips <-
insertRemoteActivityToLocalInboxes False offerID $
localRecipSieve' sieve False False localRecips
remoteRecipsHttp <-
deliverRemoteDB_L
(actbBL body) offerID recipLoomID sig remoteRecips
return $
deliverRemoteHTTP_L
now recipLoomHash (actbBL body) sig remoteRecipsHttp
forwardActivityDB
(actbBL body) localRecips sig
recipLoomActorID (LocalActorLoom recipLoomHash)
sieve offerID
-- Insert the new ticket to our DB
acceptID <- insertEmptyOutboxItem (actorOutbox recipLoomActor) now
ticketID <- insertTicket now title desc source offerID acceptID
clothID <- insertMerge recipLoomID ticketID maybeTargetBranch originOrBundle'
acceptID <- lift $ insertEmptyOutboxItem (actorOutbox recipLoomActor) now
ticketID <- lift $ insertTicket now title desc source offerID acceptID
clothID <- lift $ insertMerge recipLoomID ticketID maybeTargetBranch originOrBundle'
let maybePull =
let maybeTipInfo =
case tipInfo of
@ -720,30 +687,24 @@ loomOfferTicketF now recipLoomHash author body mfwd luOffer ticket uTarget = do
in (clothID, targetRepoID, hasBundle,) <$> maybeTipInfo
-- Prepare an Accept activity and insert to loom's outbox
(docAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
insertAcceptToOutbox clothID acceptID
(actionAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
lift $ prepareAccept clothID
_luAccept <- lift $ updateOutboxItem (LocalActorLoom recipLoomID) acceptID actionAccept
-- Deliver the Accept to local recipients, and schedule delivery
-- for unavailable remote recipients
knownRemoteRecipsAccept <-
deliverLocal'
False (LocalActorLoom recipLoomHash) recipLoomActorID
acceptID localRecipsAccept
remoteRecipsHttpAccept <-
deliverRemoteDB''
fwdHostsAccept acceptID remoteRecipsAccept
knownRemoteRecipsAccept
deliverHttpAccept <-
deliverActivityDB
(LocalActorLoom recipLoomHash) recipLoomActorID
localRecipsAccept remoteRecipsAccept
fwdHostsAccept acceptID actionAccept
-- Return instructions for HTTP inbox-forwarding of the Offer
-- activity, and for HTTP delivery of the Accept activity to
-- remote recipients, and for generating patches from
-- the origin repo
return
( maybeHttpFwdOffer
, deliverRemoteHttp'
fwdHostsAccept acceptID docAccept remoteRecipsHttpAccept
, maybePull
)
(maybeHttpFwdOffer, deliverHttpAccept, maybePull)
-- Launch asynchronous HTTP forwarding of the Offer activity and HTTP
-- delivery of the Accept activity, and generate patches if we opened
@ -811,22 +772,11 @@ loomOfferTicketF now recipLoomHash author body mfwd luOffer ticket uTarget = do
NE.map (Patch bundleID now typ) diffs
return clothID
insertAcceptToOutbox
:: TicketLoomId
-> OutboxItemId
-> WorkerDB
( AP.Doc AP.Activity URIMode
, RecipientRoutes
, [(Host, NonEmpty LocalURI)]
, [Host]
)
insertAcceptToOutbox clothID acceptID = do
prepareAccept clothID = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
clothHash <- encodeKeyHashid clothID
acceptHash <- encodeKeyHashid acceptID
ra <- getJust $ remoteAuthorId author
@ -842,26 +792,20 @@ loomOfferTicketF now recipLoomHash author body mfwd luOffer ticket uTarget = do
collectAudience [audSender, audTracker]
recips = map encodeRouteHome audLocal ++ audRemote
doc = AP.Doc hLocal AP.Activity
{ AP.activityId =
Just $ encodeRouteLocal $
LoomOutboxItemR recipLoomHash acceptHash
, AP.activityActor =
encodeRouteLocal $ LoomR recipLoomHash
, AP.activityCapability = Nothing
, AP.activitySummary = Nothing
, AP.activityAudience = AP.Audience recips [] [] [] [] []
, AP.activityFulfills = []
, AP.activitySpecific = AP.AcceptActivity AP.Accept
{ acceptObject = ObjURI hAuthor luOffer
, acceptResult =
action = AP.Action
{ AP.actionCapability = Nothing
, AP.actionSummary = Nothing
, AP.actionAudience = AP.Audience recips [] [] [] [] []
, AP.actionFulfills = []
, AP.actionSpecific = AP.AcceptActivity AP.Accept
{ AP.acceptObject = ObjURI hAuthor luOffer
, AP.acceptResult =
Just $ encodeRouteLocal $
ClothR recipLoomHash clothHash
}
}
update acceptID [OutboxItemActivity =. persistJSONObjectFromDoc doc]
return (doc, recipientSet, remoteActors, fwdHosts)
return (action, recipientSet, remoteActors, fwdHosts)
repoOfferTicketF
:: UTCTime
@ -918,7 +862,7 @@ repoOfferTicketF now recipHash author body mfwd luOffer ticket uTarget = do
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$>
deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (mremotesHttpFwd, obiidAccept, docAccept, fwdHostsAccept, recipsAccept)
case mmhttp of
Nothing -> return "Offer target isn't me, not using"
@ -1085,7 +1029,7 @@ repoAddBundleF now recipHash author body mfwd luAdd patches uTarget = do
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$>
deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (mremotesHttpFwd, mremotesHttpAccept)
case mhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
@ -1242,15 +1186,15 @@ loomApplyF now recipLoomHash author body mfwd luApply apply = (,Nothing) <$> do
-- Apply patches
applyPatches repoID maybeBranch diffs
maybeHttp <- lift $ runDB $ do
maybeHttp <- runDBExcept $ do
-- Insert the Apply to loom's inbox
mractid <- insertToInbox now author body (actorInbox recipLoomActor) luApply False
mractid <- lift $ insertToInbox now author body (actorInbox recipLoomActor) luApply False
for mractid $ \ applyID -> do
-- Forward the Apply activity to relevant local stages, and
-- schedule delivery for unavailable remote members of them
maybeHttpFwdApply <- for mfwd $ \ (localRecips, sig) -> do
maybeHttpFwdApply <- lift $ for mfwd $ \ (localRecips, sig) -> do
clothHash <- encodeKeyHashid clothID
let sieve =
makeRecipientSet
@ -1258,44 +1202,32 @@ loomApplyF now recipLoomHash author body mfwd luApply apply = (,Nothing) <$> do
[ LocalStageLoomFollowers recipLoomHash
, LocalStageClothFollowers recipLoomHash clothHash
]
remoteRecips <-
insertRemoteActivityToLocalInboxes False applyID $
localRecipSieve' sieve False False localRecips
remoteRecipsHttp <-
deliverRemoteDB_L
(actbBL body) applyID recipLoomID sig remoteRecips
return $
deliverRemoteHTTP_L
now recipLoomHash (actbBL body) sig remoteRecipsHttp
forwardActivityDB
(actbBL body) localRecips sig recipLoomActorID
(LocalActorLoom recipLoomHash) sieve applyID
-- Mark ticket in DB as resolved by the Apply
acceptID <-
insertEmptyOutboxItem (actorOutbox recipLoomActor) now
insertResolve ticketID applyID acceptID
lift $ insertEmptyOutboxItem (actorOutbox recipLoomActor) now
lift $ insertResolve ticketID applyID acceptID
-- Prepare an Accept activity and insert to loom's outbox
(docAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
insertAcceptToOutbox uCap clothID acceptID
(actionAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
lift $ prepareAccept clothID
_luAccept <- lift $ updateOutboxItem (LocalActorLoom recipLoomID) acceptID actionAccept
-- Deliver the Accept to local recipients, and schedule delivery
-- for unavailable remote recipients
knownRemoteRecipsAccept <-
deliverLocal'
False (LocalActorLoom recipLoomHash) recipLoomActorID
acceptID localRecipsAccept
remoteRecipsHttpAccept <-
deliverRemoteDB''
fwdHostsAccept acceptID remoteRecipsAccept
knownRemoteRecipsAccept
deliverHttpAccept <-
deliverActivityDB
(LocalActorLoom recipLoomHash) recipLoomActorID
localRecipsAccept remoteRecipsAccept fwdHostsAccept
acceptID actionAccept
-- Return instructions for HTTP inbox-forwarding of the Apply
-- activity, and for HTTP delivery of the Accept activity to
-- remote recipients
return
( maybeHttpFwdApply
, deliverRemoteHttp'
fwdHostsAccept acceptID docAccept remoteRecipsHttpAccept
)
return (maybeHttpFwdApply, deliverHttpAccept)
-- Launch asynchronous HTTP forwarding of the Apply activity and HTTP
-- delivery of the Accept activity
@ -1326,13 +1258,10 @@ loomApplyF now recipLoomHash author body mfwd luApply apply = (,Nothing) <$> do
}
update ticketID [TicketStatus =. TSClosed]
insertAcceptToOutbox uCap clothID acceptID = do
encodeRouteLocal <- getEncodeRouteLocal
prepareAccept clothID = do
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
clothHash <- encodeKeyHashid clothID
acceptHash <- encodeKeyHashid acceptID
ra <- getJust $ remoteAuthorId author
@ -1353,24 +1282,18 @@ loomApplyF now recipLoomHash author body mfwd luApply apply = (,Nothing) <$> do
collectAudience [audSender, audTracker]
recips = map encodeRouteHome audLocal ++ audRemote
doc = AP.Doc hLocal AP.Activity
{ AP.activityId =
Just $ encodeRouteLocal $
LoomOutboxItemR recipLoomHash acceptHash
, AP.activityActor =
encodeRouteLocal $ LoomR recipLoomHash
, AP.activityCapability = Just uCap
, AP.activitySummary = Nothing
, AP.activityAudience = AP.Audience recips [] [] [] [] []
, AP.activityFulfills = []
, AP.activitySpecific = AP.AcceptActivity AP.Accept
{ acceptObject = ObjURI hAuthor luApply
, acceptResult = Nothing
action = AP.Action
{ AP.actionCapability = Nothing
, AP.actionSummary = Nothing
, AP.actionAudience = AP.Audience recips [] [] [] [] []
, AP.actionFulfills = []
, AP.actionSpecific = AP.AcceptActivity AP.Accept
{ AP.acceptObject = ObjURI hAuthor luApply
, AP.acceptResult = Nothing
}
}
update acceptID [OutboxItemActivity =. persistJSONObjectFromDoc doc]
return (doc, recipientSet, remoteActors, fwdHosts)
return (action, recipientSet, remoteActors, fwdHosts)
personOfferDepF
:: UTCTime
@ -1430,7 +1353,7 @@ personOfferDepF now recipHash author body mfwd luOffer dep uTarget = do
(personInbox personRecip)
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$> deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$> deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (mremotesHttpFwd, mremotesHttpAccept)
case mhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
@ -1642,7 +1565,7 @@ deckOfferDepF now recipHash author body mfwd luOffer dep uTarget = do
(actorInbox actorRecip)
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$> deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$> deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (mremotesHttpFwd, mremotesHttpAccept)
case mhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
@ -1810,7 +1733,7 @@ repoOfferDepF now recipHash author body mfwd luOffer dep uTarget = do
(repoInbox repoRecip)
obiidAccept
localRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$> deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
(obiidAccept,docAccept,fwdHostsAccept,) <$> deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
return (mremotesHttpFwd, mremotesHttpAccept)
case mhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
@ -2007,7 +1930,7 @@ deckResolveF now recipHash author body mfwd luResolve (Resolve uObject) = do
obiidAccept
localRecipsAccept
(mremotesHttpFwd,obiidAccept,docAccept,fwdHostsAccept,) <$>
deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
case mmmmhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
Just mmmhttp ->
@ -2144,7 +2067,7 @@ repoResolveF now recipHash author body mfwd luResolve (Resolve uObject) = do
obiidAccept
localRecipsAccept
(mremotesHttpFwd,obiidAccept,docAccept,fwdHostsAccept,) <$>
deliverRemoteDB'' fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
deliverRemoteDB fwdHostsAccept obiidAccept remoteRecipsAccept knownRemoteRecipsAccept
case mmmmhttp of
Nothing -> return "I already have this activity in my inbox, doing nothing"
Just mmmhttp ->

View file

@ -86,7 +86,7 @@ import Vervis.Cloth
import Vervis.Data.Actor
import Vervis.Data.Collab
import Vervis.Data.Ticket
import Vervis.Delivery
import Vervis.Web.Delivery
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model

View file

@ -69,6 +69,7 @@ import Yesod.Mail.Send
import qualified Network.HTTP.Signature as S (Algorithm (..))
import Crypto.ActorKey
import Crypto.PublicVerifKey
import Network.FedURI
import Web.ActivityAccess
@ -83,7 +84,6 @@ import Text.Email.Local
import Text.Jasmine.Local (discardm)
import Yesod.Paginate.Local
import Vervis.ActorKey
import Vervis.FedURI
import Vervis.Hook
import Vervis.Model
@ -114,7 +114,7 @@ data App = App
, appLogger :: Logger
, appMailQueue :: Maybe (Chan (MailRecipe App))
, appSvgFont :: PreparedFont Double
, appActorKeys :: TVar (ActorKey, ActorKey, Bool)
, appActorKeys :: Maybe (TVar (ActorKey, ActorKey, Bool))
, appInstanceMutex :: InstanceMutex
, appCapSignKey :: AccessTokenSecretKey
, appHashidsContext :: HashidsContext
@ -140,6 +140,7 @@ type DeckKeyHashid = KeyHashid Deck
type LoomKeyHashid = KeyHashid Loom
type TicketDeckKeyHashid = KeyHashid TicketDeck
type TicketLoomKeyHashid = KeyHashid TicketLoom
type SigKeyKeyHashid = KeyHashid SigKey
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
@ -783,6 +784,7 @@ instance YesodActivityPub App where
siteInstanceHost = appInstanceHost . appSettings
sitePostSignedHeaders _ =
hRequestTarget :| [hHost, hDate, hDigest, AP.hActivityPubActor]
{-
siteGetHttpSign = do
(akey1, akey2, new1) <- liftIO . readTVarIO =<< asksSite appActorKeys
renderUrl <- askUrlRender
@ -791,6 +793,7 @@ instance YesodActivityPub App where
then (renderUrl ActorKey1R, akey1)
else (renderUrl ActorKey2R, akey2)
return (KeyId $ encodeUtf8 keyID, actorKeySign akey)
-}
instance YesodPaginate App where
sitePageParamName _ = "page"
@ -837,12 +840,16 @@ instance YesodBreadcrumbs App where
ReplyR _ -> ("", Nothing)
PersonStampR p k -> ("Stamp #" <> keyHashidText k, Just $ PersonR p)
GroupR g -> ("Team &" <> keyHashidText g, Just HomeR)
GroupInboxR g -> ("Inbox", Just $ GroupR g)
GroupOutboxR g -> ("Outbox", Just $ GroupR g)
GroupOutboxItemR g i -> (keyHashidText i, Just $ GroupOutboxR g)
GroupFollowersR g -> ("Followers", Just $ GroupR g)
GroupStampR g k -> ("Stamp #" <> keyHashidText k, Just $ GroupR g)
RepoR r -> ("Repo ^" <> keyHashidText r, Just HomeR)
RepoInboxR r -> ("Inbox", Just $ RepoR r)
RepoOutboxR r -> ("Outbox", Just $ RepoR r)
@ -871,6 +878,8 @@ instance YesodBreadcrumbs App where
RepoLinkR _ _ -> ("", Nothing)
RepoStampR r k -> ("Stamp #" <> keyHashidText k, Just $ RepoR r)
DeckR d -> ("Ticket Tracker =" <> keyHashidText d, Just HomeR)
DeckInboxR d -> ("Inbox", Just $ DeckR d)
DeckOutboxR d -> ("Outbox", Just $ DeckR d)
@ -886,6 +895,8 @@ instance YesodBreadcrumbs App where
DeckFollowR _ -> ("", Nothing)
DeckUnfollowR _ -> ("", Nothing)
DeckStampR d k -> ("Stamp #" <> keyHashidText k, Just $ DeckR d)
TicketR d t -> ("#" <> keyHashidText t, Just $ DeckTicketsR d)
TicketDiscussionR d t -> ("Discussion", Just $ TicketR d t)
TicketEventsR d t -> ("Events", Just $ TicketR d t)
@ -910,6 +921,8 @@ instance YesodBreadcrumbs App where
LoomFollowR _ -> ("", Nothing)
LoomUnfollowR _ -> ("", Nothing)
LoomStampR l k -> ("Stamp #" <> keyHashidText k, Just $ LoomR l)
ClothR l c -> ("#" <> keyHashidText c, Just $ LoomClothsR l)
ClothDiscussionR l c -> ("Discussion", Just $ ClothR l c)
ClothEventsR l c -> ("Events", Just $ ClothR l c)

View file

@ -37,43 +37,32 @@ where
import Control.Applicative
import Control.Concurrent.STM.TVar
import Control.Exception hiding (Handler)
import Control.Monad
import Control.Monad.Trans.Except
import Data.Bitraversable
import Data.List
import Data.Maybe
import Data.Text (Text)
import Data.Time.Clock
import Data.Traversable
import Database.Persist
import Text.Blaze.Html (preEscapedToHtml)
import Text.Blaze.Html.Renderer.Text
import Text.HTML.SanitizeXSS
import Yesod.Auth
import Yesod.Auth.Account
import Yesod.Auth.Account.Message
import Yesod.Core
import Yesod.Core.Widget
import Yesod.Form
import Yesod.Persist.Core
import qualified Data.ByteString.Char8 as BC
import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
import qualified Database.Esqueleto as E
import Dvara
import Database.Persist.JSON
import Network.FedURI
import Web.Text
import Yesod.ActivityPub
import Yesod.Auth.Unverified
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import Yesod.RenderSource
@ -83,26 +72,19 @@ import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Data.Either.Local
import Data.EventTime.Local
import Data.Time.Clock.Local
import Database.Persist.Local
import Yesod.Form.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.API
import Vervis.Client
import Vervis.Data.Actor
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Path
import Vervis.Recipient
import Vervis.Settings
import Vervis.Ticket
import qualified Vervis.Darcs as D
import qualified Vervis.Git as G
import Vervis.Web.Actor
-- | Account verification email resend form
getResendVerifyEmailR :: Handler Html
@ -115,28 +97,11 @@ getResendVerifyEmailR = do
^{resendVerifyEmailWidget (username person) AuthR}
|]
getActorKey
:: ((ActorKey, ActorKey, Bool) -> ActorKey)
-> Route App
-> Handler TypedContent
getActorKey choose route = do
actorKey <-
liftIO . fmap (actorKeyPublicBin . choose) . readTVarIO =<<
getsYesod appActorKeys
encodeRouteLocal <- getEncodeRouteLocal
let key = AP.PublicKey
{ AP.publicKeyId = LocalRefURI $ Left $ encodeRouteLocal route
, AP.publicKeyExpires = Nothing
, AP.publicKeyOwner = AP.OwnerInstance
, AP.publicKeyMaterial = actorKey
}
provideHtmlAndAP key $ redirectToPrettyJSON route
getActorKey1R :: Handler TypedContent
getActorKey1R = getActorKey (\ (k1, _, _) -> k1) ActorKey1R
getActorKey1R = serveInstanceKey fst ActorKey1R
getActorKey2R :: Handler TypedContent
getActorKey2R = getActorKey (\ (_, k2, _) -> k2) ActorKey2R
getActorKey2R = serveInstanceKey snd ActorKey2R
getHomeR :: Handler Html
getHomeR = do
@ -1063,6 +1028,18 @@ fedUriField = Field
, fieldEnctype = UrlEncoded
}
capField
:: Field Handler
( FedURI
, Either
(LocalActorBy Key, LocalActorBy KeyHashid, OutboxItemId)
FedURI
)
capField = checkMMap toCap fst fedUriField
where
toCap u =
runExceptT $ (u,) <$> nameExceptT "Capability URI" (parseActivityURI u)
getSender :: Handler (Entity Person, Actor)
getSender = do
ep@(Entity _ p) <- requireAuth
@ -1153,7 +1130,9 @@ postPublishOfferMergeR = do
senderHash omgTitle omgDesc omgTracker
omgTargetRepo (Just omgTargetBranch)
omgOriginRepo (Just omgOriginBranch)
offerID <- offerTicketC ep a summary audience ticket omgTracker
(localRecips, remoteRecips, fwdHosts, action) <-
makeServerInput Nothing summary audience $ AP.OfferActivity $ AP.Offer (AP.OfferTicket ticket) omgTracker
offerID <- offerTicketC ep a Nothing localRecips remoteRecips fwdHosts action ticket omgTracker
if trackerLocal
then nameExceptT "Offer published but" $ runDBExcept $ do
ticketID <- do
@ -1175,10 +1154,9 @@ postPublishOfferMergeR = do
else setMessage "Offer published"
redirect dest
mergeForm :: Form (FedURI, FedURI)
mergeForm = renderDivs $ (,)
<$> areq fedUriField "Patch bundle to apply" Nothing
<*> areq fedUriField "Grant activity to use for authorization" Nothing
<*> areq capField "Grant activity to use for authorization" Nothing
getPublishMergeR :: Handler Html
getPublishMergeR = do
@ -1196,14 +1174,16 @@ postPublishMergeR = do
federation <- getsYesod $ appFederation . appSettings
unless federation badMethod
(uBundle, uCap) <- runFormPostRedirect PublishMergeR mergeForm
(uBundle, (uCap, cap)) <- runFormPostRedirect PublishMergeR mergeForm
(ep@(Entity pid _), a) <- getSender
senderHash <- encodeKeyHashid pid
result <- runExceptT $ do
(maybeSummary, audience, apply) <- applyPatches senderHash uBundle
applyC ep a (Just uCap) maybeSummary audience apply
(localRecips, remoteRecips, fwdHosts, action) <-
makeServerInput (Just uCap) maybeSummary audience (AP.ApplyActivity apply)
applyC ep a (Just cap) localRecips remoteRecips fwdHosts action apply
case result of
Left err -> do

View file

@ -622,7 +622,7 @@ postClothApplyR :: KeyHashid Loom -> KeyHashid TicketLoom -> Handler ()
postClothApplyR loomHash clothHash = do
ep@(Entity personID person) <- requireAuth
(grantIDs, proposal, actor) <- runDB $ do
(grantIDs, proposal, actor, loomID) <- runDB $ do
(Entity loomID _, _, _, _, _, proposal) <- getCloth404 loomHash clothHash
grantIDs <-
@ -636,7 +636,7 @@ postClothApplyR loomHash clothHash = do
actor <- getJust $ personActor person
return (map E.unValue grantIDs, proposal, actor)
return (map E.unValue grantIDs, proposal, actor, loomID)
result <- runExceptT $ do
@ -652,10 +652,13 @@ postClothApplyR loomHash clothHash = do
personHash <- encodeKeyHashid personID
(maybeSummary, audience, apply) <-
C.applyPatches personHash $ encodeRouteHome bundleRoute
let cap = (LocalActorLoom loomID, LocalActorLoom loomHash, grantID)
uCap <-
encodeRouteHome . LoomOutboxItemR loomHash <$>
encodeKeyHashid grantID
applyC ep actor (Just uCap) maybeSummary audience apply
(localRecips, remoteRecips, fwdHosts, action) <-
C.makeServerInput (Just uCap) maybeSummary audience (AP.ApplyActivity apply)
applyC ep actor (Just $ Left cap) localRecips remoteRecips fwdHosts action apply
case result of
Left e -> setMessage $ toHtml e

View file

@ -32,6 +32,7 @@ module Vervis.Handler.Deck
, postDeckFollowR
, postDeckUnfollowR
, getDeckStampR
@ -318,9 +319,11 @@ postDeckNewR = do
personEntity@(Entity personID person) <- requireAuth
personHash <- encodeKeyHashid personID
(maybeSummary, audience, detail) <- C.createDeck personHash name desc
(localRecips, remoteRecips, fwdHosts, action) <-
C.makeServerInput Nothing maybeSummary audience $ AP.CreateActivity $ AP.Create (AP.CreateTicketTracker detail Nothing) Nothing
actor <- runDB $ getJust $ personActor person
result <-
runExceptT $ createTicketTrackerC personEntity actor maybeSummary audience detail Nothing Nothing
runExceptT $ createTicketTrackerC personEntity actor Nothing localRecips remoteRecips fwdHosts action detail Nothing Nothing
case result of
Left e -> do
@ -378,6 +381,8 @@ postDeckFollowR _ = error "Temporarily disabled"
postDeckUnfollowR :: KeyHashid Deck -> Handler ()
postDeckUnfollowR _ = error "Temporarily disabled"
getDeckStampR :: KeyHashid Deck -> KeyHashid SigKey -> Handler TypedContent
getDeckStampR = servePerActorKey deckActor LocalActorDeck

View file

@ -21,6 +21,7 @@ module Vervis.Handler.Group
, getGroupOutboxItemR
, getGroupFollowersR
, getGroupStampR
@ -129,6 +130,9 @@ getGroupOutboxItemR = getOutboxItem GroupOutboxItemR groupActor
getGroupFollowersR :: KeyHashid Group -> Handler TypedContent
getGroupFollowersR = getActorFollowersCollection GroupFollowersR groupActor
getGroupStampR :: KeyHashid Group -> KeyHashid SigKey -> Handler TypedContent
getGroupStampR = servePerActorKey groupActor LocalActorGroup

View file

@ -26,6 +26,8 @@ module Vervis.Handler.Loom
, postLoomNewR
, postLoomFollowR
, postLoomUnfollowR
, getLoomStampR
)
where
@ -285,8 +287,10 @@ postLoomNewR = do
getJust $ personActor person
result <-
runExceptT $ createPatchTrackerC personEntity actor maybeSummary audience detail repos Nothing Nothing
result <- do
(localRecips, remoteRecips, fwdHosts, action) <-
C.makeServerInput Nothing maybeSummary audience $ AP.CreateActivity $ AP.Create (AP.CreatePatchTracker detail repos Nothing) Nothing
runExceptT $ createPatchTrackerC personEntity actor Nothing localRecips remoteRecips fwdHosts action detail repos Nothing Nothing
case result of
Left e -> do
@ -306,3 +310,6 @@ postLoomFollowR _ = error "Temporarily disabled"
postLoomUnfollowR :: KeyHashid Loom -> Handler ()
postLoomUnfollowR _ = error "Temporarily disabled"
getLoomStampR :: KeyHashid Loom -> KeyHashid SigKey -> Handler TypedContent
getLoomStampR = servePerActorKey loomActor LocalActorLoom

View file

@ -29,12 +29,15 @@ module Vervis.Handler.Person
, postPersonUnfollowR
, postReplyR
, getPersonStampR
)
where
import Control.Monad
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import Data.List.NonEmpty (NonEmpty)
import Data.Maybe
import Data.Text (Text)
import Data.Time.Clock
@ -67,11 +70,11 @@ import Data.Either.Local
import Database.Persist.Local
import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.API
import Vervis.Data.Actor
import Vervis.Federation.Auth
import Vervis.Federation.Collab
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
@ -272,12 +275,42 @@ postPersonOutboxR personHash = do
Just (PersonR actorHash) | actorHash == personHash -> return ()
_ -> throwE "Can't post activity attributed to someone else"
handle eperson actorDB (AP.Activity _mid _actorAP mcap summary audience _fulfills specific) =
checkFederation remoteRecips = do
federation <- asksSite $ appFederation . appSettings
unless (federation || null remoteRecips) $
throwE "Federation disabled, but remote recipients found"
handle eperson actorDB (AP.Activity _mid _actorAP muCap summary audience _fulfills specific) = do
maybeCap <- traverse (nameExceptT "Capability" . parseActivityURI) muCap
ParsedAudience localRecips remoteRecips blinded fwdHosts <- do
mrecips <- parseAudience audience
fromMaybeE mrecips "No recipients"
checkFederation remoteRecips
let action = AP.Action
{ AP.actionCapability = muCap
, AP.actionSummary = summary
, AP.actionAudience = blinded
, AP.actionFulfills = []
, AP.actionSpecific = specific
}
run :: ( Entity Person
-> Actor
-> Maybe
( Either
(LocalActorBy Key, LocalActorBy KeyHashid, OutboxItemId)
FedURI
)
-> RecipientRoutes
-> [(Host, NonEmpty LocalURI)]
-> [Host]
-> AP.Action URIMode
-> t
)
-> t
run f = f eperson actorDB maybeCap localRecips remoteRecips fwdHosts action
case specific of
AP.AcceptActivity accept ->
acceptC eperson actorDB summary audience accept
AP.ApplyActivity apply ->
applyC eperson actorDB mcap summary audience apply
AP.AcceptActivity accept -> run acceptC accept
AP.ApplyActivity apply -> run applyC apply
AP.CreateActivity (AP.Create obj mtarget) ->
case obj of
{-
@ -285,14 +318,13 @@ postPersonOutboxR personHash = do
createNoteC eperson sharer summary audience note mtarget
-}
AP.CreateTicketTracker detail mlocal ->
createTicketTrackerC eperson actorDB summary audience detail mlocal mtarget
run createTicketTrackerC detail mlocal mtarget
AP.CreateRepository detail vcs mlocal ->
createRepositoryC eperson actorDB summary audience detail vcs mlocal mtarget
run createRepositoryC detail vcs mlocal mtarget
AP.CreatePatchTracker detail repos mlocal ->
createPatchTrackerC eperson actorDB summary audience detail repos mlocal mtarget
run createPatchTrackerC detail repos mlocal mtarget
_ -> throwE "Unsupported Create 'object' type"
AP.InviteActivity invite ->
inviteC eperson actorDB mcap summary audience invite
AP.InviteActivity invite -> run inviteC invite
{-
AddActivity (AP.Add obj target) ->
case obj of
@ -306,8 +338,7 @@ postPersonOutboxR personHash = do
-}
AP.OfferActivity (AP.Offer obj target) ->
case obj of
AP.OfferTicket ticket ->
offerTicketC eperson actorDB summary audience ticket target
AP.OfferTicket ticket -> run offerTicketC ticket target
{-
OfferDep dep ->
offerDepC eperson sharer summary audience dep target
@ -428,3 +459,6 @@ postPersonUnfollowR _ = error "Temporarily disabled"
postReplyR :: KeyHashid Message -> Handler ()
postReplyR _ = error "Temporarily disabled"
getPersonStampR :: KeyHashid Person -> KeyHashid SigKey -> Handler TypedContent
getPersonStampR = servePerActorKey personActor LocalActorPerson

View file

@ -44,6 +44,7 @@ module Vervis.Handler.Repo
, postRepoLinkR
, getRepoStampR
@ -432,9 +433,11 @@ postRepoNewR = do
personEntity@(Entity personID person) <- requireAuth
personHash <- encodeKeyHashid personID
(maybeSummary, audience, detail) <- C.createRepo personHash name desc
(localRecips, remoteRecips, fwdHosts, action) <-
C.makeServerInput Nothing maybeSummary audience $ AP.CreateActivity $ AP.Create (AP.CreateRepository detail vcs Nothing) Nothing
actor <- runDB $ getJust $ personActor person
result <-
runExceptT $ createRepositoryC personEntity actor maybeSummary audience detail vcs Nothing Nothing
runExceptT $ createRepositoryC personEntity actor Nothing localRecips remoteRecips fwdHosts action detail vcs Nothing Nothing
case result of
Left e -> do
@ -702,6 +705,9 @@ postRepoLinkR repoHash loomHash = do
Right () -> setMessage "Repo successfully linked with loom!"
redirect $ RepoR repoHash
getRepoStampR :: KeyHashid Repo -> KeyHashid SigKey -> Handler TypedContent
getRepoStampR = servePerActorKey repoActor LocalActorRepo

View file

@ -2723,6 +2723,56 @@ changes hLocal ctx =
update ticketID [Ticket495Title =. plain]
-- 496
, addFieldPrimRequired "Bundle" False "auto"
-- 497
, addEntities model_497_sigkey
-- 498
, addFieldRefRequired''
"Forwarding"
(do ibid <- insert Inbox498
obid <- insert Outbox498
fsid <- insert FollowerSet498
insertEntity $ Actor498 "" "" defaultTime ibid obid fsid
)
(Just $ \ (Entity aidTemp aTemp) -> do
fs <- selectKeysList ([] :: [Filter Forwarding498]) []
for_ fs $ \ forwardingID -> do
actorIDs <-
sequenceA $ map runMaybeT
[do fp <- MaybeT $ getValBy $ UniqueForwarderPerson498 forwardingID
lift $ person498Actor <$> getJust (forwarderPerson498Sender fp)
,do fg <- MaybeT $ getValBy $ UniqueForwarderGroup498 forwardingID
lift $ group498Actor <$> getJust (forwarderGroup498Sender fg)
,do fr <- MaybeT $ getValBy $ UniqueForwarderRepo498 forwardingID
lift $ repo498Actor <$> getJust (forwarderRepo498Sender fr)
,do fd <- MaybeT $ getValBy $ UniqueForwarderDeck498 forwardingID
lift $ deck498Actor <$> getJust (forwarderDeck498Sender fd)
,do fl <- MaybeT $ getValBy $ UniqueForwarderLoom498 forwardingID
lift $ loom498Actor <$> getJust (forwarderLoom498Sender fl)
]
actorID <-
case catMaybes actorIDs of
[] -> error "No Forwarder* found!"
[a] -> return a
_ -> error "Multiple Forwarder* found!"
update forwardingID [Forwarding498Forwarder =. actorID]
delete aidTemp
delete $ actor498Inbox aTemp
delete $ actor498Outbox aTemp
delete $ actor498Followers aTemp
)
"forwarder"
"Actor"
-- 499
, removeEntity "ForwarderPerson"
-- 500
, removeEntity "ForwarderGroup"
-- 501
, removeEntity "ForwarderRepo"
-- 502
, removeEntity "ForwarderDeck"
-- 503
, removeEntity "ForwarderLoom"
]
migrateDB

View file

@ -298,6 +298,7 @@ import Database.Persist.Sql (SqlBackend)
import Text.Email.Validate (EmailAddress)
import Web.Text (HTML, PandocMarkdown)
import Crypto.ActorKey
import Development.PatchMediaType
import Development.PatchMediaType.Persist
@ -669,3 +670,9 @@ model_494_mr_origin = $(schema "494_2022-09-17_mr_origin")
makeEntitiesMigration "495"
$(modelFile "migrations/495_2022-09-21_ticket_title.model")
model_497_sigkey :: [Entity SqlBackend]
model_497_sigkey = $(schema "497_2022-09-29_sigkey")
makeEntitiesMigration "498"
$(modelFile "migrations/498_2022-10-03_forwarder.model")

View file

@ -31,6 +31,7 @@ import Text.Email.Validate (EmailAddress)
import Database.Persist.Schema.TH hiding (modelFile)
import Yesod.Auth.Account (PersistUserCredentials (..))
import Crypto.ActorKey
import Crypto.PublicVerifKey
import Database.Persist.EmailAddress
import Database.Persist.Graph.Class

View file

@ -18,25 +18,44 @@ module Vervis.Persist.Actor
, verifyLocalActivityExistsInDB
, getRemoteActorURI
, insertActor
, updateOutboxItem
, fillPerActorKeys
)
where
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger.CallStack
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import Data.Text (Text)
import Data.Traversable
import Database.Persist
import Database.Persist.Sql
import qualified Data.Text as T
import qualified Database.Esqueleto as E
import Crypto.ActorKey
import Database.Persist.JSON
import Network.FedURI
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Database.Persist.Local
import Vervis.Data.Actor
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Recipient
import Vervis.Settings
getLocalActor
:: MonadIO m => ActorId -> ReaderT SqlBackend m (LocalActorBy Key)
@ -93,3 +112,35 @@ insertActor now name desc = do
}
actorID <- insert actor
return $ Entity actorID actor
updateOutboxItem
:: (MonadSite m, SiteEnv m ~ App)
=> LocalActorBy Key
-> OutboxItemId
-> AP.Action URIMode
-> ReaderT SqlBackend m LocalURI
updateOutboxItem actorByKey itemID action = do
encodeRouteLocal <- getEncodeRouteLocal
hLocal <- asksSite siteInstanceHost
actorByHash <- hashLocalActor actorByKey
itemHash <- encodeKeyHashid itemID
let luId = encodeRouteLocal $ activityRoute actorByHash itemHash
luActor = encodeRouteLocal $ renderLocalActor actorByHash
doc = AP.Doc hLocal $ AP.makeActivity luId luActor action
update itemID [OutboxItemActivity =. persistJSONObjectFromDoc doc]
return luId
fillPerActorKeys :: Worker ()
fillPerActorKeys = do
perActor <- asksSite $ appPerActorKeys . appSettings
when perActor $ do
actorIDs <- runSiteDB $ E.select $ E.from $ \ (actor `E.LeftOuterJoin` sigkey) -> do
E.on $ E.just (actor E.^. ActorId) E.==. sigkey E.?. SigKeyActor
E.where_ $ E.isNothing $ sigkey E.?. SigKeyId
return $ actor E.^. ActorId
keys <- for actorIDs $ \ (E.Value actorID) -> do
key <- liftIO generateActorKey
return $ SigKey actorID key
runSiteDB $ insertMany_ keys
logInfo $
T.concat ["Filled ", T.pack (show $ length keys), " actor keys"]

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -113,6 +113,8 @@ data AppSettings = AppSettings
-- How often to generate a new actor key for making HTTP signatures
, appActorKeyRotation :: TimeInterval
-- | Whether to use personal actor keys, or an instance-wide key
, appPerActorKeys :: Bool
-- | Use detailed request logging system
, appDetailedRequestLogging :: Bool
@ -224,6 +226,7 @@ instance FromJSON AppSettings where
appHttpSigTimeLimit <- interval <$> o .: "request-time-limit"
appActorKeyRotation <- interval <$> o .: "actor-key-rotation"
appPerActorKeys <- o .:? "per-actor-keys" .!= False
appDetailedRequestLogging <- o .:? "detailed-logging" .!= defaultDev
appShouldLogAll <- o .:? "should-log-all" .!= defaultDev

View file

@ -22,6 +22,8 @@ module Vervis.Web.Actor
, getActorFollowersCollection
, getFollowingCollection
, handleRobotInbox
, serveInstanceKey
, servePerActorKey
)
where
@ -69,6 +71,7 @@ import qualified Data.Text.Lazy as TL
import qualified Data.Vector as V
import qualified Database.Esqueleto as E
import Crypto.ActorKey
import Database.Persist.JSON
import Network.FedURI
import Web.ActivityPub hiding (Project (..), ActorLocal (..))
@ -89,10 +92,11 @@ import Database.Persist.Local
import Yesod.Persist.Local
import qualified Data.Aeson.Encode.Pretty.ToEncoding as P
import qualified Web.ActivityPub as AP
import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.API
import Vervis.Data.Actor
import Vervis.FedURI
import Vervis.Federation.Auth
import Vervis.Foundation
@ -489,3 +493,63 @@ handleRobotInbox recipByHash handleSpecific now auth body = do
msig <- checkForwarding recipByHash
let mfwd = (localRecips,) <$> msig
handleSpecific now remoteAuthor body mfwd luActivity (activitySpecific $ actbActivity body)
actorKeyAP
:: ( MonadSite m, SiteEnv m ~ site
, SiteFedURI site, SiteFedURIMode site ~ u
)
=> Maybe (Route site) -> Route site -> ActorKey -> m (AP.PublicKey u)
actorKeyAP maybeHolderR keyR akey = do
encodeRouteLocal <- getEncodeRouteLocal
return AP.PublicKey
{ AP.publicKeyId = LocalRefURI $ Left $ encodeRouteLocal keyR
, AP.publicKeyExpires = Nothing
, AP.publicKeyOwner =
case maybeHolderR of
Nothing -> AP.OwnerInstance
Just holderR -> AP.OwnerActor $ encodeRouteLocal holderR
, AP.publicKeyMaterial = actorKeyPublicBin akey
}
serveInstanceKey
:: ((ActorKey, ActorKey) -> ActorKey)
-> Route App
-> Handler TypedContent
serveInstanceKey choose keyR = do
maybeKeys <- asksSite appActorKeys
case maybeKeys of
Nothing -> notFound
Just keys -> do
akey <- liftIO $ do
(akey1, akey2, _) <- readTVarIO keys
return $ choose (akey1, akey2)
keyAP <- actorKeyAP Nothing keyR akey
provideHtmlAndAP keyAP $ redirectToPrettyJSON keyR
servePerActorKey'
:: LocalActorBy KeyHashid
-> KeyHashid SigKey
-> ActorKey
-> Handler TypedContent
servePerActorKey' holderByHash keyHash akey = do
let holderR = renderLocalActor holderByHash
keyR = stampRoute holderByHash keyHash
keyAP <- actorKeyAP (Just holderR) keyR akey
provideHtmlAndAP keyAP $ redirectToPrettyJSON keyR
servePerActorKey
:: (PersistRecordBackend holder SqlBackend, ToBackendKey SqlBackend holder)
=> (holder -> ActorId)
-> (KeyHashid holder -> LocalActorBy KeyHashid)
-> KeyHashid holder
-> KeyHashid SigKey
-> Handler TypedContent
servePerActorKey holderActor localActorHolder holderHash keyHash = do
holderID <- decodeKeyHashid404 holderHash
keyID <- decodeKeyHashid404 keyHash
akey <- runDB $ do
actorID <- holderActor <$> get404 holderID
SigKey actorID' akey <- get404 keyID
unless (actorID' == actorID) notFound
return akey
servePerActorKey' (localActorHolder holderHash) keyHash akey

File diff suppressed because it is too large Load diff

View file

@ -78,6 +78,8 @@ module Web.ActivityPub
, Undo (..)
, Audience (..)
, SpecificActivity (..)
, Action (..)
, makeActivity
, Activity (..)
-- * Utilities
@ -92,8 +94,13 @@ module Web.ActivityPub
, hActivityPubForwarder
, hForwardingSignature
, hForwardedSignature
, httpPostAP
, httpPostAPBytes
, Envelope ()
, Errand ()
, sending
, retrying
, deliver
, forwarding
, forward
, Fetched (..)
, fetchAP
, fetchAP_T
@ -115,6 +122,7 @@ import Control.Applicative ((<|>), optional)
import Control.Exception (Exception, displayException, try)
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Writer (Writer)
import Crypto.Hash hiding (Context)
@ -132,7 +140,7 @@ import Data.Proxy
import Data.Semigroup (Endo, First (..))
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8, decodeUtf8, decodeUtf8')
import Data.Time.Clock (UTCTime)
import Data.Time.Clock
import Data.Traversable
import Network.HTTP.Client hiding (Proxy, proxy)
import Network.HTTP.Client.Conduit.ActivityPub (httpAPEither)
@ -152,6 +160,7 @@ import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as M
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Vector as V
import qualified Network.HTTP.Signature as S
import qualified Text.Email.Parser as E
@ -1679,6 +1688,25 @@ data SpecificActivity u
| ResolveActivity (Resolve u)
| UndoActivity (Undo u)
data Action u = Action
{ actionCapability :: Maybe (ObjURI u)
, actionSummary :: Maybe HTML
, actionAudience :: Audience u
, actionFulfills :: [ObjURI u]
, actionSpecific :: SpecificActivity u
}
makeActivity :: LocalURI -> LocalURI -> Action u -> Activity u
makeActivity luId luActor Action{..} = Activity
{ activityId = Just luId
, activityActor = luActor
, activityCapability = actionCapability
, activitySummary = actionSummary
, activityAudience = actionAudience
, activityFulfills = actionFulfills
, activitySpecific = actionSpecific
}
data Activity u = Activity
{ activityId :: Maybe LocalURI
, activityActor :: LocalURI
@ -1855,62 +1883,168 @@ hForwardedSignature = "Forwarded-Signature"
-- * Compute HTTP signature and add _Signature_ request header
-- * Perform the POST request
-- * Verify the response status is 2xx
{-
httpPostAP
:: (MonadIO m, UriMode u, ToJSON a)
=> Manager
-> ObjURI u
-> NonEmpty HeaderName
-> S.KeyId
-> (ByteString -> S.Signature)
-> Text
-> Maybe (Either (ObjURI u) ByteString)
-> a
-> ObjURI u
-> Maybe (Either (ObjURI u) ByteString)
-> m (Either APPostError (Response ()))
httpPostAP manager uri headers keyid sign uSender mfwd value =
httpPostAPBytes manager uri headers keyid sign uSender mfwd $ encode value
httpPostAP manager headers keyid sign uSender value =
httpPostAPBytes manager headers keyid sign uSender $ encode value
-}
data ForwardMode u
= SendNoForward
| SendAllowForward LocalURI
| ForwardBy (ObjURI u) ByteString
data Envelope u = Envelope
{ envelopeKey :: RefURI u
, envelopeSign :: ByteString -> S.Signature
, envelopeHolder :: Maybe LocalURI
, envelopeBody :: BL.ByteString
}
data Errand u = Errand
{ errandKey :: RefURI u
, errandSign :: ByteString -> S.Signature
, errandHolder :: Bool
, errandFwder :: LocalURI
, errandBody :: BL.ByteString
, errandProof :: ByteString
}
-- | Like 'httpPostAP', except it takes the object as a raw lazy
-- 'BL.ByteString'. It's your responsibility to make sure it's valid JSON.
httpPostAPBytes
:: (MonadIO m, UriMode u)
=> Manager
-> ObjURI u
-> NonEmpty HeaderName
-> S.KeyId
-> RefURI u
-> (ByteString -> S.Signature)
-> Text
-> Maybe (Either (ObjURI u) ByteString)
-> Maybe LocalURI
-> BL.ByteString
-> ForwardMode u
-> ObjURI u
-> m (Either APPostError (Response ()))
httpPostAPBytes manager uri headers keyid sign uSender mfwd body =
httpPostAPBytes manager headers ruKey@(RefURI hKey _) sign mluHolder body fwd uInbox@(ObjURI hInbox _) =
liftIO $ runExceptT $ do
req <- requestFromURI $ uriFromObjURI uri
req <- requestFromURI $ uriFromObjURI uInbox
let digest = formatHttpBodyDigest SHA256 "SHA-256" $ hashlazy body
req' =
setRequestCheckStatus $
consHeader hContentType typeActivityStreams2LD $
consHeader hActivityPubActor (encodeUtf8 uSender) $
maybe id (consHeader hActivityPubActor . TE.encodeUtf8 . renderObjURI . ObjURI hKey) mluHolder $
consHeader hDigest digest $
req { method = "POST"
, requestBody = RequestBodyLBS body
}
req'' <- tryExceptT APPostErrorSig $ signRequest headers Nothing keyid sign Nothing req'
keyid = S.KeyId $ TE.encodeUtf8 $ renderRefURI ruKey
now <- lift getCurrentTime
req'' <- except $ first APPostErrorSig $ signRequest headers Nothing keyid sign now req'
req''' <-
case mfwd of
Nothing -> return req''
Just (Left uRecip) ->
tryExceptT APPostErrorSig $
signRequestInto hForwardingSignature (hDigest :| [hActivityPubForwarder]) Nothing keyid sign Nothing $ consHeader hActivityPubForwarder (encodeUtf8 $ renderObjURI uRecip) req''
Just (Right sig) ->
case fwd of
SendNoForward -> return req''
SendAllowForward luRecip ->
except $ first APPostErrorSig $
signRequestInto hForwardingSignature (hDigest :| [hActivityPubForwarder]) Nothing keyid sign now $
consHeader hActivityPubForwarder (encodeUtf8 $ renderObjURI $ ObjURI hInbox luRecip) req''
ForwardBy uSender sig ->
return $
consHeader hForwardedSignature sig $
consHeader hActivityPubForwarder (encodeUtf8 uSender)
consHeader hActivityPubForwarder (encodeUtf8 $ renderObjURI uSender)
req''
tryExceptT APPostErrorHTTP $ httpNoBody req''' manager
where
consHeader n b r = r { requestHeaders = (n, b) : requestHeaders r }
tryExceptT adapt action = ExceptT $ first adapt <$> try action
sending
:: UriMode u
=> LocalRefURI
-> (ByteString -> S.Signature)
-> Bool
-> ObjURI u
-> LocalURI
-> Action u
-> Envelope u
sending lruKey sign holder uActor@(ObjURI hActor luActor) luId action =
Envelope
{ envelopeKey = RefURI hActor lruKey
, envelopeSign = sign
, envelopeHolder = guard holder >> Just luActor
, envelopeBody = encode $ Doc hActor $ makeActivity luId luActor action
}
retrying
:: RefURI u
-> (ByteString -> S.Signature)
-> Maybe LocalURI
-> BL.ByteString
-> Envelope u
retrying = Envelope
forwarding
:: LocalRefURI
-> (ByteString -> S.Signature)
-> Bool
-> ObjURI u
-> BL.ByteString
-> ByteString
-> Errand u
forwarding lruKey sign holder (ObjURI hFwder luFwder) body sig =
Errand
{ errandKey = RefURI hFwder lruKey
, errandSign = sign
, errandHolder = holder
, errandFwder = luFwder
, errandBody = body
, errandProof = sig
}
deliver
:: (MonadIO m, UriMode u)
=> Manager
-> NonEmpty HeaderName
-> Envelope u
-> Maybe LocalURI
-> ObjURI u
-> m (Either APPostError (Response ()))
deliver manager headers (Envelope ruKey sign mluHolder body) mluFwd uInbox =
httpPostAPBytes
manager
headers
ruKey
sign
mluHolder
body
(maybe SendNoForward SendAllowForward mluFwd)
uInbox
forward
:: (MonadIO m, UriMode u)
=> Manager
-> NonEmpty HeaderName
-> Errand u
-> ObjURI u
-> m (Either APPostError (Response ()))
forward manager headers (Errand ruKey@(RefURI hKey _) sign holder luFwder body sig) uInbox =
httpPostAPBytes
manager
headers
ruKey
sign
(guard holder >> Just luFwder)
body
(ForwardBy (ObjURI hKey luFwder) sig)
uInbox
-- | Result of GETing the keyId URI and processing the JSON document.
data Fetched = Fetched
{ fetchedPublicKey :: PublicVerifKey

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -15,22 +15,33 @@
module Yesod.ActivityPub
( YesodActivityPub (..)
, prepareToSend
, prepareToRetry
, deliverActivity
, deliverActivityBL
, deliverActivityBL'
, deliverActivityExcept
, deliverActivityThrow
, prepareToForward
, forwardActivity
, forwardActivityExcept
, forwardActivityThrow
, redirectToPrettyJSON
, provideHtmlAndAP
, provideHtmlAndAP'
, provideHtmlAndAP''
, provideHtmlFeedAndAP
, hostIsLocal
, verifyHostLocal
)
where
import Control.Exception
import Control.Exception.Base
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger.CallStack
import Control.Monad.Trans.Except
import Control.Monad.Trans.Writer
@ -56,6 +67,8 @@ import qualified Data.Text as T
import Network.HTTP.Signature
import qualified Network.HTTP.Signature as S
import Database.Persist.JSON
import Network.FedURI
import Web.ActivityPub
@ -63,18 +76,307 @@ import Yesod.FedURI
import Yesod.MonadSite
import Yesod.RenderSource
import qualified Web.ActivityPub as AP
class (Yesod site, SiteFedURI site) => YesodActivityPub site where
siteInstanceHost :: site -> Authority (SiteFedURIMode site)
sitePostSignedHeaders :: site -> NonEmpty HeaderName
{-
siteGetHttpSign :: (MonadSite m, SiteEnv m ~ site)
=> m (KeyId, ByteString -> Signature)
{-
siteSigVerRequiredHeaders :: site -> [HeaderName]
siteSigVerWantedHeaders :: site -> [HeaderName]
siteSigVerSeconds :: site -> Int
-}
deliverActivity'
prepareToSend
:: (MonadSite m, SiteEnv m ~ site, SiteFedURI site, SiteFedURIMode site ~ u)
=> Route site
-> (ByteString -> S.Signature)
-> Bool
-> Route site
-> Route site
-> AP.Action u
-> m (Envelope u)
prepareToSend keyR sign holder actorR idR action = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let lruKey = LocalRefURI $ Left $ encodeRouteLocal keyR
uActor = encodeRouteHome actorR
luId = encodeRouteLocal idR
return $ AP.sending lruKey sign holder uActor luId action
prepareToRetry
:: (MonadSite m, SiteEnv m ~ site, SiteFedURI site, SiteFedURIMode site ~ u)
=> Route site
-> (ByteString -> S.Signature)
-> Maybe (Route site)
-> BL.ByteString
-> m (Envelope u)
prepareToRetry keyR sign mHolderR body = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let ruKey =
let ObjURI h lu = encodeRouteHome keyR
in RefURI h $ LocalRefURI $ Left lu
mluHolder = encodeRouteLocal <$> mHolderR
return $ AP.retrying ruKey sign mluHolder body
deliverActivity
:: ( MonadSite m, SiteEnv m ~ site, SiteFedURIMode site ~ u
, YesodActivityPub site
, HasHttpManager site
)
=> Envelope u
-> Maybe LocalURI
-> ObjURI u
-> m (Either APPostError (Response ()))
deliverActivity envelope mluFwd uInbox = do
manager <- asksSite getHttpManager
headers <- asksSite sitePostSignedHeaders
AP.deliver manager headers envelope mluFwd uInbox
deliverActivityExcept
:: ( MonadSite m, SiteEnv m ~ site, SiteFedURIMode site ~ u
, YesodActivityPub site
, HasHttpManager site
)
=> Envelope u
-> Maybe LocalURI
-> ObjURI u
-> ExceptT APPostError m (Response ())
deliverActivityExcept envelope mluFwd uInbox =
ExceptT $ deliverActivity envelope mluFwd uInbox
deliverActivityThrow
:: ( MonadSite m, SiteEnv m ~ site, SiteFedURIMode site ~ u
, YesodActivityPub site
, HasHttpManager site
)
=> Envelope u
-> Maybe LocalURI
-> ObjURI u
-> m (Response ())
deliverActivityThrow envelope mluFwd uInbox = do
result <- deliverActivity envelope mluFwd uInbox
case result of
Left e -> liftIO $ throwIO e
Right response -> return response
prepareToForward
:: (MonadSite m, SiteEnv m ~ site, SiteFedURI site, SiteFedURIMode site ~ u)
=> Route site
-> (ByteString -> S.Signature)
-> Bool
-> Route site
-> BL.ByteString
-> ByteString
-> m (Errand u)
prepareToForward keyR sign holder fwderR body sig = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let lruKey = LocalRefURI $ Left $ encodeRouteLocal keyR
uFwder = encodeRouteHome fwderR
return $ AP.forwarding lruKey sign holder uFwder body sig
forwardActivity
:: ( MonadSite m, SiteEnv m ~ site
, SiteFedURI site, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
=> Errand u
-> ObjURI u
-> m (Either APPostError (Response ()))
forwardActivity errand uInbox = do
manager <- asksSite getHttpManager
headers <- asksSite sitePostSignedHeaders
AP.forward manager headers errand uInbox
forwardActivityExcept
:: ( MonadSite m, SiteEnv m ~ site
, SiteFedURI site, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
=> Errand u
-> ObjURI u
-> ExceptT APPostError m (Response ())
forwardActivityExcept errand uInbox = ExceptT $ forwardActivity errand uInbox
forwardActivityThrow
:: ( MonadSite m, SiteEnv m ~ site
, SiteFedURI site, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
=> Errand u
-> ObjURI u
-> m (Response ())
forwardActivityThrow errand uInbox = do
result <- forwardActivity errand uInbox
case result of
Left e -> liftIO $ throwIO e
Right response -> return response
{-
-- | An 'AP.Activity' ready for sending, attached to an actor key ready to sign
-- it
data Envelope u = Envelope
{ envelopeKey :: LocalRefURI
, envelopeSign :: ByteString -> S.Signature
, envelopeHolder :: Bool
, envelopeActor :: ObjURI u
, envelopeId :: LocalURI
, envelopeAction :: Action u
}
-}
{-
-- | An 'AP.Activity' ready for sending, attached to an actor key ready to sign
-- it
data Envelope site = Envelope
{ -- | Signing key's identifier URI
envelopeKey :: Route site
-- | Signing function, producing a signature for a given input
, envelopeSign :: ByteString -> Signature
-- | Whether the signing key is used for the whole instance, or a
-- personal key used only by one actor
, envelopeSharedKey :: Bool
-- | The actor signing and sending the activity
, envelopeActor :: Route site
-- | Activity's ID URI
, envelopeId :: Route site
-- | Activity document, just needing its actor and id to be filled in
, envelopeAction :: AP.Action (SiteFedURIMode site)
}
-}
{-
prepareActivity
:: Route site
-> (ByteString -> S.Signature)
-> Bool
-> Route site
-> Route site
-> AP.Action u
-> m (Envelope u)
prepareActivity keyR sign holder actorR idR action = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let lruKey = LocalRefURI $ Left $ encodeRouteLocal keyR
uActor = encodeRouteHome actorR
luId = encodeRouteLocal idR
return $ Envelope lruKey sign holder uActor luId action
return $ AP.send manager headers lruKey sign holder uActor luId action
sendActivity
:: Envelope u
-> Maybe LocalURI
-> ObjURI u
-> m (Either AP.APPostError (Response ()))
sendActivity (Envelope lruKey sign holder uActor luId action)
-}
{-
prepareSendActivity
:: ( MonadSite m
, SiteEnv m ~ site
, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
=> Route site
-> (ByteString -> S.Signature)
-> Bool
-> Route site
-> Route site
-> AP.Action u
-> m (Maybe LocalURI -> ObjURI u -> m (Either AP.APPostError (Response ())))
prepareSendActivity keyR sign holder actorR idR action = do
manager <- asksSite getHttpManager
headers <- asksSite sitePostSignedHeaders
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let lruKey = LocalRefURI $ Left $ encodeRouteLocal keyR
uActor = encodeRouteHome actorR
luId = encodeRouteLocal idR
return $ AP.send manager headers lruKey sign holder uActor luId action
resendActivity
:: ( MonadSite m
, SiteEnv m ~ site
, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
=> Route site
-> (ByteString -> S.Signature)
-> Maybe (Route site)
-> BL.ByteString
-> Maybe LocalURI
-> ObjURI u
-> m (Either AP.APPostError (Response ()))
resendActivity keyR sign mHolderR body mluFwd uInbox = do
manager <- asksSite getHttpManager
headers <- asksSite sitePostSignedHeaders
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let ruKey =
let ObjURI h lu = encodeRouteHome keyR
in RefURI h $ LocalRefURI $ Left lu
mluHolder = encodeRouteLocal <$> mHolderR
AP.resend manager headers ruKey sign mluHolder body mluFwd uInbox
forwardActivity
:: ( MonadSite m
, SiteEnv m ~ site
, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
-> Route site
-> (ByteString -> S.Signature)
-> Bool
-> Route site
-> BL.ByteString
-> ByteString
-> ObjURI u
-> m (Either APPostError (Response ()))
forwardActivity keyR sign holder fwderR body sig uInbox = do
manager <- asksSite getHttpManager
headers <- asksSite sitePostSignedHeaders
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let lruKey = LocalRefURI $ Left $ encodeRouteLocal keyR
uFwder = encodeRouteHome fwderR
AP.forward lruKey sign holder uFwder body sig uInbox
-}
{-
data Stamp site = Stamp
{ stampActor :: Route site
, stampKey :: Route site
, stampSign :: ByteString -> Signature
}
-- | An 'AP.Activity' ready for sending, attached to an actor key ready to sign
-- it
data Envelope site = Envelope
{ -- | Activity document, just needing its actor and id to be filled in
envelopeDoc :: AP.Action (SiteFedURIMode site)
-- | Activity's ID URI
, envelopeId :: Route site
-- | The actor signing and sending the activity
, envelopeActor :: Route site
-- | Signing key's identifier URI
, envelopeKey :: Route site
-- | Signing function, producing a signature for a given input
, envelopeSign :: ByteString -> Signature
}
deliverActivityBL
:: ( MonadSite m
, SiteEnv m ~ site
, SiteFedURIMode site ~ u
@ -83,13 +385,15 @@ deliverActivity'
)
=> ObjURI u
-> Maybe (ObjURI u)
-> Text
-> Stamp
-> BL.ByteString
-> m (Either APPostError (Response ()))
deliverActivity' inbox mfwd sender body = do
deliverActivityBL inbox mfwd (Stamp actorR keyR sign) body = do
manager <- asksSite getHttpManager
headers <- asksSite sitePostSignedHeaders
(keyid, sign) <- siteGetHttpSign
(sender, keyid) <- do
renderUrl <- askUrlRender
return (renderUrl actorR, KeyId $ renderUrl keyR)
result <-
httpPostAPBytes
manager inbox headers keyid sign sender (Left <$> mfwd) body
@ -115,48 +419,24 @@ deliverActivity
)
=> ObjURI u
-> Maybe (ObjURI u)
-> Doc Activity u
-> Envelope site
-> m (Either APPostError (Response ()))
deliverActivity inbox mfwd doc@(Doc hAct activity) =
let sender = renderObjURI $ ObjURI hAct (activityActor activity)
body = encode doc
in deliverActivity' inbox mfwd sender body
deliverActivity inbox mfwd (Envelope action idR actorR keyR sign) = do
encodeRouteLocal <- getEncodeRouteLocal
hLocal <- asksSite siteInstanceHost
let body =
encode $ Doc hLocal $
makeActivity
(encodeRouteLocal idR) (encodeRouteLocal actorR) action
deliverActivityBL inbox mfwd (Stamp actorR keyR sign) body
deliverActivityBL
:: ( MonadSite m
, SiteEnv m ~ site
, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
=> ObjURI u
-> Maybe (ObjURI u)
-> Route site
-> BL.ByteString
-> m (Either APPostError (Response ()))
deliverActivityBL inbox mfwd senderR body = do
renderUrl <- askUrlRender
let sender = renderUrl senderR
deliverActivity' inbox mfwd sender body
deliverActivityBL'
:: ( MonadSite m
, SiteEnv m ~ site
, SiteFedURIMode site ~ u
, HasHttpManager site
, YesodActivityPub site
)
=> ObjURI u
-> Maybe (ObjURI u)
-> BL.ByteString
-> m (Either APPostError (Response ()))
deliverActivityBL' inbox mfwd body = do
sender <-
case M.lookup ("actor" :: Text) =<< decode body of
Just (String t) -> return t
_ ->
liftIO $ throwIO $ userError "Couldn't extract actor from body"
deliverActivity' inbox mfwd sender body
data Errand site = Errand
{ errandDoc :: BL.ByteString
, errandProof :: ByteString
, errandActor :: Route site
, errandKey :: Route site
, errandSign :: ByteString -> Signature
}
forwardActivity
:: ( MonadSite m
@ -166,18 +446,16 @@ forwardActivity
, YesodActivityPub site
)
=> ObjURI u
-> ByteString
-> Route site
-> BL.ByteString
-> Errand site
-> m (Either APPostError (Response ()))
forwardActivity inbox sig rSender body = do
forwardActivity inbox (Errand doc sig actorR keyR sign) = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
manager <- asksSite getHttpManager
headers <- asksSite sitePostSignedHeaders
(keyid, sign) <- siteGetHttpSign
renderUrl <- askUrlRender
let sender = renderUrl rSender
result <-
httpPostAPBytes manager inbox headers keyid sign sender (Just $ Right sig) body
let uActor = encodeRouteHome actorR
lruKey = LocalRefURI $ Left $ encodeRouteLocal keyR
result <- AP.forward manager headers uActor lruKey sign doc inbox sig
case result of
Left err ->
logError $ T.concat
@ -190,6 +468,7 @@ forwardActivity inbox sig rSender body = do
, "> success: ", T.pack $ show $ responseStatus resp
]
return result
-}
redirectToPrettyJSON
:: (MonadHandler m, HandlerSite m ~ site) => Route site -> m a

View file

@ -118,6 +118,12 @@ Actor
UniqueActorOutbox outbox
UniqueActorFollowers followers
SigKey
actor ActorId
material ActorKey
UniqueSigKey actor
Person
username Username
login Text
@ -161,40 +167,11 @@ Forwarding
activity RemoteActivityId
activityRaw ByteString
signature ByteString
forwarder ActorId
running Bool
UniqueForwarding recipient activity
ForwarderPerson
task ForwardingId
sender PersonId
UniqueForwarderPerson task
ForwarderGroup
task ForwardingId
sender GroupId
UniqueForwarderGroup task
ForwarderRepo
task ForwardingId
sender RepoId
UniqueForwarderRepo task
ForwarderLoom
task ForwardingId
sender LoomId
UniqueForwarderLoom task
ForwarderDeck
task ForwardingId
sender DeckId
UniqueForwarderDeck task
-- ========================================================================= --
-- ========================================================================= --

View file

@ -150,6 +150,8 @@
/reply/#MessageKeyHashid ReplyR POST
/people/#PersonKeyHashid/stamps/#SigKeyKeyHashid PersonStampR GET
---- Group ------------------------------------------------------------------
/groups/#GroupKeyHashid GroupR GET
@ -158,6 +160,8 @@
/groups/#GroupKeyHashid/outbox/#OutboxItemKeyHashid GroupOutboxItemR GET
/groups/#GroupKeyHashid/followers GroupFollowersR GET
/groups/#GroupKeyHashid/stamps/#SigKeyKeyHashid GroupStampR GET
---- Repo --------------------------------------------------------------------
/repos/#RepoKeyHashid RepoR GET
@ -186,6 +190,8 @@
/repos/#RepoKeyHashid/enable-loom/#LoomKeyHashid RepoLinkR POST
/repos/#RepoKeyHashid/stamps/#SigKeyKeyHashid RepoStampR GET
---- Deck --------------------------------------------------------------------
/decks/#DeckKeyHashid DeckR GET
@ -203,6 +209,8 @@
/decks/#DeckKeyHashid/follow DeckFollowR POST
/decks/#DeckKeyHashid/unfollow DeckUnfollowR POST
/decks/#DeckKeyHashid/stamps/#SigKeyKeyHashid DeckStampR GET
---- Ticket ------------------------------------------------------------------
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid TicketR GET
@ -248,6 +256,8 @@
/looms/#LoomKeyHashid/follow LoomFollowR POST
/looms/#LoomKeyHashid/unfollow LoomUnfollowR POST
/looms/#LoomKeyHashid/stamps/#SigKeyKeyHashid LoomStampR GET
---- Cloth -------------------------------------------------------------------
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid ClothR GET

View file

@ -46,6 +46,7 @@ library
Control.Concurrent.Local
Control.Concurrent.ResultShare
Control.Monad.Trans.Except.Local
Crypto.ActorKey
Crypto.PubKey.Encoding
Crypto.PublicVerifKey
Darcs.Local.Repository
@ -127,7 +128,6 @@ library
Vervis.Access
Vervis.ActivityPub
Vervis.ActorKey
Vervis.API
Vervis.Avatar
Vervis.BinaryBody
@ -144,7 +144,6 @@ library
Vervis.Data.Collab
Vervis.Data.Ticket
Vervis.Delivery
Vervis.Discussion
--Vervis.Federation
Vervis.Federation.Auth
@ -231,6 +230,7 @@ library
Vervis.Web.Actor
Vervis.Web.Darcs
Vervis.Web.Delivery
Vervis.Web.Discussion
Vervis.Web.Git
Vervis.Web.Repo