Implement actor-model system and start moving Person actor to it

This patch makes Vervis temporarily unusable, because all actors' inbox POST
handlers use the new system, but the actual federation handler code hasn't been
ported. The next patches will port all the S2S activities supported so far, as
well as C2S.
This commit is contained in:
fr33domlover 2023-04-29 10:40:44 +00:00
parent 36c7ae0190
commit c9db823c8c
47 changed files with 2005 additions and 429 deletions

View file

@ -32,10 +32,12 @@ On Debian based distros, installation can be done like this:
$ sudo apt install libpq-dev zlib1g-dev libssl-dev libpcre3-dev $ sudo apt install libpq-dev zlib1g-dev libssl-dev libpcre3-dev
# (2) The Stack build tool # (2) Haskell development tools
Install stack. To install stack, go to its [website](https://haskellstack.org) Go to the [GHCup website](https://www.haskell.org/ghcup) and follow the
and follow the instructions. instructions.
$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
# (3) Version control systems Darcs and Git # (3) Version control systems Darcs and Git

View file

@ -0,0 +1,332 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020, 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Control.Concurrent.Actor
( Stage (..)
, TheaterFor ()
, ActFor ()
, MonadActor (..)
, asksEnv
, Next ()
, Message (..)
, startTheater
, callIO
, call
--, sendIO
, send
, sendManyIO
, sendMany
--, spawnIO
, spawn
, done
, doneAnd
, stop
)
where
import Control.Concurrent
import Control.Concurrent.STM.TVar
import Control.Monad
import Control.Monad.Fail
import Control.Monad.IO.Unlift
import Control.Monad.Logger.CallStack
import Control.Monad.STM
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import Data.Foldable
import Data.Hashable
import Data.HashMap.Strict (HashMap)
import Data.HashSet (HashSet)
import Data.Text (Text)
import Data.Traversable
import UnliftIO.Exception
import qualified Control.Exception.Annotated as AE
import qualified Control.Monad.Trans.RWS.Lazy as RWSL
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import qualified Data.Text as T
import Control.Concurrent.Return
type LogFunc = Loc -> LogSource -> LogLevel -> LogStr -> IO ()
class Stage a where
type StageKey a
type StageMessage a
type StageReturn a
-- | A set of live actors responding to messages
data TheaterFor s = TheaterFor
{ theaterMap :: TVar (HashMap (StageKey s) (Chan (StageMessage s, Either SomeException (StageReturn s) -> IO ())))
, theaterLog :: LogFunc
, theaterEnv :: s
}
-- | Actor monad in which message reponse actions are executed. Supports
-- logging, a read-only environment, and IO.
newtype ActFor s a = ActFor
{ unActFor :: LoggingT (ReaderT (TheaterFor s) IO) a
}
deriving
( Functor, Applicative, Monad, MonadFail, MonadIO, MonadLogger
, MonadLoggerIO
)
instance MonadUnliftIO (ActFor s) where
askUnliftIO =
ActFor $ withUnliftIO $ \ u ->
return $ UnliftIO $ unliftIO u . unActFor
withRunInIO inner =
ActFor $ withRunInIO $ \ run -> inner (run . unActFor)
runActor :: TheaterFor s -> ActFor s a -> IO a
runActor theater (ActFor action) =
runReaderT (runLoggingT action $ theaterLog theater) theater
class Monad m => MonadActor m where
type ActorEnv m
askEnv :: m (ActorEnv m)
liftActor :: ActFor (ActorEnv m) a -> m a
instance MonadActor (ActFor s) where
type ActorEnv (ActFor s) = s
askEnv = theaterEnv <$> askTheater
liftActor = id
instance MonadActor m => MonadActor (ReaderT r m) where
type ActorEnv (ReaderT r m) = ActorEnv m
askEnv = lift askEnv
liftActor = lift . liftActor
instance MonadActor m => MonadActor (MaybeT m) where
type ActorEnv (MaybeT m) = ActorEnv m
askEnv = lift askEnv
liftActor = lift . liftActor
instance MonadActor m => MonadActor (ExceptT e m) where
type ActorEnv (ExceptT e m) = ActorEnv m
askEnv = lift askEnv
liftActor = lift . liftActor
instance (Monoid w, MonadActor m) => MonadActor (RWSL.RWST r w s m) where
type ActorEnv (RWSL.RWST r w s m) = ActorEnv m
askEnv = lift askEnv
liftActor = lift . liftActor
asksEnv :: MonadActor m => (ActorEnv m -> a) -> m a
asksEnv f = f <$> askEnv
data Next = Stop | Proceed
class Message a where
summarize :: a -> Text
refer :: a -> Text
launchActorThread
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
, Hashable k, Eq k, Show k, Message m, Show r
)
=> Chan (m, Either SomeException r -> IO ())
-> TheaterFor s
-> k
-> (m -> ActFor s (r, ActFor s (), Next))
-> IO ()
launchActorThread chan theater actor behavior =
void $ forkIO $ runActor theater $ do
logInfo $ prefix <> "starting"
loop
logInfo $ prefix <> "bye"
where
prefix = T.concat ["[Actor '", T.pack $ show actor, "'] "]
loop = do
(message, respond) <- liftIO $ readChan chan
logInfo $ T.concat [prefix, "received: ", summarize message]
result <- try $ behavior message
proceed <-
case result of
Left e -> do
logError $ T.concat [prefix, "on ", refer message, " exception: ", T.pack $ displayException (e :: SomeException)]
liftIO $ respond $ Left e
return True
Right (value, after, next) -> do
logInfo $ T.concat [prefix, "on ", refer message, " result: ", T.pack $ show value]
liftIO $ respond $ Right value
after
case next of
Stop -> do
logInfo $ T.concat [prefix, "on ", refer message, " stopping"]
let tvar = theaterMap theater
liftIO $ atomically $ modifyTVar' tvar $ HM.delete actor
return False
Proceed -> do
logInfo $ T.concat [prefix, "on ", refer message, " done"]
return True
when proceed loop
-- | Launch the actor system
startTheater
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
, Hashable k, Eq k, Show k, Message m, Show r
)
=> LogFunc
-> s
-> [(k, m -> ActFor s (r, ActFor s (), Next))]
-> IO (TheaterFor s)
startTheater logFunc env actors = do
actorsWithChans <- for actors $ \ (key, behavior) -> do
chan <- newChan
return ((key, chan), behavior)
tvar <- newTVarIO $ HM.fromList $ map fst actorsWithChans
let theater = TheaterFor tvar logFunc env
for_ actorsWithChans $ \ ((key, chan), behavior) ->
launchActorThread chan theater key behavior
return theater
askTheater :: ActFor s (TheaterFor s)
askTheater = ActFor $ lift ask
lookupActor
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
, Eq k, Hashable k
)
=> TheaterFor s
-> k
-> IO (Maybe (Chan (m, Either SomeException r -> IO ())))
lookupActor (TheaterFor tvar _ _) actor = HM.lookup actor <$> readTVarIO tvar
-- | Same as 'call', except it takes the theater as a parameter.
callIO
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
, Eq k, Hashable k
)
=> TheaterFor s -> k -> m -> IO (Maybe r)
callIO theater actor msg = do
maybeChan <- lookupActor theater actor
for maybeChan $ \ chan -> do
(returx, wait) <- newReturn
writeChan chan (msg, returx)
result <- wait
case result of
Left e -> AE.checkpointCallStack $ throwIO e
Right r -> return r
-- | Send a message to an actor, and wait for the result to arrive. Return
-- 'Nothing' if actor doesn't exist, otherwise 'Just' the result.
--
-- If the called method throws an exception, it is rethrown, wrapped with an
-- annotation, in the current thread.
call
:: ( MonadActor n, ActorEnv n ~ s
, StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
, Eq k, Hashable k
)
=> k -> m -> n (Maybe r)
call key msg = liftActor $ do
theater <- askTheater
liftIO $ callIO theater key msg
-- | Like 'send', except it takes the theater as a parameter.
sendIO
:: (StageKey s ~ k, StageMessage s ~ m, Eq k, Hashable k)
=> TheaterFor s -> k -> m -> IO Bool
sendIO theater actor msg = do
maybeChan <- lookupActor theater actor
case maybeChan of
Nothing -> return False
Just chan -> do
writeChan chan (msg, const $ pure ())
return True
-- | Send a message to an actor, without waiting for a result. Return 'True' if
-- the given actor exists, 'False' otherwise.
send
:: ( MonadActor n, ActorEnv n ~ s
, StageKey s ~ k, StageMessage s ~ m
, Eq k, Hashable k
)
=> k -> m -> n Bool
send key msg = liftActor $ do
theater <- askTheater
liftIO $ sendIO theater key msg
-- | Like 'sendMany', except it takes the theater as a parameter.
sendManyIO
:: (StageKey s ~ k, StageMessage s ~ m, Eq k, Hashable k)
=> TheaterFor s -> HashSet k -> m -> IO ()
sendManyIO (TheaterFor tvar _ _) recips msg = do
allActors <- readTVarIO tvar
for_ (HM.intersection allActors (HS.toMap recips)) $
\ chan -> writeChan chan (msg, const $ pure ())
-- | Send a message to each actor in the set that exists in the system,
-- without waiting for results.
sendMany
:: ( MonadActor n, ActorEnv n ~ s
, StageKey s ~ k, StageMessage s ~ m
, Eq k, Hashable k
)
=> HashSet k -> m -> n ()
sendMany keys msg = liftActor $ do
theater <- askTheater
liftIO $ sendManyIO theater keys msg
-- | Same as 'spawn', except it takes the theater as a parameter.
spawnIO
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
, Eq k, Hashable k, Show k, Message m, Show r
)
=> TheaterFor s
-> k
-> (m -> ActFor s (r, ActFor s (), Next))
-> IO Bool
spawnIO theater@(TheaterFor tvar _ _) actor behavior = do
chan <- newChan
added <- atomically $ stateTVar tvar $ \ hm ->
let hm' = HM.alter (create chan) actor hm
in ( not (HM.member actor hm) && HM.member actor hm'
, hm'
)
when added $ launchActorThread chan theater actor behavior
return added
where
create chan Nothing = Just chan
create _ j@(Just _) = j
-- | Launch a new actor with the given ID and behavior. Return 'True' if the ID
-- was unused and the actor has been launched. Return 'False' if the ID is
-- already in use, thus a new actor hasn't been launched.
spawn
:: ( MonadActor n, ActorEnv n ~ s
, StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
, Eq k, Hashable k, Show k, Message m, Show r
)
=> k
-> (m -> ActFor s (r, ActFor s (), Next))
-> n Bool
spawn key behavior = liftActor $ do
theater <- askTheater
liftIO $ spawnIO theater key behavior
done :: Monad n => a -> n (a, ActFor s (), Next)
done msg = return (msg, return (), Proceed)
doneAnd :: Monad n => a -> ActFor s () -> n (a, ActFor s (), Next)
doneAnd msg act = return (msg, act, Proceed)
stop :: Monad n => a -> n (a, ActFor s (), Next)
stop msg = return (msg, return (), Stop)

View file

@ -0,0 +1,37 @@
{- This file is part of Vervis.
-
- Written in 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
-- | Give another thread a way to send a value back to us.
module Control.Concurrent.Return
( newReturn
)
where
import Control.Concurrent.MVar
import Control.Monad
-- | Produce a pair of IO actions:
--
-- 1. Setter to give another thread, where it would be called at most once to
-- send us a value
-- 2. Action that waits until the value arrives
newReturn :: IO (a -> IO (), IO a)
newReturn = do
mvar <- newEmptyMVar
return (putReturn mvar, readMVar mvar)
where
putReturn mvar val = do
success <- tryPutMVar mvar val
unless success $ error "newReturn: putReturn: MVar is full"

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -80,6 +80,7 @@ import Network.FedURI
import Web.ActivityPub hiding (Patch (..), Ticket, Follow, Repo (..), ActorLocal (..), ActorDetail (..), Actor (..)) import Web.ActivityPub hiding (Patch (..), Ticket, Follow, Repo (..), ActorLocal (..), ActorDetail (..), Actor (..))
import Web.Text import Web.Text
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
@ -781,7 +782,7 @@ createNoteC (Entity senderPersonID senderPerson) senderActor maybeCap localRecip
-- Check input -- Check input
verifyNothingE maybeCap "Capability not needed" verifyNothingE maybeCap "Capability not needed"
Comment maybeParent topic source content <- do Comment maybeParent topic source content <- do
(authorPersonID, comment) <- parseNewLocalComment note (authorPersonID, comment) <- parseNewLocalCommentOld note
unless (authorPersonID == senderPersonID) $ unless (authorPersonID == senderPersonID) $
throwE "Note attributed to someone else" throwE "Note attributed to someone else"
return comment return comment
@ -1079,7 +1080,7 @@ createPatchTrackerC (Entity pidUser personUser) senderActor maybeCap localRecips
parseRepo (ObjURI h lu :| us) = do parseRepo (ObjURI h lu :| us) = do
unless (null us) $ throwE "More than one repo is specified" unless (null us) $ throwE "More than one repo is specified"
hl <- hostIsLocal h hl <- hostIsLocalOld h
unless hl $ throwE "A remote repo is specified" unless hl $ throwE "A remote repo is specified"
route <- fromMaybeE (decodeRouteLocal lu) "Not a valid route" route <- fromMaybeE (decodeRouteLocal lu) "Not a valid route"
case route of case route of
@ -2712,7 +2713,7 @@ resolveC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips r
-- Check input -- Check input
maybeLocalWorkItem <- maybeLocalWorkItem <-
nameExceptT "Resolve object" $ either Just (const Nothing) <$> do nameExceptT "Resolve object" $ either Just (const Nothing) <$> do
routeOrRemote <- parseFedURI uObject routeOrRemote <- parseFedURIOld uObject
bitraverse bitraverse
(\ r -> do (\ r -> do
wiByHash <- wiByHash <-

271
src/Vervis/Actor.hs Normal file
View file

@ -0,0 +1,271 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
-- These are for the Barbie-based generated instances
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Vervis.Actor
( -- * Local actors
LocalActorBy (..)
, LocalActor
-- * Local recipient set
, TicketRoutes (..)
, ClothRoutes (..)
, PersonRoutes (..)
, GroupRoutes (..)
, RepoRoutes (..)
, DeckRoutes (..)
, LoomRoutes (..)
, DeckFamilyRoutes (..)
, LoomFamilyRoutes (..)
, RecipientRoutes (..)
-- * AP system base types
, RemoteAuthor (..)
, ActivityBody (..)
, VerseRemote (..)
-- * Behavior utility types
, Verse
, Event (..)
, Env (..)
, Act
, ActE
, ActDB
, ActDBE
, Theater
-- * Behavior utilities
, withDB
, withDBExcept
, behave
)
where
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Data.Barbie
import Data.ByteString (ByteString)
import Data.Hashable
import Data.Text (Text)
import Data.Time.Clock
import Database.Persist.Sql
import GHC.Generics
import UnliftIO.Exception
import Web.Hashids
import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import Control.Concurrent.Actor
import Network.FedURI
import Web.Actor
import Web.Actor.Persist
import qualified Web.ActivityPub as AP
import Vervis.FedURI
import Vervis.Model hiding (Actor, Message)
import Vervis.Settings
data LocalActorBy f
= LocalActorPerson (f Person)
| LocalActorGroup (f Group)
| LocalActorRepo (f Repo)
| LocalActorDeck (f Deck)
| LocalActorLoom (f Loom)
deriving (Generic, FunctorB, ConstraintsB)
deriving instance AllBF Eq f LocalActorBy => Eq (LocalActorBy f)
deriving instance AllBF Ord f LocalActorBy => Ord (LocalActorBy f)
deriving instance AllBF Hashable f LocalActorBy => Hashable (LocalActorBy f)
deriving instance AllBF Show f LocalActorBy => Show (LocalActorBy f)
type LocalActor = LocalActorBy KeyHashid
data TicketRoutes = TicketRoutes
{ routeTicketFollowers :: Bool
}
deriving Eq
data ClothRoutes = ClothRoutes
{ routeClothFollowers :: Bool
}
deriving Eq
data PersonRoutes = PersonRoutes
{ routePerson :: Bool
, routePersonFollowers :: Bool
}
deriving Eq
data GroupRoutes = GroupRoutes
{ routeGroup :: Bool
, routeGroupFollowers :: Bool
}
deriving Eq
data RepoRoutes = RepoRoutes
{ routeRepo :: Bool
, routeRepoFollowers :: Bool
}
deriving Eq
data DeckRoutes = DeckRoutes
{ routeDeck :: Bool
, routeDeckFollowers :: Bool
}
deriving Eq
data LoomRoutes = LoomRoutes
{ routeLoom :: Bool
, routeLoomFollowers :: Bool
}
deriving Eq
data DeckFamilyRoutes = DeckFamilyRoutes
{ familyDeck :: DeckRoutes
, familyTickets :: [(KeyHashid TicketDeck, TicketRoutes)]
}
deriving Eq
data LoomFamilyRoutes = LoomFamilyRoutes
{ familyLoom :: LoomRoutes
, familyCloths :: [(KeyHashid TicketLoom, ClothRoutes)]
}
deriving Eq
data RecipientRoutes = RecipientRoutes
{ recipPeople :: [(KeyHashid Person, PersonRoutes)]
, recipGroups :: [(KeyHashid Group , GroupRoutes)]
, recipRepos :: [(KeyHashid Repo , RepoRoutes)]
, recipDecks :: [(KeyHashid Deck , DeckFamilyRoutes)]
, recipLooms :: [(KeyHashid Loom , LoomFamilyRoutes)]
}
deriving Eq
data RemoteAuthor = RemoteAuthor
{ remoteAuthorURI :: FedURI
, remoteAuthorInstance :: InstanceId
, remoteAuthorId :: RemoteActorId
}
data ActivityBody = ActivityBody
{ actbBL :: BL.ByteString
, actbObject :: A.Object
, actbActivity :: AP.Activity URIMode
}
data VerseRemote = VerseRemote
{ verseAuthor :: RemoteAuthor
, verseBody :: ActivityBody
, verseForward :: Maybe (RecipientRoutes, ByteString)
, verseActivity :: LocalURI
}
data Event
= EventFwdRemoteGrantToSomeoneElse RemoteActivityId
| EventRemoteFwdLocalActivity (LocalActorBy Key) OutboxItemId
| EventUnknown
deriving Show
type Verse = Either Event VerseRemote
instance Message Verse where
summarize (Left event) = T.pack $ show event
summarize (Right (VerseRemote author body _fwd uri)) =
let ObjURI h _ = remoteAuthorURI author
typ = AP.activityType $ AP.activitySpecific $ actbActivity body
in T.concat [typ, " ", renderObjURI $ ObjURI h uri]
refer (Left event) = T.pack $ show event
refer (Right (VerseRemote author _body _fwd uri)) =
let ObjURI h _ = remoteAuthorURI author
in renderObjURI $ ObjURI h uri
-- | Data to which every actor has access. Since such data can be passed to the
-- behavior function when launching the actor, having a dedicated datatype is
-- just convenience. The main reason is to allow 'runDB' not to take a
-- connection pool parameter, instead grabbing it from the ReaderT. Another
-- reason is to avoid the clutter of passing the same arguments manually
-- everywhere.
--
-- Maybe in the future there won't be data shared by all actors, and then this
-- type can be removed.
data Env = Env
{ envSettings :: AppSettings
, envDbPool :: ConnectionPool
, envHashidsContext :: HashidsContext
}
instance Stage Env where
type StageKey Env = LocalActorBy Key
type StageMessage Env = Verse
type StageReturn Env = Either Text Text
instance StageWeb Env where
type StageURIMode Env = URIMode
stageInstanceHost = appInstanceHost . envSettings
instance StageHashids Env where
stageHashidsContext = envHashidsContext
type Act = ActFor Env
type ActE = ActForE Env
type ActDB = SqlPersistT Act
type ActDBE = ExceptT Text ActDB
type Theater = TheaterFor Env
-- | Run a database transaction. If an exception is thrown, the whole
-- transaction is aborted.
withDB :: ActDB a -> Act a
withDB action = do
env <- askEnv
runPool (appDatabaseConf $ envSettings env) action (envDbPool env)
newtype FedError = FedError Text deriving Show
instance Exception FedError
-- | Like 'withDB', but supports errors via 'ExceptT. If an exception is
-- thrown, either via the 'ExceptT' or via regular throwing, the whole
-- transaction is aborted.
withDBExcept :: ExceptT Text (SqlPersistT Act) a -> ExceptT Text Act a
withDBExcept action = do
result <- lift $ try $ withDB $ either abort return =<< runExceptT action
case result of
Left (FedError t) -> throwE t
Right r -> return r
where
abort = throwIO . FedError
behave
:: (UTCTime -> Key a -> Verse -> ExceptT Text Act (Text, Act (), Next))
-> (Key a -> Verse -> Act (Either Text Text, Act (), Next))
behave handler key msg = do
now <- liftIO getCurrentTime
result <- runExceptT $ handler now key msg
case result of
Left e -> done $ Left e
Right (t, after, next) -> return (Right t, after, next)

64
src/Vervis/Actor/Deck.hs Normal file
View file

@ -0,0 +1,64 @@
{- This file is part of Vervis.
-
- Written in 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Actor.Deck
( deckBehavior
)
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.Maybe
import Data.ByteString (ByteString)
import Data.Foldable
import Data.Text (Text)
import Data.Time.Clock
import Database.Persist
import Yesod.Persist.Core
import qualified Data.Text as T
import Control.Concurrent.Actor
import Network.FedURI
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Database.Persist.Local
import Vervis.Actor
import Vervis.Cloth
import Vervis.Data.Discussion
import Vervis.FedURI
import Vervis.Federation.Util
import Vervis.Foundation
import Vervis.Model
import Vervis.Persist.Discussion
import Vervis.Ticket
deckBehavior
:: UTCTime -> DeckId -> Verse -> ExceptT Text Act (Text, Act (), Next)
deckBehavior now deckID (Left event) =
case event of
EventRemoteFwdLocalActivity _ _ ->
throwE "Got a forwarded local activity, I don't need those"
_ -> throwE $ "Unsupported event for Deck: " <> T.pack (show event)
deckBehavior now deckID (Right (VerseRemote author body mfwd luActivity)) =
case AP.activitySpecific $ actbActivity body of
_ -> throwE "Unsupported activity type for Deck"

64
src/Vervis/Actor/Group.hs Normal file
View file

@ -0,0 +1,64 @@
{- This file is part of Vervis.
-
- Written in 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Actor.Group
( groupBehavior
)
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.Maybe
import Data.ByteString (ByteString)
import Data.Foldable
import Data.Text (Text)
import Data.Time.Clock
import Database.Persist
import Yesod.Persist.Core
import qualified Data.Text as T
import Control.Concurrent.Actor
import Network.FedURI
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Database.Persist.Local
import Vervis.Actor
import Vervis.Cloth
import Vervis.Data.Discussion
import Vervis.FedURI
import Vervis.Federation.Util
import Vervis.Foundation
import Vervis.Model
import Vervis.Persist.Discussion
import Vervis.Ticket
groupBehavior
:: UTCTime -> GroupId -> Verse -> ExceptT Text Act (Text, Act (), Next)
groupBehavior now groupID (Left event) =
case event of
EventRemoteFwdLocalActivity _ _ ->
throwE "Got a forwarded local activity, I don't need those"
_ -> throwE $ "Unsupported event for Group: " <> T.pack (show event)
groupBehavior now groupID (Right (VerseRemote author body mfwd luActivity)) =
case AP.activitySpecific $ actbActivity body of
_ -> throwE "Unsupported activity type for Group"

64
src/Vervis/Actor/Loom.hs Normal file
View file

@ -0,0 +1,64 @@
{- This file is part of Vervis.
-
- Written in 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Actor.Loom
( loomBehavior
)
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.Maybe
import Data.ByteString (ByteString)
import Data.Foldable
import Data.Text (Text)
import Data.Time.Clock
import Database.Persist
import Yesod.Persist.Core
import qualified Data.Text as T
import Control.Concurrent.Actor
import Network.FedURI
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Database.Persist.Local
import Vervis.Actor
import Vervis.Cloth
import Vervis.Data.Discussion
import Vervis.FedURI
import Vervis.Federation.Util
import Vervis.Foundation
import Vervis.Model
import Vervis.Persist.Discussion
import Vervis.Ticket
loomBehavior
:: UTCTime -> LoomId -> Verse -> ExceptT Text Act (Text, Act (), Next)
loomBehavior now loomID (Left event) =
case event of
EventRemoteFwdLocalActivity _ _ ->
throwE "Got a forwarded local activity, I don't need those"
_ -> throwE $ "Unsupported event for Loom: " <> T.pack (show event)
loomBehavior now loomID (Right (VerseRemote author body mfwd luActivity)) =
case AP.activitySpecific $ actbActivity body of
_ -> throwE "Unsupported activity type for Loom"

189
src/Vervis/Actor/Person.hs Normal file
View file

@ -0,0 +1,189 @@
{- This file is part of Vervis.
-
- Written in 2016, 2018, 2019, 2020, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Actor.Person
( personBehavior
)
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.Maybe
import Control.Monad.Trans.Reader
import Data.ByteString (ByteString)
import Data.Foldable
import Data.Text (Text)
import Data.Time.Clock
import Database.Persist
import Database.Persist.Sql
import Yesod.Persist.Core
import qualified Data.Text as T
import Control.Concurrent.Actor
import Network.FedURI
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Database.Persist.Local
import Vervis.Actor
import Vervis.Cloth
import Vervis.Data.Discussion
import Vervis.FedURI
import Vervis.Federation.Util
import Vervis.Foundation
import Vervis.Model
import Vervis.Persist.Actor
import Vervis.Persist.Discussion
import Vervis.Ticket
insertActivityToInbox
:: MonadIO m
=> UTCTime -> ActorId -> OutboxItemId -> ReaderT SqlBackend m Bool
insertActivityToInbox now recipActorID outboxItemID = do
inboxID <- actorInbox <$> getJust recipActorID
inboxItemID <- insert $ InboxItem True now
maybeItem <- insertUnique $ InboxItemLocal inboxID outboxItemID inboxItemID
case maybeItem of
Nothing -> do
delete inboxItemID
return False
Just _ -> return True
-- Meaning: Someone commented on an issue/PR
-- Behavior: Insert to inbox
personCreateNote
:: UTCTime
-> PersonId
-> RemoteAuthor
-> ActivityBody
-> Maybe (RecipientRoutes, ByteString)
-> LocalURI
-> AP.Note URIMode
-> ExceptT Text Act (Text, Act (), Next)
personCreateNote now recipPersonID author body mfwd luCreate note = do
-- Check input
(luNote, published, Comment maybeParent topic source content) <- do
(luId, luAuthor, published, comment) <- parseRemoteComment note
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
throwE "Create author != note author"
return (luId, published, comment)
mractid <- withDBExcept $ do
Entity recipActorID recipActor <- lift $ do
person <- getJust recipPersonID
let actorID = personActor person
Entity actorID <$> getJust actorID
case topic of
Right uContext -> do
checkContextParent uContext maybeParent
lift $ insertToInbox now author body (actorInbox recipActor) luCreate True
Left (CommentTopicTicket deckID taskID) -> do
(_, _, Entity _ ticket, _, _) <- do
mticket <- lift $ getTicket deckID taskID
fromMaybeE mticket "Context: No such deck-ticket"
let did = ticketDiscuss ticket
_ <- traverse (getMessageParent did) maybeParent
lift $ insertToInbox now author body (actorInbox recipActor) luCreate True
Left (CommentTopicCloth loomID clothID) -> do
(_, _, Entity _ ticket, _, _, _) <- do
mticket <- lift $ getCloth loomID clothID
fromMaybeE mticket "Context: No such loom-cloth"
let did = ticketDiscuss ticket
_ <- traverse (getMessageParent did) maybeParent
lift $ insertToInbox now author body (actorInbox recipActor) luCreate True
done $
case mractid of
Nothing -> "I already have this activity in my inbox, doing nothing"
Just _ -> "Inserted Create{Note} to my inbox"
where
checkContextParent (ObjURI hContext luContext) mparent = do
mdid <- lift $ runMaybeT $ do
iid <- MaybeT $ getKeyBy $ UniqueInstance hContext
roid <- MaybeT $ getKeyBy $ UniqueRemoteObject iid luContext
rd <- MaybeT $ getValBy $ UniqueRemoteDiscussionIdent roid
return $ remoteDiscussionDiscuss rd
for_ mparent $ \ parent ->
case parent of
Left msg -> do
did <- fromMaybeE mdid "Local parent inexistent, no RemoteDiscussion"
void $ getLocalParentMessageId did msg
Right (ObjURI hParent luParent) -> do
mrm <- lift $ runMaybeT $ do
iid <- MaybeT $ getKeyBy $ UniqueInstance hParent
roid <- MaybeT $ getKeyBy $ UniqueRemoteObject iid luParent
MaybeT $ getValBy $ UniqueRemoteMessageIdent roid
for_ mrm $ \ rm -> do
let mid = remoteMessageRest rm
m <- lift $ getJust mid
did <- fromMaybeE mdid "Remote parent known, but no context RemoteDiscussion"
unless (messageRoot m == did) $
throwE "Remote parent belongs to a different discussion"
personBehavior :: UTCTime -> PersonId -> Verse -> ActE (Text, Act (), Next)
personBehavior now personID (Left event) =
case event of
EventFwdRemoteGrantToSomeoneElse grantID -> do
lift $ withDB $ do
(_personRecip, actorRecip) <- do
p <- getJust personID
(p,) <$> getJust (personActor p)
let inboxID = actorInbox actorRecip
itemID <- insert $ InboxItem True now
insert_ $ InboxItemRemote inboxID grantID itemID
done "Inserted Grant to inbox"
EventRemoteFwdLocalActivity authorByKey outboxItemID -> withDBExcept $ do
recipPerson <- lift $ getJust personID
verifyLocalActivityExistsInDB authorByKey outboxItemID
if LocalActorPerson personID == authorByKey
then done "Received activity authored by self, ignoring"
else do
inserted <- lift $ insertActivityToInbox now (personActor recipPerson) outboxItemID
done $
if inserted
then "Activity inserted to my inbox"
else "Activity already exists in my inbox, ignoring"
_ -> throwE $ "Unsupported event for Person: " <> T.pack (show event)
personBehavior now personID (Right (VerseRemote author body mfwd luActivity)) =
case AP.activitySpecific $ actbActivity body of
AP.CreateActivity (AP.Create obj mtarget) ->
case obj of
AP.CreateNote _ note ->
personCreateNote now personID author body mfwd luActivity note
_ -> throwE "Unsupported create object type for people"
{-
AP.FollowActivity follow ->
personFollowA now personID author body mfwd luActivity follow
AP.GrantActivity grant ->
personGrantA now personID author body mfwd luActivity grant
AP.InviteActivity invite ->
personInviteA now personID author body mfwd luActivity invite
AP.UndoActivity undo ->
(,Nothing) <$> personUndoA now personID author body mfwd luActivity undo
-}
_ -> throwE "Unsupported activity type for Person"

64
src/Vervis/Actor/Repo.hs Normal file
View file

@ -0,0 +1,64 @@
{- This file is part of Vervis.
-
- Written in 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Actor.Repo
( repoBehavior
)
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.Maybe
import Data.ByteString (ByteString)
import Data.Foldable
import Data.Text (Text)
import Data.Time.Clock
import Database.Persist
import Yesod.Persist.Core
import qualified Data.Text as T
import Control.Concurrent.Actor
import Network.FedURI
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Database.Persist.Local
import Vervis.Actor
import Vervis.Cloth
import Vervis.Data.Discussion
import Vervis.FedURI
import Vervis.Federation.Util
import Vervis.Foundation
import Vervis.Model
import Vervis.Persist.Discussion
import Vervis.Ticket
repoBehavior
:: UTCTime -> RepoId -> Verse -> ExceptT Text Act (Text, Act (), Next)
repoBehavior now repoID (Left event) =
case event of
EventRemoteFwdLocalActivity _ _ ->
throwE "Got a forwarded local activity, I don't need those"
_ -> throwE $ "Unsupported event for Repo: " <> T.pack (show event)
repoBehavior now repoID (Right (VerseRemote author body mfwd luActivity)) =
case AP.activitySpecific $ actbActivity body of
_ -> throwE "Unsupported activity type for Repo"

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2018, 2019, 2020, 2022 - Written in 2016, 2018, 2019, 2020, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>. - by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
@ -32,10 +32,12 @@ module Vervis.Application
where where
import Control.Concurrent.Chan import Control.Concurrent.Chan
import Control.Concurrent.STM
import Control.Concurrent.STM.TVar import Control.Concurrent.STM.TVar
import Control.Exception hiding (Handler) import Control.Exception hiding (Handler)
import Control.Monad import Control.Monad
import Control.Monad.Logger (liftLoc, runLoggingT, logInfo, logError) import Control.Monad.Logger (liftLoc, runLoggingT, logInfo, logError)
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader import Control.Monad.Trans.Reader
import Data.Bifunctor import Data.Bifunctor
import Data.Default.Class import Data.Default.Class
@ -47,6 +49,7 @@ import Data.Maybe
import Data.Proxy import Data.Proxy
import Data.String import Data.String
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Clock
import Data.Traversable import Data.Traversable
import Database.Persist.Postgresql import Database.Persist.Postgresql
import Graphics.SVGFonts.Fonts (lin2) import Graphics.SVGFonts.Fonts (lin2)
@ -75,6 +78,7 @@ import Yesod.Persist.Core
import Yesod.Static import Yesod.Static
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T import qualified Data.Text as T
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
@ -82,6 +86,7 @@ import Database.Persist.Schema.PostgreSQL (schemaBackend)
import Dvara import Dvara
import Yesod.Mail.Send (runMailer) import Yesod.Mail.Send (runMailer)
import Control.Concurrent.Actor
import Control.Concurrent.ResultShare import Control.Concurrent.ResultShare
import Crypto.ActorKey import Crypto.ActorKey
import Data.KeyFile import Data.KeyFile
@ -94,8 +99,14 @@ import Control.Concurrent.Local
import Data.List.NonEmpty.Local import Data.List.NonEmpty.Local
import Web.Hashids.Local import Web.Hashids.Local
import Vervis.Actor
import Vervis.Actor.Deck
import Vervis.Actor.Group
import Vervis.Actor.Loom
import Vervis.Actor.Person
import Vervis.Actor.Repo
import Vervis.Darcs import Vervis.Darcs
import Vervis.Web.Delivery import Vervis.Data.Actor
import Vervis.Foundation import Vervis.Foundation
import Vervis.Git import Vervis.Git
import Vervis.Hook import Vervis.Hook
@ -127,6 +138,7 @@ import Vervis.Path
import Vervis.Persist.Actor import Vervis.Persist.Actor
import Vervis.Settings import Vervis.Settings
import Vervis.Ssh (runSsh) import Vervis.Ssh (runSsh)
import Vervis.Web.Delivery
-- This line actually creates our YesodDispatch instance. It is the second half -- This line actually creates our YesodDispatch instance. It is the second half
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the -- of the call to mkYesodData which occurs in Foundation.hs. Please see the
@ -176,6 +188,9 @@ makeFoundation appSettings = do
appActorFetchShare <- newResultShare actorFetchShareAction appActorFetchShare <- newResultShare actorFetchShareAction
-- Temporarily blank actor map, we'll replace it in a moment
--appTheatre <- startTheater (error "logFunc") (error "env") []
appActivities <- appActivities <-
case appInboxDebugReportLength appSettings of case appInboxDebugReportLength appSettings of
Nothing -> return Nothing Nothing -> return Nothing
@ -189,7 +204,8 @@ makeFoundation appSettings = do
let mkFoundation let mkFoundation
appConnPool appConnPool
appCapSignKey appCapSignKey
appHashidsContext = appHashidsContext
appTheater =
App {..} App {..}
-- The App {..} syntax is an example of record wild cards. For more -- The App {..} syntax is an example of record wild cards. For more
-- information, see: -- information, see:
@ -199,6 +215,7 @@ makeFoundation appSettings = do
(error "connPool forced in tempFoundation") (error "connPool forced in tempFoundation")
(error "capSignKey forced in tempFoundation") (error "capSignKey forced in tempFoundation")
(error "hashidsContext forced in tempFoundation") (error "hashidsContext forced in tempFoundation")
(error "theater forced in tempFoundation")
logFunc = loggingFunction tempFoundation logFunc = loggingFunction tempFoundation
-- Create the database connection pool -- Create the database connection pool
@ -213,7 +230,7 @@ makeFoundation appSettings = do
hashidsSalt <- loadKeyFile loadMode $ appHashidsSaltFile appSettings hashidsSalt <- loadKeyFile loadMode $ appHashidsSaltFile appSettings
let hashidsCtx = hashidsContext hashidsSalt let hashidsCtx = hashidsContext hashidsSalt
app = mkFoundation pool capSignKey hashidsCtx app = mkFoundation pool capSignKey hashidsCtx (error "theater")
-- Perform database migration using our application's logging settings. -- Perform database migration using our application's logging settings.
--runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc --runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
@ -227,6 +244,11 @@ makeFoundation appSettings = do
writePostReceiveHooks writePostReceiveHooks
writePostApplyHooks writePostApplyHooks
-- Launch actor threads and fill the actor map
actors <- flip runWorker app $ runSiteDB loadTheatre
let env = Env appSettings pool hashidsCtx
theater <- startTheater logFunc env actors
let hostString = T.unpack $ renderAuthority hLocal let hostString = T.unpack $ renderAuthority hLocal
writeHookConfig hostString Config writeHookConfig hostString Config
{ configSecret = hookSecretText appHookSecret { configSecret = hookSecretText appHookSecret
@ -235,7 +257,7 @@ makeFoundation appSettings = do
} }
-- Return the foundation -- Return the foundation
return app return app { appTheater = theater }
where where
verifyRepoDir = do verifyRepoDir = do
repos <- lift reposFromDir repos <- lift reposFromDir
@ -300,6 +322,23 @@ makeFoundation appSettings = do
, T.pack $ show from, " ==> ", T.pack $ show to , T.pack $ show from, " ==> ", T.pack $ show to
] ]
loadTheatre = concat <$> sequenceA
[ selectAll LocalActorPerson personBehavior
, selectAll LocalActorGroup groupBehavior
, selectAll LocalActorRepo repoBehavior
, selectAll LocalActorDeck deckBehavior
, selectAll LocalActorLoom loomBehavior
]
where
selectAll
:: PersistRecordBackend a SqlBackend
=> (Key a -> LocalActorBy Key)
-> (UTCTime -> Key a -> Verse -> ExceptT Text Act (Text, Act (), Next))
-> WorkerDB [(LocalActorBy Key, Verse -> Act (Either Text Text, Act (), Next))]
selectAll makeLocalActor behavior =
map (\ xid -> (makeLocalActor xid, behave behavior xid)) <$>
selectKeysList [] []
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and -- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
-- applying some additional middlewares. -- applying some additional middlewares.
makeApplication :: App -> IO Application makeApplication :: App -> IO Application

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -21,17 +21,27 @@ module Vervis.Data.Actor
, parseStampRoute , parseStampRoute
, localActorID , localActorID
, parseLocalURI , parseLocalURI
, parseFedURI , parseFedURIOld
, parseLocalActorE , parseLocalActorE
) )
where where
import Control.Concurrent.Chan
import Control.Concurrent.STM.TVar
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger.CallStack
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import Data.Text (Text) import Data.Text (Text)
import Database.Persist.Types import Database.Persist.Types
import UnliftIO.Exception (try, SomeException, displayException)
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import Network.FedURI import Network.FedURI
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
@ -76,7 +86,7 @@ parseActivityURI
FedURI FedURI
) )
parseActivityURI u@(ObjURI h lu) = do parseActivityURI u@(ObjURI h lu) = do
hl <- hostIsLocal h hl <- hostIsLocalOld h
if hl if hl
then Left <$> parseLocalActivityURI lu then Left <$> parseLocalActivityURI lu
else pure $ Right u else pure $ Right u
@ -95,6 +105,8 @@ stampRoute (LocalActorRepo r) = RepoStampR r
stampRoute (LocalActorDeck d) = DeckStampR d stampRoute (LocalActorDeck d) = DeckStampR d
stampRoute (LocalActorLoom l) = LoomStampR l stampRoute (LocalActorLoom l) = LoomStampR l
parseStampRoute
:: Route App -> Maybe (LocalActorBy KeyHashid, KeyHashid SigKey)
parseStampRoute (PersonStampR p i) = Just (LocalActorPerson p, i) parseStampRoute (PersonStampR p i) = Just (LocalActorPerson p, i)
parseStampRoute (GroupStampR g i) = Just (LocalActorGroup g, i) parseStampRoute (GroupStampR g i) = Just (LocalActorGroup g, i)
parseStampRoute (RepoStampR r i) = Just (LocalActorRepo r, i) parseStampRoute (RepoStampR r i) = Just (LocalActorRepo r, i)
@ -102,18 +114,23 @@ parseStampRoute (DeckStampR d i) = Just (LocalActorDeck d, i)
parseStampRoute (LoomStampR l i) = Just (LocalActorLoom l, i) parseStampRoute (LoomStampR l i) = Just (LocalActorLoom l, i)
parseStampRoute _ = Nothing parseStampRoute _ = Nothing
localActorID :: LocalActorBy Entity -> ActorId
localActorID (LocalActorPerson (Entity _ p)) = personActor p localActorID (LocalActorPerson (Entity _ p)) = personActor p
localActorID (LocalActorGroup (Entity _ g)) = groupActor g localActorID (LocalActorGroup (Entity _ g)) = groupActor g
localActorID (LocalActorRepo (Entity _ r)) = repoActor r localActorID (LocalActorRepo (Entity _ r)) = repoActor r
localActorID (LocalActorDeck (Entity _ d)) = deckActor d localActorID (LocalActorDeck (Entity _ d)) = deckActor d
localActorID (LocalActorLoom (Entity _ l)) = loomActor l localActorID (LocalActorLoom (Entity _ l)) = loomActor l
parseLocalURI :: LocalURI -> ExceptT Text Handler (Route App) parseFedURIOld
parseLocalURI lu = fromMaybeE (decodeRouteLocal lu) "Not a valid route" :: ( MonadSite m
, SiteEnv m ~ site
parseFedURI :: FedURI -> ExceptT Text Handler (Either (Route App) FedURI) , YesodActivityPub site
parseFedURI u@(ObjURI h lu) = do , SiteFedURIMode site ~ URIMode
hl <- hostIsLocal h )
=> FedURI
-> ExceptT Text m (Either (Route App) FedURI)
parseFedURIOld u@(ObjURI h lu) = do
hl <- hostIsLocalOld h
if hl if hl
then Left <$> parseLocalURI lu then Left <$> parseLocalURI lu
else pure $ Right u else pure $ Right u

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -42,6 +42,7 @@ import GHC.Generics
import Network.FedURI import Network.FedURI
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
@ -86,7 +87,7 @@ verifyRole (Right _) =
throwE "ForgeFed Admin is the only role allowed currently" throwE "ForgeFed Admin is the only role allowed currently"
parseTopic u = do parseTopic u = do
routeOrRemote <- parseFedURI u routeOrRemote <- parseFedURIOld u
bitraverse bitraverse
(\ route -> do (\ route -> do
resourceHash <- resourceHash <-
@ -113,7 +114,7 @@ parseInvite sender (AP.Invite instrument object target) = do
<*> nameExceptT "Invite object" (parseRecipient object) <*> nameExceptT "Invite object" (parseRecipient object)
where where
parseRecipient u = do parseRecipient u = do
routeOrRemote <- parseFedURI u routeOrRemote <- parseFedURIOld u
bitraverse bitraverse
(\ route -> do (\ route -> do
recipHash <- recipHash <-
@ -158,7 +159,7 @@ parseGrant (AP.Grant object context target) = do
verifyRole (Right _) = verifyRole (Right _) =
throwE "ForgeFed Admin is the only role allowed currently" throwE "ForgeFed Admin is the only role allowed currently"
parseContext u@(ObjURI h lu) = do parseContext u@(ObjURI h lu) = do
hl <- hostIsLocal h hl <- hostIsLocalOld h
if hl if hl
then Left <$> do then Left <$> do
route <- route <-
@ -179,7 +180,7 @@ parseGrant (AP.Grant object context target) = do
parseGrantResource (LoomR l) = Just $ GrantResourceLoom l parseGrantResource (LoomR l) = Just $ GrantResourceLoom l
parseGrantResource _ = Nothing parseGrantResource _ = Nothing
parseTarget u@(ObjURI h lu) = do parseTarget u@(ObjURI h lu) = do
hl <- hostIsLocal h hl <- hostIsLocalOld h
if hl if hl
then Left <$> do then Left <$> do
route <- route <-

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2016, 2019, 2020, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -18,7 +19,8 @@ module Vervis.Data.Discussion
, commentTopicAudience , commentTopicAudience
, commentTopicManagingActor , commentTopicManagingActor
, Comment (..) , Comment (..)
, parseNewLocalComment , parseNewLocalCommentOld
, parseRemoteCommentOld
, parseRemoteComment , parseRemoteComment
, messageRoute , messageRoute
) )
@ -29,24 +31,53 @@ import Data.Bitraversable
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Clock import Data.Time.Clock
import Control.Concurrent.Actor
import Network.FedURI import Network.FedURI
import Web.Actor.Persist
import Web.Text import Web.Text
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.MonadSite
import qualified Web.ActivityPub as AP import qualified Web.ActivityPub as AP
import qualified Yesod.Hashids as YH
import Control.Monad.Trans.Except.Local import Control.Monad.Trans.Except.Local
import Vervis.Actor
import Vervis.Data.Actor import Vervis.Data.Actor
import Vervis.FedURI import Vervis.FedURI
import Vervis.Foundation import Vervis.Foundation
import Vervis.Model import Vervis.Model
import Vervis.Recipient import Vervis.Recipient
parseCommentId parseCommentIdOld
:: Route App -> ExceptT Text Handler (LocalActorBy Key, LocalMessageId) :: ( MonadSite m
, SiteEnv m ~ site
, YH.YesodHashids site
, SiteFedURIMode site ~ URIMode
)
=> Route App
-> ExceptT Text m (LocalActorBy Key, LocalMessageId)
parseCommentIdOld (PersonMessageR p m) =
(,) <$> (LocalActorPerson <$> YH.decodeKeyHashidE p "Invalid actor keyhashid")
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
parseCommentIdOld (GroupMessageR g m) =
(,) <$> (LocalActorGroup <$> YH.decodeKeyHashidE g "Invalid actor keyhashid")
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
parseCommentIdOld (RepoMessageR r m) =
(,) <$> (LocalActorRepo <$> YH.decodeKeyHashidE r "Invalid actor keyhashid")
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
parseCommentIdOld (DeckMessageR d m) =
(,) <$> (LocalActorDeck <$> YH.decodeKeyHashidE d "Invalid actor keyhashid")
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
parseCommentIdOld (LoomMessageR l m) =
(,) <$> (LocalActorLoom <$> YH.decodeKeyHashidE l "Invalid actor keyhashid")
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
parseCommentIdOld _ = throwE "Not a message route"
parseCommentId :: Route App -> ActE (LocalActorBy Key, LocalMessageId)
parseCommentId (PersonMessageR p m) = parseCommentId (PersonMessageR p m) =
(,) <$> (LocalActorPerson <$> decodeKeyHashidE p "Invalid actor keyhashid") (,) <$> (LocalActorPerson <$> decodeKeyHashidE p "Invalid actor keyhashid")
<*> decodeKeyHashidE m "Invalid LocalMessage keyhashid" <*> decodeKeyHashidE m "Invalid LocalMessage keyhashid"
@ -77,7 +108,24 @@ commentTopicAudience (CommentTopicCloth loomID clothID) =
commentTopicManagingActor :: CommentTopic -> LocalActorBy Key commentTopicManagingActor :: CommentTopic -> LocalActorBy Key
commentTopicManagingActor = fst . commentTopicAudience commentTopicManagingActor = fst . commentTopicAudience
parseCommentTopic :: Route App -> ExceptT Text Handler CommentTopic parseCommentTopicOld
:: (MonadSite m, YH.YesodHashids (SiteEnv m))
=> Route App
-> ExceptT Text m CommentTopic
parseCommentTopicOld (TicketR dkhid ltkhid) =
CommentTopicTicket
<$> YH.decodeKeyHashidE dkhid "Invalid dkhid"
<*> YH.decodeKeyHashidE ltkhid "Invalid ltkhid"
parseCommentTopicOld (ClothR lkhid ltkhid) =
CommentTopicCloth
<$> YH.decodeKeyHashidE lkhid "Invalid lkhid"
<*> YH.decodeKeyHashidE ltkhid "Invalid ltkhid"
parseCommentTopicOld _ = throwE "Not a ticket/cloth route"
parseCommentTopic
:: (MonadActor m, StageHashids (ActorEnv m))
=> Route App
-> ExceptT Text m CommentTopic
parseCommentTopic (TicketR dkhid ltkhid) = parseCommentTopic (TicketR dkhid ltkhid) =
CommentTopicTicket CommentTopicTicket
<$> decodeKeyHashidE dkhid "Invalid dkhid" <$> decodeKeyHashidE dkhid "Invalid dkhid"
@ -95,7 +143,28 @@ data Comment = Comment
, commentContent :: HTML , commentContent :: HTML
} }
parseComment :: AP.Note URIMode -> ExceptT Text Handler (Maybe LocalURI, LocalURI, Maybe UTCTime, Comment) parseCommentOld
:: ( MonadSite m
, SiteEnv m ~ site
, YH.YesodHashids site
, YesodActivityPub site
, SiteFedURIMode site ~ URIMode
)
=> AP.Note URIMode
-> ExceptT Text m (Maybe LocalURI, LocalURI, Maybe UTCTime, Comment)
parseCommentOld (AP.Note mluNote luAttrib _aud muParent muContext mpublished source content) = do
uContext <- fromMaybeE muContext "Note without context"
topic <- bitraverse parseCommentTopicOld pure =<< parseFedURIOld uContext
maybeParent <- do
uParent <- fromMaybeE muParent "Note doesn't specify inReplyTo"
if uParent == uContext
then pure Nothing
else fmap Just . bitraverse parseCommentIdOld pure =<< parseFedURIOld uParent
return (mluNote, luAttrib, mpublished, Comment maybeParent topic source content)
parseComment
:: AP.Note URIMode
-> ActE (Maybe LocalURI, LocalURI, Maybe UTCTime, Comment)
parseComment (AP.Note mluNote luAttrib _aud muParent muContext mpublished source content) = do parseComment (AP.Note mluNote luAttrib _aud muParent muContext mpublished source content) = do
uContext <- fromMaybeE muContext "Note without context" uContext <- fromMaybeE muContext "Note without context"
topic <- bitraverse parseCommentTopic pure =<< parseFedURI uContext topic <- bitraverse parseCommentTopic pure =<< parseFedURI uContext
@ -106,10 +175,10 @@ parseComment (AP.Note mluNote luAttrib _aud muParent muContext mpublished source
else fmap Just . bitraverse parseCommentId pure =<< parseFedURI uParent else fmap Just . bitraverse parseCommentId pure =<< parseFedURI uParent
return (mluNote, luAttrib, mpublished, Comment maybeParent topic source content) return (mluNote, luAttrib, mpublished, Comment maybeParent topic source content)
parseNewLocalComment parseNewLocalCommentOld
:: AP.Note URIMode -> ExceptT Text Handler (PersonId, Comment) :: AP.Note URIMode -> ExceptT Text Handler (PersonId, Comment)
parseNewLocalComment note = do parseNewLocalCommentOld note = do
(mluId, luAuthor, maybePublished, comment) <- parseComment note (mluId, luAuthor, maybePublished, comment) <- parseCommentOld note
verifyNothingE mluId "Note specifies an id" verifyNothingE mluId "Note specifies an id"
authorPersonID <- do authorPersonID <- do
authorByKey <- authorByKey <-
@ -121,9 +190,24 @@ parseNewLocalComment note = do
verifyNothingE maybePublished "Note specifies published" verifyNothingE maybePublished "Note specifies published"
return (authorPersonID, comment) return (authorPersonID, comment)
parseRemoteCommentOld
:: ( MonadSite m
, SiteEnv m ~ site
, YH.YesodHashids site
, YesodActivityPub site
, SiteFedURIMode site ~ URIMode
)
=> AP.Note URIMode
-> ExceptT Text m (LocalURI, LocalURI, UTCTime, Comment)
parseRemoteCommentOld note = do
(mluId, luAuthor, maybePublished, comment) <- parseCommentOld note
luId <- fromMaybeE mluId "Note doesn't specify id"
published <- fromMaybeE maybePublished "Note doesn't specify published"
return (luId, luAuthor, published, comment)
parseRemoteComment parseRemoteComment
:: AP.Note URIMode :: AP.Note URIMode
-> ExceptT Text Handler (LocalURI, LocalURI, UTCTime, Comment) -> ExceptT Text Act (LocalURI, LocalURI, UTCTime, Comment)
parseRemoteComment note = do parseRemoteComment note = do
(mluId, luAuthor, maybePublished, comment) <- parseComment note (mluId, luAuthor, maybePublished, comment) <- parseComment note
luId <- fromMaybeE mluId "Note doesn't specify id" luId <- fromMaybeE mluId "Note doesn't specify id"

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -62,7 +62,7 @@ parseFollow
-> ExceptT Text Handler -> ExceptT Text Handler
(Either (FolloweeBy Key) (Host, LocalURI, LocalURI), Bool) (Either (FolloweeBy Key) (Host, LocalURI, LocalURI), Bool)
parseFollow (AP.Follow uObject mluContext hide) = do parseFollow (AP.Follow uObject mluContext hide) = do
routeOrRemote <- parseFedURI uObject routeOrRemote <- parseFedURIOld uObject
(,hide) <$> (,hide) <$>
bitraverse bitraverse
(parseLocal mluContext) (parseLocal mluContext)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -66,6 +66,7 @@ import Development.PatchMediaType
import Network.FedURI import Network.FedURI
import Web.Text import Web.Text
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
@ -113,7 +114,7 @@ data WorkItemOffer = WorkItemOffer
checkAuthor :: FedURI -> ExceptT Text Handler (Either PersonId FedURI) checkAuthor :: FedURI -> ExceptT Text Handler (Either PersonId FedURI)
checkAuthor u@(ObjURI h lu) = do checkAuthor u@(ObjURI h lu) = do
hl <- hostIsLocal h hl <- hostIsLocalOld h
if hl if hl
then do then do
route <- fromMaybeE (decodeRouteLocal lu) "Local author not a valid route" route <- fromMaybeE (decodeRouteLocal lu) "Local author not a valid route"
@ -143,7 +144,7 @@ checkBundle h (AP.BundleOffer mlocal patches) = do
checkTipURI :: FedURI -> ExceptT Text Handler (Either RepoId FedURI) checkTipURI :: FedURI -> ExceptT Text Handler (Either RepoId FedURI)
checkTipURI u@(ObjURI h lu) = do checkTipURI u@(ObjURI h lu) = do
hl <- hostIsLocal h hl <- hostIsLocalOld h
if hl if hl
then Left <$> do then Left <$> do
route <- fromMaybeE (decodeRouteLocal lu) "URI is local but isn't a valid route" route <- fromMaybeE (decodeRouteLocal lu) "URI is local but isn't a valid route"
@ -177,7 +178,7 @@ checkMR h (AP.MergeRequest muOrigin target mbundle) =
checkTracker :: FedURI -> ExceptT Text Handler Tracker checkTracker :: FedURI -> ExceptT Text Handler Tracker
checkTracker u@(ObjURI h lu) = do checkTracker u@(ObjURI h lu) = do
hl <- hostIsLocal h hl <- hostIsLocalOld h
if hl if hl
then do then do
route <- fromMaybeE (decodeRouteLocal lu) "Local tracker not a valid route" route <- fromMaybeE (decodeRouteLocal lu) "Local tracker not a valid route"
@ -230,7 +231,7 @@ checkOfferTicket host ticket uTarget = do
return $ WorkItemOffer author title desc source tam return $ WorkItemOffer author title desc source tam
parseBundleRoute name u@(ObjURI h lu) = do parseBundleRoute name u@(ObjURI h lu) = do
hl <- hostIsLocal h hl <- hostIsLocalOld h
if hl if hl
then Left <$> do then Left <$> do
route <- route <-

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -14,10 +14,10 @@
-} -}
module Vervis.Federation.Auth module Vervis.Federation.Auth
( RemoteAuthor (..) ( --RemoteAuthor (..)
, ActivityAuthentication (..) --, ActivityAuthentication (..)
, ActivityBody (..) --, ActivityBody (..)
, authenticateActivity authenticateActivity
, checkForwarding , checkForwarding
) )
where where
@ -79,6 +79,7 @@ import Network.FedURI
import Network.HTTP.Digest import Network.HTTP.Digest
import Web.ActivityPub hiding (Follow) import Web.ActivityPub hiding (Follow)
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.Auth.Unverified import Yesod.Auth.Unverified
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
@ -94,6 +95,7 @@ import Data.Tuple.Local
import Database.Persist.Local import Database.Persist.Local
import Yesod.Persist.Local import Yesod.Persist.Local
import Vervis.Actor
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.Data.Actor import Vervis.Data.Actor
import Vervis.FedURI import Vervis.FedURI
@ -104,22 +106,6 @@ import Vervis.Recipient
import Vervis.RemoteActorStore import Vervis.RemoteActorStore
import Vervis.Settings import Vervis.Settings
data RemoteAuthor = RemoteAuthor
{ remoteAuthorURI :: FedURI
, remoteAuthorInstance :: InstanceId
, remoteAuthorId :: RemoteActorId
}
data ActivityAuthentication
= ActivityAuthLocal (LocalActorBy Key)
| ActivityAuthRemote RemoteAuthor
data ActivityBody = ActivityBody
{ actbBL :: BL.ByteString
, actbObject :: Object
, actbActivity :: Activity URIMode
}
parseKeyId (KeyId k) = parseKeyId (KeyId k) =
case parseRefURI =<< (first displayException . decodeUtf8') k of case parseRefURI =<< (first displayException . decodeUtf8') k of
Left e -> throwE $ "keyId isn't a valid FedURI: " ++ e Left e -> throwE $ "keyId isn't a valid FedURI: " ++ e
@ -365,7 +351,7 @@ verifyForwardedSig hAuthor luAuthor (Verification malgo keyid input signature) =
RefURI hKey luKey <- parseKeyId keyid RefURI hKey luKey <- parseKeyId keyid
unless (hAuthor == hKey) $ unless (hAuthor == hKey) $
throwE "Author and forwarded sig key on different hosts" throwE "Author and forwarded sig key on different hosts"
local <- hostIsLocal hKey local <- hostIsLocalOld hKey
if local if local
then ActivityAuthLocal <$> verifySelfSig luAuthor luKey input signature then ActivityAuthLocal <$> verifySelfSig luAuthor luKey input signature
else ActivityAuthRemote <$> verifyActorSig' malgo input signature hKey luKey (Just luAuthor) else ActivityAuthRemote <$> verifyActorSig' malgo input signature hKey luKey (Just luAuthor)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -76,6 +76,7 @@ import Yesod.Persist.Local
import Vervis.Access import Vervis.Access
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.Actor
import Vervis.Data.Actor import Vervis.Data.Actor
import Vervis.Data.Collab import Vervis.Data.Collab
import Vervis.Web.Delivery import Vervis.Web.Delivery

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -67,6 +67,7 @@ import Database.Persist.Local
import Yesod.Persist.Local import Yesod.Persist.Local
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.Actor
import Vervis.Cloth import Vervis.Cloth
import Vervis.Data.Discussion import Vervis.Data.Discussion
import Vervis.FedURI import Vervis.FedURI
@ -179,7 +180,7 @@ personCreateNoteF now recipPersonHash author body mfwd luCreate note = do
-- Check input -- Check input
recipPersonID <- decodeKeyHashid404 recipPersonHash recipPersonID <- decodeKeyHashid404 recipPersonHash
(luNote, published, Comment maybeParent topic source content) <- do (luNote, published, Comment maybeParent topic source content) <- do
(luId, luAuthor, published, comment) <- parseRemoteComment note (luId, luAuthor, published, comment) <- parseRemoteCommentOld note
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $ unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
throwE "Create author != note author" throwE "Create author != note author"
return (luId, published, comment) return (luId, published, comment)
@ -253,7 +254,7 @@ deckCreateNoteF now recipDeckHash author body mfwd luCreate note = do
recipDeckID <- decodeKeyHashid404 recipDeckHash recipDeckID <- decodeKeyHashid404 recipDeckHash
(luNote, published, Comment maybeParent topic source content) <- do (luNote, published, Comment maybeParent topic source content) <- do
(luId, luAuthor, published, comment) <- parseRemoteComment note (luId, luAuthor, published, comment) <- parseRemoteCommentOld note
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $ unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
throwE "Create author != note author" throwE "Create author != note author"
return (luId, published, comment) return (luId, published, comment)
@ -322,7 +323,7 @@ loomCreateNoteF now recipLoomHash author body mfwd luCreate note = do
recipLoomID <- decodeKeyHashid404 recipLoomHash recipLoomID <- decodeKeyHashid404 recipLoomHash
(luNote, published, Comment maybeParent topic source content) <- do (luNote, published, Comment maybeParent topic source content) <- do
(luId, luAuthor, published, comment) <- parseRemoteComment note (luId, luAuthor, published, comment) <- parseRemoteCommentOld note
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $ unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
throwE "Create author != note author" throwE "Create author != note author"
return (luId, published, comment) return (luId, published, comment)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -82,6 +82,7 @@ import Yesod.Persist.Local
import Vervis.Access import Vervis.Access
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.Actor
import Vervis.Cloth import Vervis.Cloth
import Vervis.Data.Actor import Vervis.Data.Actor
import Vervis.FedURI import Vervis.FedURI
@ -323,7 +324,7 @@ followF parseFollowee grabActor unread getFollowee getSieve makeLocalActor makeA
recipID <- decodeKeyHashid404 recipHash recipID <- decodeKeyHashid404 recipHash
followee <- nameExceptT "Follow object" $ do followee <- nameExceptT "Follow object" $ do
route <- do route <- do
routeOrRemote <- parseFedURI uObject routeOrRemote <- parseFedURIOld uObject
case routeOrRemote of case routeOrRemote of
Left route -> pure route Left route -> pure route
Right _ -> throwE "Remote, so definitely not me/mine" Right _ -> throwE "Remote, so definitely not me/mine"

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2021, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2021, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -95,6 +96,7 @@ import Development.PatchMediaType
import Vervis.Access import Vervis.Access
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.Actor
import Vervis.Cloth import Vervis.Cloth
import Vervis.Data.Actor import Vervis.Data.Actor
import Vervis.Data.Ticket import Vervis.Data.Ticket
@ -1902,7 +1904,7 @@ trackerResolveF maybeWorkItem grabActor getWorkItem makeResource trackerFollower
recipID <- decodeKeyHashid404 recipHash recipID <- decodeKeyHashid404 recipHash
wiID <- nameExceptT "Resolve object" $ do wiID <- nameExceptT "Resolve object" $ do
route <- do route <- do
routeOrRemote <- parseFedURI uObject routeOrRemote <- parseFedURIOld uObject
case routeOrRemote of case routeOrRemote of
Left route -> pure route Left route -> pure route
Right _ -> throwE "Remote, so definitely not mine" Right _ -> throwE "Remote, so definitely not mine"

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -31,6 +31,7 @@ import Network.FedURI
import Database.Persist.Local import Database.Persist.Local
import Vervis.Actor
import Vervis.Federation.Auth import Vervis.Federation.Auth
import Vervis.Foundation import Vervis.Foundation
import Vervis.Model import Vervis.Model

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2016, 2018, 2019, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -21,6 +22,8 @@ import Control.Concurrent.STM.TVar
import Control.Monad import Control.Monad
import Control.Monad.Logger.CallStack (logWarn) import Control.Monad.Logger.CallStack (logWarn)
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import Data.ByteString (ByteString)
import Data.HashMap.Strict (HashMap)
import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty (NonEmpty (..))
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding import Data.Text.Encoding
@ -48,10 +51,11 @@ import Yesod.Core.Types
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Form.Fields import Yesod.Form.Fields
import Yesod.Form.Functions import Yesod.Form.Functions
import Yesod.Form.Types import Yesod.Form.Types hiding (Env)
import Yesod.Persist.Core import Yesod.Persist.Core
import Yesod.Static import Yesod.Static
import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy as BL (ByteString) import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Data.Time.Units as U import qualified Data.Time.Units as U
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
@ -69,21 +73,25 @@ import Yesod.Mail.Send
import qualified Network.HTTP.Signature as S (Algorithm (..)) import qualified Network.HTTP.Signature as S (Algorithm (..))
import Control.Concurrent.Actor hiding (Message)
import Crypto.ActorKey import Crypto.ActorKey
import Crypto.PublicVerifKey import Crypto.PublicVerifKey
import Network.FedURI import Network.FedURI
import Web.ActivityAccess import Web.ActivityAccess
import Web.Actor.Persist
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
import qualified Web.ActivityPub as AP import qualified Web.ActivityPub as AP
import qualified Yesod.Hashids as YH
import Text.Email.Local import Text.Email.Local
import Text.Jasmine.Local (discardm) import Text.Jasmine.Local (discardm)
import Yesod.Paginate.Local import Yesod.Paginate.Local
import Vervis.Actor
import Vervis.FedURI import Vervis.FedURI
import Vervis.Hook import Vervis.Hook
import Vervis.Model import Vervis.Model
@ -95,6 +103,10 @@ import Vervis.Settings
import Vervis.Style import Vervis.Style
import Vervis.Widget (breadcrumbsW, revisionW) import Vervis.Widget (breadcrumbsW, revisionW)
data ActivityAuthentication
= ActivityAuthLocal (LocalActorBy Key)
| ActivityAuthRemote RemoteAuthor
data ActivityReport = ActivityReport data ActivityReport = ActivityReport
{ _arTime :: UTCTime { _arTime :: UTCTime
, _arMessage :: Text , _arMessage :: Text
@ -120,6 +132,7 @@ data App = App
, appHashidsContext :: HashidsContext , appHashidsContext :: HashidsContext
, appHookSecret :: HookSecret , appHookSecret :: HookSecret
, appActorFetchShare :: ActorFetchShare App , appActorFetchShare :: ActorFetchShare App
, appTheater :: Theater
, appActivities :: Maybe (Int, TVar (Vector ActivityReport)) , appActivities :: Maybe (Int, TVar (Vector ActivityReport))
} }
@ -142,6 +155,9 @@ type TicketDeckKeyHashid = KeyHashid TicketDeck
type TicketLoomKeyHashid = KeyHashid TicketLoom type TicketLoomKeyHashid = KeyHashid TicketLoom
type SigKeyKeyHashid = KeyHashid SigKey type SigKeyKeyHashid = KeyHashid SigKey
instance StageYesod Env where
type StageSite Env = App
-- This is where we define all of the routes in our application. For a full -- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see: -- explanation of the syntax, please see:
-- http://www.yesodweb.com/book/routing-and-handlers -- http://www.yesodweb.com/book/routing-and-handlers
@ -259,7 +275,7 @@ instance Yesod App where
case vs :: [E.Value Int] of case vs :: [E.Value Int] of
[E.Value i] -> return i [E.Value i] -> return i
_ -> error $ "countUnread returned " ++ show vs _ -> error $ "countUnread returned " ++ show vs
hash <- encodeKeyHashid pid hash <- YH.encodeKeyHashid pid
return (p, hash, verified, unread) return (p, hash, verified, unread)
(title, bcs) <- breadcrumbs (title, bcs) <- breadcrumbs
@ -448,7 +464,7 @@ instance Yesod App where
person :: KeyHashid Person -> Handler AuthResult person :: KeyHashid Person -> Handler AuthResult
person hash = personAnd $ \ (Entity pid _) -> do person hash = personAnd $ \ (Entity pid _) -> do
hash' <- encodeKeyHashid pid hash' <- YH.encodeKeyHashid pid
return $ if hash == hash' return $ if hash == hash'
then Authorized then Authorized
else Unauthorized "No access to this operation" else Unauthorized "No access to this operation"
@ -770,7 +786,7 @@ unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- https://github.com/yesodweb/yesod/wiki/Serve-static-files-from-a-separate-domain -- https://github.com/yesodweb/yesod/wiki/Serve-static-files-from-a-separate-domain
-- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding -- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding
instance YesodHashids App where instance YH.YesodHashids App where
siteHashidsContext = appHashidsContext siteHashidsContext = appHashidsContext
instance YesodRemoteActorStore App where instance YesodRemoteActorStore App where

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2018, 2019, 2020, 2022 - Written in 2016, 2018, 2019, 2020, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>. - by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
@ -30,6 +30,9 @@ module Vervis.Handler.Client
, getPublishOfferMergeR , getPublishOfferMergeR
, postPublishOfferMergeR , postPublishOfferMergeR
--, getPublishCommentR
--, postPublishCommentR
, getPublishMergeR , getPublishMergeR
, postPublishMergeR , postPublishMergeR
) )
@ -1025,7 +1028,7 @@ postPublishOfferMergeR = do
(ep@(Entity pid _), a) <- getSender (ep@(Entity pid _), a) <- getSender
senderHash <- encodeKeyHashid pid senderHash <- encodeKeyHashid pid
trackerLocal <- hostIsLocal $ objUriAuthority omgTracker trackerLocal <- hostIsLocalOld $ objUriAuthority omgTracker
edest <- runExceptT $ do edest <- runExceptT $ do
(summary, audience, ticket) <- (summary, audience, ticket) <-
offerMerge offerMerge
@ -1056,6 +1059,65 @@ postPublishOfferMergeR = do
else setMessage "Offer published" else setMessage "Offer published"
redirect dest redirect dest
{-
data Comment = Comment
{ commentTopic :: FedURI
, commentParent :: Maybe FedURI
, commentText :: PandocMarkdown
}
commentForm :: Form Comment
commentForm = Comment
<$> areq fedUriField "Topic" Nothing
<*> aopt fedUriField "Replying to" Nothing
<*> (pandocMarkdownFromText <$>
areq textField "Message" Nothing
)
getPublishCommentR :: Handler Html
getPublishCommentR = do
((_, widget), enctype) <- runFormPost commentForm
defaultLayout
[whamlet|
<h1>Comment on a ticket or a merge request
<form method=POST action=@{PublishCommentR} enctype=#{enctype}>
^{widget}
<input type=submit>
|]
postPublishCommentR :: Handler ()
postPublishCommentR = do
federation <- getsYesod $ appFederation . appSettings
unless federation badMethod
Comment uTopic uParent source <-
runFormPostRedirect PublishCommentR commentForm
(ep@(Entity pid _), a) <- getSender
senderHash <- encodeKeyHashid pid
result <- runExceptT $ do
(maybeSummary, audience, apply) <- applyPatches senderHash uBundle
(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
setMessage $ toHtml err
redirect PublishMergeR
Right _ -> do
setMessage "Apply activity sent"
redirect HomeR
-}
mergeForm = renderDivs $ (,) mergeForm = renderDivs $ (,)
<$> areq fedUriField "Patch bundle to apply" Nothing <$> areq fedUriField "Patch bundle to apply" Nothing
<*> areq capField "Grant activity to use for authorization" Nothing <*> areq capField "Grant activity to use for authorization" Nothing

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2016, 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -177,19 +177,11 @@ getDeckInboxR :: KeyHashid Deck -> Handler TypedContent
getDeckInboxR = getInbox DeckInboxR deckActor getDeckInboxR = getInbox DeckInboxR deckActor
postDeckInboxR :: KeyHashid Deck -> Handler () postDeckInboxR :: KeyHashid Deck -> Handler ()
postDeckInboxR recipDeckHash = postDeckInboxR deckHash = do
postInbox $ handleRobotInbox (LocalActorDeck recipDeckHash) handle deckID <- decodeKeyHashid404 deckHash
where postInbox $ LocalActorDeck deckID
handle
:: UTCTime {-
-> RemoteAuthor
-> ActivityBody
-> Maybe (RecipientRoutes, ByteString)
-> LocalURI
-> SpecificActivity URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
handle now author body mfwd luActivity specific =
case specific of
AP.AcceptActivity accept -> AP.AcceptActivity accept ->
deckAcceptF now recipDeckHash author body mfwd luActivity accept deckAcceptF now recipDeckHash author body mfwd luActivity accept
AP.CreateActivity (AP.Create obj mtarget) -> AP.CreateActivity (AP.Create obj mtarget) ->
@ -217,6 +209,7 @@ postDeckInboxR recipDeckHash =
AP.UndoActivity undo -> AP.UndoActivity undo ->
(,Nothing) <$> deckUndoF now recipDeckHash author body mfwd luActivity undo (,Nothing) <$> deckUndoF now recipDeckHash author body mfwd luActivity undo
_ -> return ("Unsupported activity type for decks", Nothing) _ -> return ("Unsupported activity type for decks", Nothing)
-}
getDeckOutboxR :: KeyHashid Deck -> Handler TypedContent getDeckOutboxR :: KeyHashid Deck -> Handler TypedContent
getDeckOutboxR = getOutbox DeckOutboxR DeckOutboxItemR deckActor getDeckOutboxR = getOutbox DeckOutboxR DeckOutboxItemR deckActor

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2016, 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -115,20 +115,9 @@ getGroupInboxR :: KeyHashid Group -> Handler TypedContent
getGroupInboxR = getInbox GroupInboxR groupActor getGroupInboxR = getInbox GroupInboxR groupActor
postGroupInboxR :: KeyHashid Group -> Handler () postGroupInboxR :: KeyHashid Group -> Handler ()
postGroupInboxR recipGroupHash = postGroupInboxR groupHash = do
postInbox $ handleRobotInbox (LocalActorGroup recipGroupHash) handle groupID <- decodeKeyHashid404 groupHash
where postInbox $ LocalActorGroup groupID
handle
:: UTCTime
-> RemoteAuthor
-> ActivityBody
-> Maybe (RecipientRoutes, ByteString)
-> LocalURI
-> AP.SpecificActivity URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
handle _now _author _body _mfwd _luActivity specific =
case specific of
_ -> return ("Unsupported activity type for groups", Nothing)
getGroupOutboxR :: KeyHashid Group -> Handler TypedContent getGroupOutboxR :: KeyHashid Group -> Handler TypedContent
getGroupOutboxR = getOutbox GroupOutboxR GroupOutboxItemR groupActor getGroupOutboxR = getOutbox GroupOutboxR GroupOutboxItemR groupActor

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -138,19 +138,11 @@ getLoomInboxR :: KeyHashid Loom -> Handler TypedContent
getLoomInboxR = getInbox LoomInboxR loomActor getLoomInboxR = getInbox LoomInboxR loomActor
postLoomInboxR :: KeyHashid Loom -> Handler () postLoomInboxR :: KeyHashid Loom -> Handler ()
postLoomInboxR recipLoomHash = postLoomInboxR loomHash = do
postInbox $ handleRobotInbox (LocalActorLoom recipLoomHash) handle loomID <- decodeKeyHashid404 loomHash
where postInbox $ LocalActorLoom loomID
handle
:: UTCTime {-
-> RemoteAuthor
-> ActivityBody
-> Maybe (RecipientRoutes, ByteString)
-> LocalURI
-> AP.SpecificActivity URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
handle now author body mfwd luActivity specific =
case specific of
AP.AcceptActivity accept -> AP.AcceptActivity accept ->
loomAcceptF now recipLoomHash author body mfwd luActivity accept loomAcceptF now recipLoomHash author body mfwd luActivity accept
AP.ApplyActivity apply-> AP.ApplyActivity apply->
@ -176,6 +168,7 @@ postLoomInboxR recipLoomHash =
AP.UndoActivity undo -> AP.UndoActivity undo ->
(,Nothing) <$> loomUndoF now recipLoomHash author body mfwd luActivity undo (,Nothing) <$> loomUndoF now recipLoomHash author body mfwd luActivity undo
_ -> return ("Unsupported activity type for looms", Nothing) _ -> return ("Unsupported activity type for looms", Nothing)
-}
getLoomOutboxR :: KeyHashid Loom -> Handler TypedContent getLoomOutboxR :: KeyHashid Loom -> Handler TypedContent
getLoomOutboxR = getOutbox LoomOutboxR LoomOutboxItemR loomActor getLoomOutboxR = getOutbox LoomOutboxR LoomOutboxItemR loomActor

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2016, 2018, 2019, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -57,6 +58,7 @@ import Text.Email.Local
import Network.FedURI import Network.FedURI
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
@ -138,103 +140,10 @@ getPersonR personHash = do
getPersonInboxR :: KeyHashid Person -> Handler TypedContent getPersonInboxR :: KeyHashid Person -> Handler TypedContent
getPersonInboxR = getInbox PersonInboxR personActor getPersonInboxR = getInbox PersonInboxR personActor
parseAuthenticatedLocalActivityURI
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalActorBy Key -> Maybe LocalURI -> ExceptT Text m OutboxItemId
parseAuthenticatedLocalActivityURI author maybeActivityURI = do
luAct <- fromMaybeE maybeActivityURI "No 'id'"
(actorByKey, _, outboxItemID) <- parseLocalActivityURI luAct
unless (actorByKey == author) $
throwE "'actor' actor and 'id' actor mismatch"
return outboxItemID
insertActivityToInbox
:: MonadIO m
=> UTCTime -> ActorId -> OutboxItemId -> ReaderT SqlBackend m Bool
insertActivityToInbox now recipActorID outboxItemID = do
inboxID <- actorInbox <$> getJust recipActorID
inboxItemID <- insert $ InboxItem True now
maybeItem <- insertUnique $ InboxItemLocal inboxID outboxItemID inboxItemID
case maybeItem of
Nothing -> do
delete inboxItemID
return False
Just _ -> return True
postPersonInboxR :: KeyHashid Person -> Handler () postPersonInboxR :: KeyHashid Person -> Handler ()
postPersonInboxR recipPersonHash = postInbox handle postPersonInboxR personHash = do
where personID <- decodeKeyHashid404 personHash
handle postInbox $ LocalActorPerson personID
:: UTCTime
-> ActivityAuthentication
-> ActivityBody
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
handle now (ActivityAuthLocal authorByKey) body = (,Nothing) <$> do
outboxItemID <-
parseAuthenticatedLocalActivityURI
authorByKey
(AP.activityId $ actbActivity body)
recipPersonID <- decodeKeyHashid404 recipPersonHash
runDBExcept $ do
recipPerson <- lift $ get404 recipPersonID
verifyLocalActivityExistsInDB authorByKey outboxItemID
if LocalActorPerson recipPersonID == authorByKey
then return "Received activity authored by self, ignoring"
else lift $ do
inserted <- insertActivityToInbox now (personActor recipPerson) outboxItemID
return $
if inserted
then "Activity inserted to recipient's inbox"
else "Activity already exists in recipient's inbox"
handle now (ActivityAuthRemote author) body = do
luActivity <-
fromMaybeE (AP.activityId $ actbActivity body) "Activity without 'id'"
localRecips <- do
mrecips <- parseAudience $ AP.activityAudience $ actbActivity body
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
msig <- checkForwarding $ LocalActorPerson recipPersonHash
let mfwd = (localRecips,) <$> msig
case AP.activitySpecific $ actbActivity body of
{-
AcceptActivity accept ->
(,Nothing) <$> sharerAcceptF shrRecip now author body mfwd luActivity accept
AddActivity (AP.Add obj target) ->
case obj of
Right (AddBundle patches) ->
sharerAddBundleF now shrRecip author body mfwd luActivity patches target
_ -> return ("Unsupported add object type for sharers", Nothing)
-}
AP.CreateActivity (AP.Create obj mtarget) ->
case obj of
AP.CreateNote _ note ->
(,Nothing) <$> personCreateNoteF now recipPersonHash author body mfwd luActivity note
_ -> return ("Unsupported create object type for people", Nothing)
AP.FollowActivity follow ->
personFollowF now recipPersonHash author body mfwd luActivity follow
AP.GrantActivity grant ->
personGrantF now recipPersonHash author body mfwd luActivity grant
AP.InviteActivity invite ->
personInviteF now recipPersonHash author body mfwd luActivity invite
{-
OfferActivity (Offer obj target) ->
case obj of
OfferTicket ticket ->
(,Nothing) <$> sharerOfferTicketF now shrRecip author body mfwd luActivity ticket target
OfferDep dep ->
sharerOfferDepF now shrRecip author body mfwd luActivity dep target
_ -> return ("Unsupported offer object type for sharers", Nothing)
PushActivity push ->
(,Nothing) <$> sharerPushF shrRecip now author body mfwd luActivity push
RejectActivity reject ->
(,Nothing) <$> sharerRejectF shrRecip now author body mfwd luActivity reject
ResolveActivity resolve ->
(,Nothing) <$> sharerResolveF now shrRecip author body mfwd luActivity resolve
-}
AP.UndoActivity undo ->
(,Nothing) <$> personUndoF now recipPersonHash author body mfwd luActivity undo
_ -> return ("Unsupported activity type for Person", Nothing)
getPersonOutboxR :: KeyHashid Person -> Handler TypedContent getPersonOutboxR :: KeyHashid Person -> Handler TypedContent
getPersonOutboxR = getOutbox PersonOutboxR PersonOutboxItemR personActor getPersonOutboxR = getOutbox PersonOutboxR PersonOutboxItemR personActor
@ -253,7 +162,7 @@ postPersonOutboxR personHash = do
verifyContentTypeAP verifyContentTypeAP
AP.Doc h activity <- requireInsecureJsonBody AP.Doc h activity <- requireInsecureJsonBody
hl <- hostIsLocal h hl <- hostIsLocalOld h
unless hl $ invalidArgs ["Activity host isn't the instance host"] unless hl $ invalidArgs ["Activity host isn't the instance host"]
result <- runExceptT $ do result <- runExceptT $ do

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2018, 2019, 2020, 2022 - Written in 2016, 2018, 2019, 2020, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>. - by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
@ -242,19 +242,11 @@ getRepoInboxR :: KeyHashid Repo -> Handler TypedContent
getRepoInboxR = getInbox RepoInboxR repoActor getRepoInboxR = getInbox RepoInboxR repoActor
postRepoInboxR :: KeyHashid Repo -> Handler () postRepoInboxR :: KeyHashid Repo -> Handler ()
postRepoInboxR recipRepoHash = postRepoInboxR repoHash = do
postInbox $ handleRobotInbox (LocalActorRepo recipRepoHash) handle repoID <- decodeKeyHashid404 repoHash
where postInbox $ LocalActorRepo repoID
handle
:: UTCTime {-
-> RemoteAuthor
-> ActivityBody
-> Maybe (RecipientRoutes, ByteString)
-> LocalURI
-> AP.SpecificActivity URIMode
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
handle now author body mfwd luActivity specific =
case specific of
AP.AcceptActivity accept -> AP.AcceptActivity accept ->
repoAcceptF now recipRepoHash author body mfwd luActivity accept repoAcceptF now recipRepoHash author body mfwd luActivity accept
{- {-
@ -289,6 +281,7 @@ postRepoInboxR recipRepoHash =
AP.UndoActivity undo-> AP.UndoActivity undo->
(,Nothing) <$> repoUndoF now recipRepoHash author body mfwd luActivity undo (,Nothing) <$> repoUndoF now recipRepoHash author body mfwd luActivity undo
_ -> return ("Unsupported activity type for repos", Nothing) _ -> return ("Unsupported activity type for repos", Nothing)
-}
getRepoOutboxR :: KeyHashid Repo -> Handler TypedContent getRepoOutboxR :: KeyHashid Repo -> Handler TypedContent
getRepoOutboxR = getOutbox RepoOutboxR RepoOutboxItemR repoActor getRepoOutboxR = getOutbox RepoOutboxR RepoOutboxItemR repoActor

View file

@ -85,6 +85,26 @@ instance Hashable RoleId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey hash = hash . fromSqlKey
instance Hashable PersonId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey
instance Hashable GroupId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey
instance Hashable RepoId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey
instance Hashable DeckId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey
instance Hashable LoomId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey
{- {-
instance PersistEntityGraph Ticket TicketDependency where instance PersistEntityGraph Ticket TicketDependency where
sourceParam = ticketDependencyParent sourceParam = ticketDependencyParent
@ -106,3 +126,20 @@ instance PersistEntityGraphNumbered Ticket TicketDependency where
numberField _ = TicketNumber numberField _ = TicketNumber
uniqueNode _ = UniqueTicket uniqueNode _ = UniqueTicket
-} -}
{-
instance VervisActor Person where
type VervisActorForwarder Person = ForwarderPerson
instance VervisActor Group where
type VervisActorForwarder Group = ForwarderGroup
instance VervisActor Repo where
type VervisActorForwarder Repo = ForwarderRepo
instance VervisActor Deck where
type VervisActorForwarder Deck = ForwarderDeck
instance VervisActor Loom where
type VervisActorForwarder Loom = ForwarderLoom
-}

View file

@ -28,6 +28,7 @@ import Control.Monad
import Control.Monad.Trans.Class import Control.Monad.Trans.Class
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import Data.Graph.Inductive.Graph (mkGraph, lab') import Data.Graph.Inductive.Graph (mkGraph, lab')
import Data.Graph.Inductive.PatriciaTree (Gr) import Data.Graph.Inductive.PatriciaTree (Gr)
import Data.Graph.Inductive.Query.DFS (dffWith) import Data.Graph.Inductive.Query.DFS (dffWith)
@ -158,9 +159,10 @@ getDiscussionTree getdid =
sortByTime . discussionTree <$> getAllMessages getdid sortByTime . discussionTree <$> getAllMessages getdid
getMessageFromRoute getMessageFromRoute
:: LocalActorBy Key :: MonadIO m
=> LocalActorBy Key
-> LocalMessageId -> LocalMessageId
-> ExceptT Text AppDB -> ExceptT Text (ReaderT SqlBackend m)
( LocalActorBy Entity ( LocalActorBy Entity
, Entity Actor , Entity Actor
, Entity LocalMessage , Entity LocalMessage
@ -187,9 +189,10 @@ getMessageFromRoute authorByKey localMsgID = do
) )
getLocalParentMessageId getLocalParentMessageId
:: DiscussionId :: MonadIO m
=> DiscussionId
-> (LocalActorBy Key, LocalMessageId) -> (LocalActorBy Key, LocalMessageId)
-> ExceptT Text AppDB MessageId -> ExceptT Text (ReaderT SqlBackend m) MessageId
getLocalParentMessageId discussionID (authorByKey, localMsgID) = do getLocalParentMessageId discussionID (authorByKey, localMsgID) = do
(_, _, _, Entity msgID msg) <- getMessageFromRoute authorByKey localMsgID (_, _, _, Entity msgID msg) <- getMessageFromRoute authorByKey localMsgID
unless (messageRoot msg == discussionID) $ unless (messageRoot msg == discussionID) $
@ -200,9 +203,10 @@ getLocalParentMessageId discussionID (authorByKey, localMsgID) = do
-- know and have this parent note in the DB, and whether the child and parent -- know and have this parent note in the DB, and whether the child and parent
-- belong to the same discussion root. -- belong to the same discussion root.
getMessageParent getMessageParent
:: DiscussionId :: MonadIO m
=> DiscussionId
-> Either (LocalActorBy Key, LocalMessageId) FedURI -> Either (LocalActorBy Key, LocalMessageId) FedURI
-> ExceptT Text AppDB (Either MessageId FedURI) -> ExceptT Text (ReaderT SqlBackend m) (Either MessageId FedURI)
getMessageParent did (Left msg) = Left <$> getLocalParentMessageId did msg getMessageParent did (Left msg) = Left <$> getLocalParentMessageId did msg
getMessageParent did (Right p@(ObjURI hParent luParent)) = do getMessageParent did (Right p@(ObjURI hParent luParent)) = do
mrm <- lift $ runMaybeT $ do mrm <- lift $ runMaybeT $ do

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -121,6 +121,7 @@ import qualified Data.Text as T
import Network.FedURI import Network.FedURI
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Actor
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
@ -130,6 +131,7 @@ import qualified Web.ActivityPub as AP
import Data.List.Local import Data.List.Local
import Data.List.NonEmpty.Local import Data.List.NonEmpty.Local
import Vervis.Actor
import Vervis.FedURI import Vervis.FedURI
import Vervis.Foundation import Vervis.Foundation
import Vervis.Model import Vervis.Model
@ -142,17 +144,6 @@ import Vervis.Model
-- types, then you can do any further parsing and grouping. -- types, then you can do any further parsing and grouping.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
data LocalActorBy f
= LocalActorPerson (f Person)
| LocalActorGroup (f Group)
| LocalActorRepo (f Repo)
| LocalActorDeck (f Deck)
| LocalActorLoom (f Loom)
deriving (Generic, FunctorB, ConstraintsB)
deriving instance AllBF Eq f LocalActorBy => Eq (LocalActorBy f)
deriving instance AllBF Ord f LocalActorBy => Ord (LocalActorBy f)
{- {-
instance (Eq (f Person), Eq (f Group), Eq (f Repo), Eq (f Deck), Eq (f Loom)) => Eq (LocalActorBy f) where instance (Eq (f Person), Eq (f Group), Eq (f Repo), Eq (f Deck), Eq (f Loom)) => Eq (LocalActorBy f) where
(==) (LocalActorPerson p) (LocalActorPerson p') = p == p' (==) (LocalActorPerson p) (LocalActorPerson p') = p == p'
@ -175,8 +166,6 @@ instance (Ord (f Person), Ord (f Group), Ord (f Repo), Ord (f Deck), Ord (f Loom
(<=) (LocalActorGroup _) _ = True (<=) (LocalActorGroup _) _ = True
-} -}
type LocalActor = LocalActorBy KeyHashid
parseLocalActor :: Route App -> Maybe LocalActor parseLocalActor :: Route App -> Maybe LocalActor
parseLocalActor (PersonR pkhid) = Just $ LocalActorPerson pkhid parseLocalActor (PersonR pkhid) = Just $ LocalActorPerson pkhid
parseLocalActor (GroupR gkhid) = Just $ LocalActorGroup gkhid parseLocalActor (GroupR gkhid) = Just $ LocalActorGroup gkhid
@ -504,67 +493,6 @@ recipientFromStage (LocalStageClothFollowers lkhid ltkhid) =
-- logic rather than plain lists of routes. -- logic rather than plain lists of routes.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
data TicketRoutes = TicketRoutes
{ routeTicketFollowers :: Bool
}
deriving Eq
data ClothRoutes = ClothRoutes
{ routeClothFollowers :: Bool
}
deriving Eq
data PersonRoutes = PersonRoutes
{ routePerson :: Bool
, routePersonFollowers :: Bool
}
deriving Eq
data GroupRoutes = GroupRoutes
{ routeGroup :: Bool
, routeGroupFollowers :: Bool
}
deriving Eq
data RepoRoutes = RepoRoutes
{ routeRepo :: Bool
, routeRepoFollowers :: Bool
}
deriving Eq
data DeckRoutes = DeckRoutes
{ routeDeck :: Bool
, routeDeckFollowers :: Bool
}
deriving Eq
data LoomRoutes = LoomRoutes
{ routeLoom :: Bool
, routeLoomFollowers :: Bool
}
deriving Eq
data DeckFamilyRoutes = DeckFamilyRoutes
{ familyDeck :: DeckRoutes
, familyTickets :: [(KeyHashid TicketDeck, TicketRoutes)]
}
deriving Eq
data LoomFamilyRoutes = LoomFamilyRoutes
{ familyLoom :: LoomRoutes
, familyCloths :: [(KeyHashid TicketLoom, ClothRoutes)]
}
deriving Eq
data RecipientRoutes = RecipientRoutes
{ recipPeople :: [(KeyHashid Person, PersonRoutes)]
, recipGroups :: [(KeyHashid Group , GroupRoutes)]
, recipRepos :: [(KeyHashid Repo , RepoRoutes)]
, recipDecks :: [(KeyHashid Deck , DeckFamilyRoutes)]
, recipLooms :: [(KeyHashid Loom , LoomFamilyRoutes)]
}
deriving Eq
groupLocalRecipients :: [LocalRecipient] -> RecipientRoutes groupLocalRecipients :: [LocalRecipient] -> RecipientRoutes
groupLocalRecipients = organize . partitionByActor groupLocalRecipients = organize . partitionByActor
where where

View file

@ -511,6 +511,7 @@ actorFetchShareAction u (site, iid) = flip runWorkerT site $ do
for_ mroid $ \ roid -> for_ mroid $ \ roid ->
insertUnique_ $ RemoteCollection roid insertUnique_ $ RemoteCollection roid
return Nothing return Nothing
-- TODO see https://vervis.peers.community/decks/br6Go/tickets/r7dDo
fetchRemoteActor fetchRemoteActor
:: ( YesodPersist site :: ( YesodPersist site

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -71,6 +71,7 @@ import qualified Data.Text.Lazy as TL
import qualified Data.Vector as V import qualified Data.Vector as V
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import Control.Concurrent.Actor
import Crypto.ActorKey import Crypto.ActorKey
import Database.Persist.JSON import Database.Persist.JSON
import Network.FedURI import Network.FedURI
@ -94,6 +95,7 @@ import Yesod.Persist.Local
import qualified Data.Aeson.Encode.Pretty.ToEncoding as P import qualified Data.Aeson.Encode.Pretty.ToEncoding as P
import qualified Web.ActivityPub as AP import qualified Web.ActivityPub as AP
import Vervis.Actor
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.API import Vervis.API
import Vervis.Data.Actor import Vervis.Data.Actor
@ -226,47 +228,48 @@ getInbox here actor hash = do
where where
ibiidString = "InboxItem #" ++ show (fromSqlKey ibid) ibiidString = "InboxItem #" ++ show (fromSqlKey ibid)
postInbox postInbox :: LocalActorBy Key -> Handler ()
:: ( UTCTime postInbox recipByKey = do
-> ActivityAuthentication
-> ActivityBody
-> ExceptT Text Handler
( Text
, Maybe (ExceptT Text Worker Text)
)
)
-> Handler ()
postInbox handler = do
federation <- getsYesod $ appFederation . appSettings federation <- getsYesod $ appFederation . appSettings
unless federation badMethod unless federation badMethod
contentTypes <- lookupHeaders "Content-Type" contentTypes <- lookupHeaders "Content-Type"
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
result <- runExceptT $ do result <- runExceptT $ do
(auth, body) <- authenticateActivity now (auth, body) <- authenticateActivity now
(actbObject body,) <$> handler now auth body verse <-
case auth of
ActivityAuthLocal authorByKey -> Left <$> do
outboxItemID <-
parseAuthenticatedLocalActivityURI
authorByKey
(AP.activityId $ actbActivity body)
return $ EventRemoteFwdLocalActivity authorByKey outboxItemID
ActivityAuthRemote author -> Right <$> do
luActivity <-
fromMaybeE (AP.activityId $ actbActivity body) "Activity without 'id'"
localRecips <- do
mrecips <- parseAudience $ AP.activityAudience $ actbActivity body
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
recipByHash <- hashLocalActor recipByKey
msig <- checkForwarding recipByHash
let mfwd = (localRecips,) <$> msig
return $ VerseRemote author body mfwd luActivity
theater <- getsYesod appTheater
r <- liftIO $ callIO theater recipByKey verse
case r of
Nothing -> notFound
Just (Left e) -> throwE e
Just (Right t) -> return (actbObject body, t)
recordActivity now result contentTypes recordActivity now result contentTypes
case result of case result of
Left err -> do Left err -> do
logDebug err logDebug err
sendResponseStatus badRequest400 err sendResponseStatus badRequest400 err
Right (obj, (_, mworker)) -> Right _ -> return ()
for_ mworker $ \ worker -> forkWorker "postInbox worker" $ do
wait <- asyncWorker $ runExceptT worker
result' <- wait
let result'' =
case result' of
Left e -> Left $ T.pack $ displayException e
Right (Left e) -> Left e
Right (Right t) -> Right (obj, (t, Nothing))
now' <- liftIO getCurrentTime
recordActivity now' result'' contentTypes
case result'' of
Left err -> logDebug err
Right _ -> return ()
where where
recordActivity recordActivity
:: (MonadSite m, SiteEnv m ~ App) :: (MonadSite m, SiteEnv m ~ App)
=> UTCTime -> Either Text (Object, (Text, w)) -> [ContentType] -> m () => UTCTime -> Either Text (Object, Text) -> [ContentType] -> m ()
recordActivity now result contentTypes = do recordActivity now result contentTypes = do
macts <- asksSite appActivities macts <- asksSite appActivities
for_ macts $ \ (size, acts) -> for_ macts $ \ (size, acts) ->
@ -274,12 +277,21 @@ postInbox handler = do
let (msg, body) = let (msg, body) =
case result of case result of
Left t -> (t, "{?}") Left t -> (t, "{?}")
Right (o, (t, _)) -> (t, encodePretty o) Right (o, t) -> (t, encodePretty o)
item = ActivityReport now msg contentTypes body item = ActivityReport now msg contentTypes body
vec' = item `V.cons` vec vec' = item `V.cons` vec
in if V.length vec' > size in if V.length vec' > size
then V.init vec' then V.init vec'
else vec' else vec'
parseAuthenticatedLocalActivityURI
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalActorBy Key -> Maybe LocalURI -> ExceptT Text m OutboxItemId
parseAuthenticatedLocalActivityURI author maybeActivityURI = do
luAct <- fromMaybeE maybeActivityURI "No 'id'"
(actorByKey, _, outboxItemID) <- parseLocalActivityURI luAct
unless (actorByKey == author) $
throwE "'actor' actor and 'id' actor mismatch"
return outboxItemID
getOutbox here itemRoute grabActorID hash = do getOutbox here itemRoute grabActorID hash = do
key <- decodeKeyHashid404 hash key <- decodeKeyHashid404 hash

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2021, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2021, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -31,12 +32,15 @@ module Vervis.Web.Delivery
fixRunningDeliveries fixRunningDeliveries
, retryOutboxDelivery , retryOutboxDelivery
, deliverActivityDB_Live
, deliverActivityDB , deliverActivityDB
, forwardActivityDB_Live
, forwardActivityDB , forwardActivityDB
) )
where where
import Control.Applicative import Control.Applicative
import Control.Concurrent.Chan
import Control.Concurrent.STM.TVar import Control.Concurrent.STM.TVar
import Control.Exception hiding (Handler, try) import Control.Exception hiding (Handler, try)
import Control.Monad import Control.Monad
@ -63,11 +67,14 @@ import Database.Persist.Sql
import Yesod.Core hiding (logError, logWarn, logInfo, logDebug) import Yesod.Core hiding (logError, logWarn, logInfo, logDebug)
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import qualified Data.List.Ordered as LO import qualified Data.List.Ordered as LO
import qualified Data.Text as T import qualified Data.Text as T
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import Control.Concurrent.Actor
import Crypto.ActorKey import Crypto.ActorKey
import Database.Persist.JSON import Database.Persist.JSON
import Network.FedURI import Network.FedURI
@ -82,6 +89,7 @@ import Data.Maybe.Local
import Data.Tuple.Local import Data.Tuple.Local
import Database.Persist.Local import Database.Persist.Local
import Vervis.Actor
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.Data.Actor import Vervis.Data.Actor
import Vervis.FedURI import Vervis.FedURI
@ -557,13 +565,23 @@ deliverRemoteHttp hContexts obid envelope (fetched, unfetched, unknown) = do
-- * Insert activity to inboxes of actors -- * Insert activity to inboxes of actors
-- * If collections are listed, insert activity to the local members and return -- * If collections are listed, insert activity to the local members and return
-- the remote members -- the remote members
--
-- NOTE: This functions is in a transition process! Instead of adding items to
-- local inboxes, it will send the items to live actors. At the moment, the
-- transition status is:
--
-- * For person actors, send to live actors
-- * For all other types, insert to inboxes
insertActivityToLocalInboxes insertActivityToLocalInboxes
:: ( MonadSite m :: ( MonadSite m
, YesodHashids (SiteEnv m) , YesodHashids (SiteEnv m)
, SiteEnv m ~ App
, PersistRecordBackend record SqlBackend , PersistRecordBackend record SqlBackend
) )
=> (InboxId -> InboxItemId -> record) => Event
-- ^ Database record to insert as an new inbox item to each inbox -- ^ Event to send to local live actors
-> (InboxId -> InboxItemId -> record)
-- ^ Database record to insert as a new inbox item to each inbox
-> Bool -> Bool
-- ^ Whether to deliver to collection only if owner actor is addressed -- ^ Whether to deliver to collection only if owner actor is addressed
-> Maybe LocalActor -> Maybe LocalActor
@ -577,7 +595,7 @@ insertActivityToLocalInboxes
-- author. -- author.
-> RecipientRoutes -> RecipientRoutes
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)] -> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recips = do insertActivityToLocalInboxes event makeInboxItem requireOwner mauthor maidAuthor recips = do
-- Unhash actor and work item hashids -- Unhash actor and work item hashids
people <- unhashKeys $ recipPeople recips people <- unhashKeys $ recipPeople recips
@ -625,7 +643,7 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
loomIDsForSelf = loomIDsForSelf =
[ key | (key, (routes, _)) <- loomsAndCloths, routeLoom routes ] [ key | (key, (routes, _)) <- loomsAndCloths, routeLoom routes ]
-- Grab actor actors whose followers are going to be delivered to -- Grab local actors whose followers are going to be delivered to
let personIDsForFollowers = let personIDsForFollowers =
[ key | (key, routes) <- peopleForStages, routePersonFollowers routes ] [ key | (key, routes) <- peopleForStages, routePersonFollowers routes ]
groupIDsForFollowers = groupIDsForFollowers =
@ -658,9 +676,9 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
loomsAndClothsForStages loomsAndClothsForStages
-- Get addressed Actor IDs from DB -- Get addressed Actor IDs from DB
-- Except for Person actors, we'll send to them via actor system
actorIDsForSelf <- orderedUnion <$> sequenceA actorIDsForSelf <- orderedUnion <$> sequenceA
[ selectActorIDsOrdered personActor PersonActor personIDsForSelf [ selectActorIDsOrdered groupActor GroupActor groupIDsForSelf
, selectActorIDsOrdered groupActor GroupActor groupIDsForSelf
, selectActorIDsOrdered repoActor RepoActor repoIDsForSelf , selectActorIDsOrdered repoActor RepoActor repoIDsForSelf
, selectActorIDsOrdered deckActor DeckActor deckIDsForSelf , selectActorIDsOrdered deckActor DeckActor deckIDsForSelf
, selectActorIDsOrdered loomActor LoomActor loomIDsForSelf , selectActorIDsOrdered loomActor LoomActor loomIDsForSelf
@ -694,15 +712,27 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
) )
-- Get the local and remote followers of the follower sets from DB -- Get the local and remote followers of the follower sets from DB
localFollowers <- localFollowersDB <-
map (followActor . entityVal) <$> fmap (map E.unValue) $
selectList [FollowTarget <-. followerSetIDs] [Asc FollowActor] E.select $ E.from $ \ (f `E.LeftOuterJoin` p) -> do
E.on $ E.just (f E.^. FollowActor) E.==. p E.?. PersonActor
E.where_ $
f E.^. FollowTarget `E.in_` E.valList followerSetIDs E.&&.
E.isNothing (p E.?. PersonId)
E.orderBy [E.asc $ f E.^. FollowActor]
return $ f E.^. FollowActor
localFollowersLivePersonIDs <-
fmap (map E.unValue) $
E.select $ E.from $ \ (f `E.InnerJoin` p) -> do
E.on $ f E.^. FollowActor E.==. p E.^. PersonActor
E.where_ $ f E.^. FollowTarget `E.in_` E.valList followerSetIDs
return $ p E.^. PersonId
remoteFollowers <- getRemoteFollowers followerSetIDs remoteFollowers <- getRemoteFollowers followerSetIDs
-- Insert inbox items to all local recipients, i.e. the local actors -- Insert inbox items to all local recipients, i.e. the local actors
-- directly addressed or listed in a local stage addressed -- directly addressed or listed in a local stage addressed
let localRecipients = let localRecipients =
let allLocal = LO.union localFollowers actorIDsForSelf let allLocal = LO.union localFollowersDB actorIDsForSelf
in case maidAuthor of in case maidAuthor of
Nothing -> allLocal Nothing -> allLocal
Just actorID -> LO.minus' allLocal [actorID] Just actorID -> LO.minus' allLocal [actorID]
@ -713,6 +743,14 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
inboxItemIDs <- insertMany $ replicate (length inboxIDs) $ InboxItem True now inboxItemIDs <- insertMany $ replicate (length inboxIDs) $ InboxItem True now
insertMany_ $ zipWith makeInboxItem inboxIDs inboxItemIDs insertMany_ $ zipWith makeInboxItem inboxIDs inboxItemIDs
-- Insert activity to message queues of live actors
let liveRecips =
HS.fromList $ map LocalActorPerson $
localFollowersLivePersonIDs ++ personIDsForSelf
lift $ do
theater <- asksSite appTheater
liftIO $ sendManyIO theater liveRecips $ Left event
-- Return remote followers, to whom we need to deliver via HTTP -- Return remote followers, to whom we need to deliver via HTTP
return remoteFollowers return remoteFollowers
where where
@ -814,16 +852,19 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
-- * Insert activity to inboxes of actors -- * Insert activity to inboxes of actors
-- * If collections are listed, insert activity to the local members and return -- * If collections are listed, insert activity to the local members and return
-- the remote members -- the remote members
--
-- NOTE transition to live actors
deliverLocal' deliverLocal'
:: (MonadSite m, YesodHashids (SiteEnv m)) :: (MonadSite m, YesodHashids (SiteEnv m), SiteEnv m ~ App)
=> Bool -- ^ Whether to deliver to collection only if owner actor is addressed => Bool -- ^ Whether to deliver to collection only if owner actor is addressed
-> LocalActor -> LocalActor
-> ActorId -> ActorId
-> OutboxItemId -> OutboxItemId
-> Event
-> RecipientRoutes -> RecipientRoutes
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)] -> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
deliverLocal' requireOwner author aidAuthor obiid = deliverLocal' requireOwner author aidAuthor obiid event =
insertActivityToLocalInboxes makeItem requireOwner (Just author) (Just aidAuthor) insertActivityToLocalInboxes event makeItem requireOwner (Just author) (Just aidAuthor)
where where
makeItem ibid ibiid = InboxItemLocal ibid obiid ibiid makeItem ibid ibiid = InboxItemLocal ibid obiid ibiid
@ -834,30 +875,35 @@ deliverLocal' requireOwner author aidAuthor obiid =
-- * If the author's follower collection is listed, insert activity to the -- * If the author's follower collection is listed, insert activity to the
-- local members and return the remote members -- local members and return the remote members
-- * Ignore other collections -- * Ignore other collections
--
-- NOTE transition to live actors
deliverLocal deliverLocal
:: KeyHashid Person :: KeyHashid Person
-> ActorId -> ActorId
-> OutboxItemId -> OutboxItemId
-> Event
-> RecipientRoutes -> RecipientRoutes
-> AppDB -> AppDB
[ ( (InstanceId, Host) [ ( (InstanceId, Host)
, NonEmpty RemoteRecipient , NonEmpty RemoteRecipient
) )
] ]
deliverLocal authorHash aidAuthor obiid deliverLocal authorHash aidAuthor obiid event
= deliverLocal' True (LocalActorPerson authorHash) aidAuthor obiid = deliverLocal' True (LocalActorPerson authorHash) aidAuthor obiid event
. localRecipSieve sieve True . localRecipSieve sieve True
where where
sieve = RecipientRoutes [(authorHash, PersonRoutes False True)] [] [] [] [] sieve = RecipientRoutes [(authorHash, PersonRoutes False True)] [] [] [] []
-- NOTE transition to live actors
insertRemoteActivityToLocalInboxes insertRemoteActivityToLocalInboxes
:: (MonadSite m, YesodHashids (SiteEnv m)) :: (MonadSite m, YesodHashids (SiteEnv m), SiteEnv m ~ App)
=> Bool => Bool
-> RemoteActivityId -> RemoteActivityId
-> Event
-> RecipientRoutes -> RecipientRoutes
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)] -> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
insertRemoteActivityToLocalInboxes requireOwner ractid = insertRemoteActivityToLocalInboxes requireOwner ractid event =
insertActivityToLocalInboxes makeItem requireOwner Nothing Nothing insertActivityToLocalInboxes event makeItem requireOwner Nothing Nothing
where where
makeItem ibid ibiid = InboxItemRemote ibid ractid ibiid makeItem ibid ibiid = InboxItemRemote ibid ractid ibiid
@ -1262,7 +1308,8 @@ retryOutboxDelivery = do
logInfo "Periodic delivery done" logInfo "Periodic delivery done"
deliverActivityDB -- NOTE transition to live actors
deliverActivityDB_Live
:: (MonadSite m, SiteEnv m ~ App) :: (MonadSite m, SiteEnv m ~ App)
=> LocalActorBy KeyHashid => LocalActorBy KeyHashid
-> ActorId -> ActorId
@ -1270,10 +1317,11 @@ deliverActivityDB
-> [(Host, NonEmpty LocalURI)] -> [(Host, NonEmpty LocalURI)]
-> [Host] -> [Host]
-> OutboxItemId -> OutboxItemId
-> Event
-> AP.Action URIMode -> AP.Action URIMode
-> ExceptT Text (ReaderT SqlBackend m) (Worker ()) -> ExceptT Text (ReaderT SqlBackend m) (Worker ())
deliverActivityDB senderByHash senderActorID localRecips remoteRecips fwdHosts itemID action = do deliverActivityDB_Live senderByHash senderActorID localRecips remoteRecips fwdHosts itemID event action = do
moreRemoteRecips <- lift $ deliverLocal' True senderByHash senderActorID itemID localRecips moreRemoteRecips <- lift $ deliverLocal' True senderByHash senderActorID itemID event localRecips
checkFederation moreRemoteRecips checkFederation moreRemoteRecips
remoteRecipsHttp <- lift $ deliverRemoteDB fwdHosts itemID remoteRecips moreRemoteRecips remoteRecipsHttp <- lift $ deliverRemoteDB fwdHosts itemID remoteRecips moreRemoteRecips
envelope <- lift $ prepareSendP senderActorID senderByHash itemID action envelope <- lift $ prepareSendP senderActorID senderByHash itemID action
@ -1284,7 +1332,12 @@ deliverActivityDB senderByHash senderActorID localRecips remoteRecips fwdHosts i
unless (federation || null remoteRecips) $ unless (federation || null remoteRecips) $
throwE "Federation disabled, but remote recipients found" throwE "Federation disabled, but remote recipients found"
forwardActivityDB -- NOTE transition to live actors
deliverActivityDB senderByHash senderActorID localRecips remoteRecips fwdHosts itemID =
deliverActivityDB_Live senderByHash senderActorID localRecips remoteRecips fwdHosts itemID EventUnknown
-- NOTE transition to live actors
forwardActivityDB_Live
:: (MonadSite m, SiteEnv m ~ App) :: (MonadSite m, SiteEnv m ~ App)
=> BL.ByteString => BL.ByteString
-> RecipientRoutes -> RecipientRoutes
@ -1293,13 +1346,18 @@ forwardActivityDB
-> LocalActorBy KeyHashid -> LocalActorBy KeyHashid
-> RecipientRoutes -> RecipientRoutes
-> RemoteActivityId -> RemoteActivityId
-> Event
-> ReaderT SqlBackend m (Worker ()) -> ReaderT SqlBackend m (Worker ())
forwardActivityDB body localRecips sig fwderActorID fwderByHash sieve activityID = do forwardActivityDB_Live body localRecips sig fwderActorID fwderByHash sieve activityID event = do
let localRecipsFinal = localRecipSieve' sieve False False localRecips let localRecipsFinal = localRecipSieve' sieve False False localRecips
remoteRecips <- remoteRecips <-
insertRemoteActivityToLocalInboxes False activityID localRecipsFinal insertRemoteActivityToLocalInboxes False activityID event localRecipsFinal
remoteRecipsHttp <- remoteRecipsHttp <-
forwardRemoteDB body activityID fwderActorID sig remoteRecips forwardRemoteDB body activityID fwderActorID sig remoteRecips
errand <- prepareForwardP fwderActorID fwderByHash body sig errand <- prepareForwardP fwderActorID fwderByHash body sig
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
return $ forwardRemoteHttp now errand remoteRecipsHttp return $ forwardRemoteHttp now errand remoteRecipsHttp
-- NOTE transition to live actors
forwardActivityDB body localRecips sig fwderActorID fwderByHash sieve activityID =
forwardActivityDB_Live body localRecips sig fwderActorID fwderByHash sieve activityID EventUnknown

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2021, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2021, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -79,6 +80,7 @@ module Web.ActivityPub
, Undo (..) , Undo (..)
, Audience (..) , Audience (..)
, SpecificActivity (..) , SpecificActivity (..)
, activityType
, Action (..) , Action (..)
, makeActivity , makeActivity
, Activity (..) , Activity (..)
@ -174,6 +176,119 @@ import Web.Text
import Data.Aeson.Local import Data.Aeson.Local
{-
data Link = Link
{ linkHref :: URI
, linkRel ::
, linkMediaType ::
, linkName ::
, linkHreflang ::
, linkHeight ::
, linkWidth ::
, linkPreview ::
, linkRest :: Object
}
data X = X
{ xId :: LocalURI
, x
}
data Object' u = Object'
{ objectId :: ObjURI
, objectType ::
, objectSubject ::
, objectRelationship ::
, objectActor ::
, objectAttributedTo ::
, objectAttachment ::
, objectBcc ::
, objectBto ::
, objectCc ::
, objectContext ::
, objectCurrent ::
, objectFirst ::
, objectGenerator ::
, objectIcon ::
, objectImage ::
, objectInReplyTo ::
, objectItems ::
, objectInstrument ::
, objectOrderedItems ::
, objectLast ::
, objectLocation ::
, objectNext ::
, objectObject ::
, objectOneOf ::
, objectAnyOf ::
, objectClosed ::
, objectOrigin ::
, objectAccuracy ::
, objectPrev ::
, objectPreview ::
, objectProvider ::
, objectReplies ::
, objectResult ::
, objectAudience ::
, objectPartOf ::
, objectTag ::
, objectTags ::
, objectTarget ::
, objectTo ::
, objectUrl ::
, objectAltitude ::
, objectContent ::
, objectContentMap ::
, objectName ::
, objectNameMap ::
, objectDuration ::
, objectEndTime ::
, objectHeight ::
, objectHref ::
, objectHreflang ::
, objectLatitude ::
, objectLongitude ::
, objectMediaType ::
, objectPublished ::
, objectRadius ::
, objectRating ::
, objectRel ::
, objectStartIndex ::
, objectStartTime ::
, objectSummary ::
, objectSummaryMap ::
, objectTotalItems ::
, objectUnits ::
, objectUpdated ::
, objectWidth ::
, objectDescribes ::
, objectFormerType ::
, objectDeleted ::
, objectEndpoints ::
, objectFollowing ::
, objectFollowers ::
, objectInbox ::
, objectLiked ::
, objectShares ::
, objectLikes ::
, objectOauthAuthorizationEndpoint ::
, objectOauthTokenEndpoint ::
, objectOutbox ::
, objectPreferredUsername ::
, objectProvideClientKey ::
, objectProxyUrl ::
, objectSharedInbox ::
, objectSignClientKey ::
, objectSource ::
, objectStreams ::
, objectUploadMedia ::
, objectRest :: Object
}
-}
proxy :: a u -> Proxy a proxy :: a u -> Proxy a
proxy _ = Proxy proxy _ = Proxy
@ -1712,6 +1827,21 @@ data SpecificActivity u
| ResolveActivity (Resolve u) | ResolveActivity (Resolve u)
| UndoActivity (Undo u) | UndoActivity (Undo u)
activityType :: SpecificActivity u -> Text
activityType (AcceptActivity _) = "Accept"
activityType (AddActivity _) = "Add"
activityType (ApplyActivity _) = "Apply"
activityType (CreateActivity _) = "Create"
activityType (FollowActivity _) = "Follow"
activityType (GrantActivity _) = "Grant"
activityType (InviteActivity _) = "Invite"
activityType (JoinActivity _) = "Join"
activityType (OfferActivity _) = "Offer"
activityType (PushActivity _) = "Push"
activityType (RejectActivity _) = "Reject"
activityType (ResolveActivity _) = "Resolve"
activityType (UndoActivity _) = "Undo"
data Action u = Action data Action u = Action
{ actionCapability :: Maybe (ObjURI u) { actionCapability :: Maybe (ObjURI u)
, actionSummary :: Maybe HTML , actionSummary :: Maybe HTML
@ -1782,20 +1912,6 @@ instance ActivityPub Activity where
<> "fulfills" .=% fulfills <> "fulfills" .=% fulfills
<> encodeSpecific authority actor specific <> encodeSpecific authority actor specific
where where
activityType :: SpecificActivity u -> Text
activityType (AcceptActivity _) = "Accept"
activityType (AddActivity _) = "Add"
activityType (ApplyActivity _) = "Apply"
activityType (CreateActivity _) = "Create"
activityType (FollowActivity _) = "Follow"
activityType (GrantActivity _) = "Grant"
activityType (InviteActivity _) = "Invite"
activityType (JoinActivity _) = "Join"
activityType (OfferActivity _) = "Offer"
activityType (PushActivity _) = "Push"
activityType (RejectActivity _) = "Reject"
activityType (ResolveActivity _) = "Resolve"
activityType (UndoActivity _) = "Undo"
encodeSpecific h _ (AcceptActivity a) = encodeAccept h a encodeSpecific h _ (AcceptActivity a) = encodeAccept h a
encodeSpecific h _ (AddActivity a) = encodeAdd h a encodeSpecific h _ (AddActivity a) = encodeAdd h a
encodeSpecific _ _ (ApplyActivity a) = encodeApply a encodeSpecific _ _ (ApplyActivity a) = encodeApply a

51
src/Web/Actor.hs Normal file
View file

@ -0,0 +1,51 @@
{- This file is part of Vervis.
-
- Written in 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
-- | Reusable library for building decentralized actor-model-based web apps,
-- with 'Control.Concurrent.Actor' for the local actor system, and ActivityPub
-- as the network protocol.
--
-- At the time of writing (April 2023), this module is collecting the pieces
-- that aren't tied to a specific web framework. Yesod-specific parts are in
-- separate modules.
--
-- Ideally, the whole application structure would be specified using
-- framework-independent tools, and framework integration (right now just
-- Yesod, might also be Servant in the future) would be an automatic or
-- auto-generated nearly-seamless part. I hope to get there, gradually, in
-- steps of refactoring.
module Web.Actor
( StageWeb (..)
, ActForE
, hostIsLocal
)
where
import Control.Monad.Trans.Except
import Data.Text (Text)
import Control.Concurrent.Actor
import Network.FedURI
class (Stage s, UriMode (StageURIMode s)) => StageWeb s where
type StageURIMode s
stageInstanceHost :: s -> Authority (StageURIMode s)
type ActForE s = ExceptT Text (ActFor s)
hostIsLocal
:: (MonadActor m, ActorEnv m ~ s, StageWeb s)
=> Authority (StageURIMode s) -> m Bool
hostIsLocal h = asksEnv $ (== h) . stageInstanceHost

137
src/Web/Actor/Persist.hs Normal file
View file

@ -0,0 +1,137 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Web.Actor.Persist
( StageHashids (..)
, KeyHashid ()
, keyHashidText
, encodeKeyHashidPure
--, getEncodeKeyHashid
--, encodeKeyHashid
, decodeKeyHashidPure
--, decodeKeyHashid
--, decodeKeyHashidF
--, decodeKeyHashidM
, decodeKeyHashidE
)
where
import Prelude hiding (fail)
import Control.Monad.Fail
import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe
import Data.Text (Text)
import Data.Text.Encoding
import Database.Persist.Class
import Database.Persist.Sql
import Web.Hashids
import Web.PathPieces
import Control.Concurrent.Actor
import Web.Actor
--import Yesod.MonadActor
import Web.Hashids.Local
class StageWeb s => StageHashids s where
stageHashidsContext :: s -> HashidsContext
newtype KeyHashid record = KeyHashid
{ keyHashidText :: Text
}
deriving (Eq, Ord, Read, Show)
instance PersistEntity record => PathPiece (KeyHashid record) where
fromPathPiece t = KeyHashid <$> fromPathPiece t
toPathPiece (KeyHashid t) = toPathPiece t
encodeKeyHashidPure
:: ToBackendKey SqlBackend record
=> HashidsContext -> Key record -> KeyHashid record
encodeKeyHashidPure ctx = KeyHashid . decodeUtf8 . encodeInt64 ctx . fromSqlKey
getEncodeKeyHashid
:: ( MonadActor m
, StageHashids (ActorEnv m)
, ToBackendKey SqlBackend record
)
=> m (Key record -> KeyHashid record)
getEncodeKeyHashid = do
ctx <- asksEnv stageHashidsContext
return $ encodeKeyHashidPure ctx
encodeKeyHashid
:: ( MonadActor m
, StageHashids (ActorEnv m)
, ToBackendKey SqlBackend record
)
=> Key record
-> m (KeyHashid record)
encodeKeyHashid k = do
enc <- getEncodeKeyHashid
return $ enc k
decodeKeyHashidPure
:: ToBackendKey SqlBackend record
=> HashidsContext
-> KeyHashid record
-> Maybe (Key record)
decodeKeyHashidPure ctx (KeyHashid t) =
fmap toSqlKey $ decodeInt64 ctx $ encodeUtf8 t
decodeKeyHashid
:: ( MonadActor m
, StageHashids (ActorEnv m)
, ToBackendKey SqlBackend record
)
=> KeyHashid record
-> m (Maybe (Key record))
decodeKeyHashid khid = do
ctx <- asksEnv stageHashidsContext
return $ decodeKeyHashidPure ctx khid
decodeKeyHashidF
:: ( MonadFail m
, MonadActor m
, StageHashids (ActorEnv m)
, ToBackendKey SqlBackend record
)
=> KeyHashid record
-> String
-> m (Key record)
decodeKeyHashidF khid e = maybe (fail e) return =<< decodeKeyHashid khid
decodeKeyHashidM
:: ( MonadActor m
, StageHashids (ActorEnv m)
, ToBackendKey SqlBackend record
)
=> KeyHashid record
-> MaybeT m (Key record)
decodeKeyHashidM = MaybeT . decodeKeyHashid
decodeKeyHashidE
:: ( MonadActor m
, StageHashids (ActorEnv m)
, ToBackendKey SqlBackend record
)
=> KeyHashid record
-> e
-> ExceptT e m (Key record)
decodeKeyHashidE khid e =
ExceptT $ maybe (Left e) Right <$> decodeKeyHashid khid

View file

@ -34,7 +34,7 @@ module Yesod.ActivityPub
, provideHtmlAndAP'' , provideHtmlAndAP''
, provideHtmlFeedAndAP , provideHtmlFeedAndAP
, hostIsLocal , hostIsLocalOld
, verifyHostLocal , verifyHostLocal
) )
where where
@ -576,14 +576,14 @@ provideHtmlFeedAndAP object feed widget = do
widget widget
(Just feed) (Just feed)
hostIsLocal hostIsLocalOld
:: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site) :: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site)
=> Authority (SiteFedURIMode site) -> m Bool => Authority (SiteFedURIMode site) -> m Bool
hostIsLocal h = asksSite $ (== h) . siteInstanceHost hostIsLocalOld h = asksSite $ (== h) . siteInstanceHost
verifyHostLocal verifyHostLocal
:: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site) :: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site)
=> Authority (SiteFedURIMode site) -> Text -> ExceptT Text m () => Authority (SiteFedURIMode site) -> Text -> ExceptT Text m ()
verifyHostLocal h t = do verifyHostLocal h t = do
local <- hostIsLocal h local <- hostIsLocalOld h
unless local $ throwE t unless local $ throwE t

56
src/Yesod/Actor.hs Normal file
View file

@ -0,0 +1,56 @@
{- This file is part of Vervis.
-
- Written 2019, 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
-- | Tools for integrating 'Web.Actor' with the Yesod web framework.
module Yesod.Actor
( decodeRouteLocal
, parseLocalURI
, StageYesod (..)
, parseFedURI
)
where
import Control.Monad.Trans.Except
import Data.Text (Text)
import Data.Text.Encoding
import Network.HTTP.Types.URI
import Yesod.Core
import Network.FedURI
import Web.Actor
import Control.Monad.Trans.Except.Local
decodeRouteLocal :: ParseRoute site => LocalURI -> Maybe (Route site)
decodeRouteLocal =
parseRoute . (,[]) . decodePathSegments . encodeUtf8 . localUriPath
parseLocalURI
:: (Monad m, ParseRoute site)
=> LocalURI -> ExceptT Text m (Route site)
parseLocalURI lu = fromMaybeE (decodeRouteLocal lu) "Not a valid route"
class (StageWeb s, Yesod (StageSite s)) => StageYesod s where
type StageSite s
parseFedURI
:: (StageYesod s, ParseRoute (StageSite s))
=> ObjURI (StageURIMode s)
-> ActForE s (Either (Route (StageSite s)) (ObjURI (StageURIMode s)))
parseFedURI u@(ObjURI h lu) = do
hl <- lift $ hostIsLocal h
if hl
then Left <$> parseLocalURI lu
else pure $ Right u

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written 2019 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -18,19 +18,14 @@ module Yesod.FedURI
, getEncodeRouteLocal , getEncodeRouteLocal
, getEncodeRouteHome , getEncodeRouteHome
, getEncodeRouteFed , getEncodeRouteFed
, decodeRouteLocal
, getEncodeRoutePageLocal , getEncodeRoutePageLocal
, getEncodeRoutePageHome , getEncodeRoutePageHome
, getEncodeRoutePageFed , getEncodeRoutePageFed
) )
where where
import Data.Text.Encoding
import Network.HTTP.Types.URI
import Yesod.Core import Yesod.Core
import qualified Data.Text as T
import Network.FedURI import Network.FedURI
import Yesod.MonadSite import Yesod.MonadSite
@ -63,10 +58,6 @@ getEncodeRouteFed
=> m (Authority u -> Route site -> ObjURI u) => m (Authority u -> Route site -> ObjURI u)
getEncodeRouteFed = (\ f a -> ObjURI a . f) <$> getEncodeRouteLocal getEncodeRouteFed = (\ f a -> ObjURI a . f) <$> getEncodeRouteLocal
decodeRouteLocal :: ParseRoute site => LocalURI -> Maybe (Route site)
decodeRouteLocal =
parseRoute . (,[]) . decodePathSegments . encodeUtf8 . localUriPath
getEncodeRoutePageLocal getEncodeRoutePageLocal
:: (MonadSite m, SiteEnv m ~ site, SiteFedURI site, YesodPaginate site) :: (MonadSite m, SiteEnv m ~ site, SiteFedURI site, YesodPaginate site)
=> m (Route site -> Int -> LocalPageURI) => m (Route site -> Int -> LocalPageURI)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
- -
- Copying is an act of love. Please copy, reuse and share. - Copying is an act of love. Please copy, reuse and share.
- -
@ -15,7 +15,7 @@
module Yesod.Hashids module Yesod.Hashids
( YesodHashids (..) ( YesodHashids (..)
, KeyHashid () , KeyHashid
, keyHashidText , keyHashidText
, encodeKeyHashidPure , encodeKeyHashidPure
@ -44,6 +44,7 @@ import Web.Hashids
import Web.PathPieces import Web.PathPieces
import Yesod.Core import Yesod.Core
import Web.Actor.Persist (KeyHashid, keyHashidText, encodeKeyHashidPure, decodeKeyHashidPure)
import Yesod.MonadSite import Yesod.MonadSite
import Web.Hashids.Local import Web.Hashids.Local
@ -51,20 +52,6 @@ import Web.Hashids.Local
class Yesod site => YesodHashids site where class Yesod site => YesodHashids site where
siteHashidsContext :: site -> HashidsContext siteHashidsContext :: site -> HashidsContext
newtype KeyHashid record = KeyHashid
{ keyHashidText :: Text
}
deriving (Eq, Ord, Read, Show)
instance PersistEntity record => PathPiece (KeyHashid record) where
fromPathPiece t = KeyHashid <$> fromPathPiece t
toPathPiece (KeyHashid t) = toPathPiece t
encodeKeyHashidPure
:: ToBackendKey SqlBackend record
=> HashidsContext -> Key record -> KeyHashid record
encodeKeyHashidPure ctx = KeyHashid . decodeUtf8 . encodeInt64 ctx . fromSqlKey
getEncodeKeyHashid getEncodeKeyHashid
:: ( MonadSite m :: ( MonadSite m
, YesodHashids (SiteEnv m) , YesodHashids (SiteEnv m)
@ -86,14 +73,6 @@ encodeKeyHashid k = do
enc <- getEncodeKeyHashid enc <- getEncodeKeyHashid
return $ enc k return $ enc k
decodeKeyHashidPure
:: ToBackendKey SqlBackend record
=> HashidsContext
-> KeyHashid record
-> Maybe (Key record)
decodeKeyHashidPure ctx (KeyHashid t) =
fmap toSqlKey $ decodeInt64 ctx $ encodeUtf8 t
decodeKeyHashid decodeKeyHashid
:: ( MonadSite m :: ( MonadSite m
, YesodHashids (SiteEnv m) , YesodHashids (SiteEnv m)

View file

@ -15,6 +15,12 @@ extra-deps:
# yesod-auth-account # yesod-auth-account
- git: https://vervis.peers.community/repos/VE2Kr - git: https://vervis.peers.community/repos/VE2Kr
commit: 70024e76cafb95bfa50b456efcf0970d720207bd commit: 70024e76cafb95bfa50b456efcf0970d720207bd
# - git: https://notabug.org/fr33domlover/haskell-persistent
# commit: 9cc700b540a680ac1fdc9df94847a631013cb3ca
# subdirs:
# - persistent
# - persistent-postgresql
- ./lib/darcs-lights - ./lib/darcs-lights
- ./lib/darcs-rev - ./lib/darcs-rev
- ./lib/dvara - ./lib/dvara
@ -49,6 +55,7 @@ extra-deps:
- time-interval-0.1.1 - time-interval-0.1.1
- time-units-1.0.0 - time-units-1.0.0
- url-2.1.3 - url-2.1.3
- annotated-exception-0.2.0.4
# Override default flag values for local packages and extra-deps # Override default flag values for local packages and extra-deps
flags: flags:

View file

@ -35,6 +35,9 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<li> <li>
<a href=@{PublishOfferMergeR}> <a href=@{PublishOfferMergeR}>
Open a merge request Open a merge request
$# <li>
$# <a href=@{PublishCommentR}>
$# Comment on a ticket or merge request
<li> <li>
<a href=@{PublishMergeR}> <a href=@{PublishMergeR}>
Merge a merge request Merge a merge request

View file

@ -23,8 +23,13 @@ Instance
UniqueInstance host UniqueInstance host
RemoteObject RemoteObject
instance InstanceId instance InstanceId
ident LocalURI ident LocalURI
-- fetched UTCTime Maybe
-- type Text Maybe
-- followers LocalURI Maybe
-- team LocalURI Maybe
UniqueRemoteObject instance ident UniqueRemoteObject instance ident

View file

@ -129,6 +129,7 @@
/ssh-keys KeysR GET POST /ssh-keys KeysR GET POST
/ssh-keys/#SshKeyKeyHashid/delete KeyDeleteR POST /ssh-keys/#SshKeyKeyHashid/delete KeyDeleteR POST
--/publish/comment PublishCommentR GET POST
/publish/offer-merge PublishOfferMergeR GET POST /publish/offer-merge PublishOfferMergeR GET POST
/publish/merge PublishMergeR GET POST /publish/merge PublishMergeR GET POST

View file

@ -43,8 +43,10 @@ library
Vervis.Hook Vervis.Hook
other-modules: other-modules:
Control.Applicative.Local Control.Applicative.Local
Control.Concurrent.Actor
Control.Concurrent.Local Control.Concurrent.Local
Control.Concurrent.ResultShare Control.Concurrent.ResultShare
Control.Concurrent.Return
Control.Monad.Trans.Except.Local Control.Monad.Trans.Except.Local
Crypto.ActorKey Crypto.ActorKey
Crypto.PubKey.Encoding Crypto.PubKey.Encoding
@ -109,11 +111,14 @@ library
Text.Jasmine.Local Text.Jasmine.Local
Web.ActivityAccess Web.ActivityAccess
Web.ActivityPub Web.ActivityPub
Web.Actor
Web.Actor.Persist
-- Web.Capability -- Web.Capability
Web.Text Web.Text
Web.Hashids.Local Web.Hashids.Local
Web.PathPieces.Local Web.PathPieces.Local
Yesod.ActivityPub Yesod.ActivityPub
Yesod.Actor
Yesod.Auth.Unverified Yesod.Auth.Unverified
Yesod.Auth.Unverified.Creds Yesod.Auth.Unverified.Creds
Yesod.Auth.Unverified.Internal Yesod.Auth.Unverified.Internal
@ -128,6 +133,12 @@ library
Vervis.Access Vervis.Access
Vervis.ActivityPub Vervis.ActivityPub
Vervis.Actor
Vervis.Actor.Deck
Vervis.Actor.Group
Vervis.Actor.Loom
Vervis.Actor.Person
Vervis.Actor.Repo
Vervis.API Vervis.API
Vervis.Avatar Vervis.Avatar
Vervis.BinaryBody Vervis.BinaryBody
@ -270,6 +281,8 @@ library
build-depends: aeson build-depends: aeson
-- For activity JSOn display in /inbox test page -- For activity JSOn display in /inbox test page
, aeson-pretty , aeson-pretty
-- For rethrowing in Control.Concurrent.Actor
, annotated-exception
-- for encoding and decoding of crypto public keys -- for encoding and decoding of crypto public keys
, asn1-encoding , asn1-encoding
, asn1-types , asn1-types