Move remote actor DB code from Foundation to separate module

This commit is contained in:
fr33domlover 2019-03-09 15:40:02 +00:00
parent b0e33af4d8
commit 37216d9045
4 changed files with 449 additions and 302 deletions

View file

@ -65,6 +65,7 @@ import Web.Hashids.Local
import Vervis.ActorKey (generateActorKey, actorKeyRotator) import Vervis.ActorKey (generateActorKey, actorKeyRotator)
import Vervis.KeyFile (isInitialSetup) import Vervis.KeyFile (isInitialSetup)
import Vervis.RemoteActorStore (newInstanceMutex)
-- Import all relevant handler modules here. -- Import all relevant handler modules here.
-- Don't forget to add new modules to your cabal file! -- Don't forget to add new modules to your cabal file!
@ -122,7 +123,7 @@ makeFoundation appSettings = do
newTVarIO =<< newTVarIO =<<
(,,) <$> generateActorKey <*> generateActorKey <*> pure True (,,) <$> generateActorKey <*> generateActorKey <*> pure True
appInstanceMutex <- newTVarIO M.empty appInstanceMutex <- newInstanceMutex
appActivities <- newTVarIO mempty appActivities <- newTVarIO mempty

View file

@ -34,8 +34,7 @@ import Data.Time.Interval (TimeInterval, fromTimeUnit, toTimeUnit)
import Data.Time.Units (Second, Minute, Day) import Data.Time.Units (Second, Minute, Day)
import Database.Persist.Sql (ConnectionPool, runSqlPool) import Database.Persist.Sql (ConnectionPool, runSqlPool)
import Graphics.SVGFonts.ReadFont (PreparedFont) import Graphics.SVGFonts.ReadFont (PreparedFont)
import Network.HTTP.Client (Manager, HttpException, requestFromURI, responseBody) import Network.HTTP.Client
import Network.HTTP.Simple (httpJSONEither, setRequestManager, addRequestHeader)
import Network.HTTP.Types.Header (hHost) import Network.HTTP.Types.Header (hHost)
import Network.URI (URI, uriAuthority, uriFragment, uriRegName, parseURI) import Network.URI (URI, uriAuthority, uriFragment, uriRegName, parseURI)
import Text.Shakespeare.Text (textFile) import Text.Shakespeare.Text (textFile)
@ -76,6 +75,7 @@ import Vervis.Import.NoFoundation hiding (Handler, Day, last, init, logWarn)
import Vervis.Model.Group import Vervis.Model.Group
import Vervis.Model.Ident import Vervis.Model.Ident
import Vervis.Model.Role import Vervis.Model.Role
import Vervis.RemoteActorStore
import Vervis.Widget (breadcrumbsW, revisionW) import Vervis.Widget (breadcrumbsW, revisionW)
-- | The foundation datatype for your application. This can be a good place to -- | The foundation datatype for your application. This can be a good place to
@ -91,7 +91,7 @@ data App = App
, appMailQueue :: Maybe (Chan (MailRecipe App)) , appMailQueue :: Maybe (Chan (MailRecipe App))
, appSvgFont :: PreparedFont Double , appSvgFont :: PreparedFont Double
, appActorKeys :: TVar (ActorKey, ActorKey, Bool) , appActorKeys :: TVar (ActorKey, ActorKey, Bool)
, appInstanceMutex :: TVar (HashMap Text (MVar ())) , appInstanceMutex :: InstanceMutex
, appCapSignKey :: ActorKey , appCapSignKey :: ActorKey
, appHashidEncode :: Int64 -> Text , appHashidEncode :: Int64 -> Text
, appHashidDecode :: Text -> Maybe Int64 , appHashidDecode :: Text -> Maybe Int64
@ -575,8 +575,8 @@ instance RenderMessage App FormMessage where
-- An example is background jobs that send email. -- An example is background jobs that send email.
-- This can also be useful for writing code that works across multiple Yesod -- This can also be useful for writing code that works across multiple Yesod
-- applications. -- applications.
--instance HasHttpManager App where instance HasHttpManager App where
-- getHttpManager = appHttpManager getHttpManager = appHttpManager
unsafeHandler :: App -> Handler a -> IO a unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
@ -589,241 +589,11 @@ 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
-- TODO this is copied from stm-2.5, remove when we upgrade LTS instance YesodRemoteActorStore App where
stateTVar :: TVar s -> (s -> (a, s)) -> STM a siteInstanceMutex = appInstanceMutex
stateTVar var f = do siteInstanceRoomMode = appMaxInstanceKeys . appSettings
s <- readTVar var siteActorRoomMode = appMaxActorKeys . appSettings
let (a, s') = f s -- since we destructure this, we are strict in f siteRejectOnMaxKeys = appRejectOnMaxKeys . appSettings
writeTVar var s'
return a
withHostLock :: Text -> Handler a -> Handler a
withHostLock host action = do
tvar <- getsYesod appInstanceMutex
mvar <- liftIO $ do
existing <- M.lookup host <$> readTVarIO tvar
case existing of
Just v -> return v
Nothing -> do
v <- newEmptyMVar
atomically $ stateTVar tvar $ \ m ->
case M.lookup host m of
Just v' -> (v', m)
Nothing -> (v, M.insert host v m)
withMVar mvar $ const action
sumUpTo :: Int -> AppDB Int -> AppDB Int -> AppDB Bool
sumUpTo limit action1 action2 = do
n <- action1
if n <= limit
then do
m <- action2
return $ n + m <= limit
else return False
-- | Grab instance and remote sharer IDs from the DB, inserting new ones if
-- they can't be found in the DB. The @Maybe Bool@ indicates whether the IDs
-- are newly inserted record: 'Nothing' means they're both new. @Just True@
-- means the instance record existed but the remote sharer is new. @Just False@
-- means both the instance and remote sharer existed in the DB.
instanceAndActor
:: Text
-> LocalURI
-> LocalURI
-> AppDB (InstanceId, RemoteSharerId, Maybe Bool)
instanceAndActor host luActor luInbox = do
(iid, inew) <- idAndNew <$> insertBy (Instance host)
let rs = RemoteSharer luActor iid luInbox
if inew
then do
rsid <- insert rs
return (iid, rsid, Nothing)
else do
(rsid, rsnew) <- idAndNew <$> insertBy rs
return (iid, rsid, Just rsnew)
where
idAndNew (Left (Entity iid _)) = (iid, False)
idAndNew (Right iid) = (iid, True)
actorRoom :: Int -> RemoteSharerId -> AppDB Bool
actorRoom limit rsid = do
sumUpTo limit
(count [VerifKeySharedUsageUser ==. rsid])
(count [VerifKeySharer ==. Just rsid])
getOldUsageId rsid = fmap entityKey . listToMaybe <$> selectList [VerifKeySharedUsageUser ==. rsid] [Asc VerifKeySharedUsageId, LimitTo 1]
getOldPersonalKeyId rsid = fmap entityKey . listToMaybe <$> selectList [VerifKeySharer ==. Just rsid] [Asc VerifKeyExpires, Asc VerifKeyId, LimitTo 1]
makeActorRoomByPersonal limit rsid vkid = do
room <-
if limit <= 1
then return False
else (< limit-1) <$> count [VerifKeySharer ==. Just rsid, VerifKeyId !=. vkid]
unless room $ delete vkid
makeActorRoomByUsage limit rsid suid = do
room <-
if limit <= 1
then return False
else
sumUpTo (limit-1)
(count [VerifKeySharedUsageUser ==. rsid, VerifKeySharedUsageId !=. suid])
(count [VerifKeySharer ==. Just rsid])
unless room $ delete suid
-- | Checks whether the given actor has room left for a new shared key usage
-- record, and if not, deletes a record to make room for a new one. It prefers
-- to delete a usage record if any exist; otherwise it deletes a personal key.
--
-- The first parameter is the actor key storage limit, and it must be above
-- zero.
makeActorRoomForUsage :: Int -> RemoteSharerId -> AppDB ()
makeActorRoomForUsage limit rsid = do
msuid <- getOldUsageId rsid
case msuid of
Nothing -> do
mvkid <- getOldPersonalKeyId rsid
case mvkid of
Nothing -> return ()
Just vkid -> makeActorRoomByPersonal limit rsid vkid
Just suid -> makeActorRoomByUsage limit rsid suid
-- | Checks whether the given actor has room left for a new personal key
-- record, and if not, deletes a record to make room for a new one. It prefers
-- to delete a personal key if any exist; otherwise it deletes a usage record.
--
-- The first parameter is the actor key storage limit, and it must be above
-- zero.
makeActorRoomForPersonalKey :: Int -> RemoteSharerId -> AppDB ()
makeActorRoomForPersonalKey limit rsid = do
mvkid <- getOldPersonalKeyId rsid
case mvkid of
Nothing -> do
msuid <- getOldUsageId rsid
case msuid of
Nothing -> return ()
Just suid -> makeActorRoomByUsage limit rsid suid
Just vkid -> makeActorRoomByPersonal limit rsid vkid
-- | Checks whether the given instance has room left for a new shared key
-- record, and if not, deletes a record to make room for a new one.
--
-- The first parameter is the actor key storage limit, and it must be above
-- zero.
makeInstanceRoom :: Int -> InstanceId -> AppDB ()
makeInstanceRoom limit iid = do
mvk <- listToMaybe <$> selectList [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing] [Asc VerifKeyExpires, Asc VerifKeyId, LimitTo 1]
case mvk of
Nothing -> return ()
Just (Entity vkid _) -> do
room <-
if limit <= 1
then return False
else (< limit-1) <$> count [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing, VerifKeyId !=. vkid]
unless room $ delete vkid
data RoomModeDB
= RoomModeNoLimit
| RoomModeLimit Int
data RoomMode
= RoomModeInstant
| RoomModeCached RoomModeDB
roomModeFromLimit :: Maybe Int -> RoomMode
roomModeFromLimit Nothing = RoomModeCached $ RoomModeNoLimit
roomModeFromLimit (Just limit) =
if limit <= 0
then RoomModeInstant
else RoomModeCached $ RoomModeLimit limit
actorRoomMode :: AppSettings -> RoomMode
actorRoomMode = roomModeFromLimit . appMaxActorKeys
instanceRoomMode :: AppSettings -> RoomMode
instanceRoomMode = roomModeFromLimit . appMaxInstanceKeys
-- | Given a shared key we have in our DB, verify that the given actor lists
-- this key, and update the DB accordingly.
--
-- * If the storage limit on actor keys is zero:
-- - If we're supposed to reject signatures when there's no room, raise
-- an error! We can't store anything with a limit of 0
-- - Otherwise, fetch the actor, store in DB if we don't have it, verify
-- usage via actor JSON. Usage isn't stored in the DB.
-- * If there's no storage limit, or it's above zero:
-- - If we know the actor and we have a record that it lists the key,
-- return success, no other action
-- - If we know the actor but we don't have a record of usage, fetch the
-- actor and verify usage. If the actor already has the maximal number of
-- keys: If we're supposed to reject signatures when there's no room,
-- raise an error. Otherwise, delete an old key/usage and store the new
-- usage in the DB.
-- - If we don't know the actor, fetch actor, verify usage, store actor and
-- usage in DB.
--
-- If we get success, that means the actor lists the key, and both the actor
-- and the usage exist in our DB now (if the storage limit isn't zero).
keyListedByActorShared
:: Manager
-> InstanceId
-> VerifKeyId
-> Text
-> LocalURI
-> LocalURI
-> ExceptT String Handler ()
keyListedByActorShared manager iid vkid host luKey luActor = do
(reject, roomMode) <- do
s <- getsYesod appSettings
return (appRejectOnMaxKeys s, actorRoomMode s)
case roomMode of
RoomModeInstant -> do
when reject $ throwE "Actor key storage limit is 0 and set to reject"
luInbox <- actorInbox <$> ExceptT (keyListedByActor manager host luKey luActor)
_ <- lift $ runDB $ insertUnique $ RemoteSharer luActor iid luInbox
return ()
RoomModeCached m -> do
mresult <- do
ments <- lift $ runDB $ do
mrs <- getBy $ UniqueRemoteSharer iid luActor
for mrs $ \ (Entity rsid _) ->
(rsid,) . isJust <$>
getBy (UniqueVerifKeySharedUsage vkid rsid)
return $
case ments of
Nothing -> Just Nothing
Just (rsid, used) ->
if used
then Nothing
else Just $ Just rsid
for_ mresult $ \ mrsid -> do
luInbox <- actorInbox <$> ExceptT (keyListedByActor manager host luKey luActor)
ExceptT $ runDB $ do
vkExists <- isJust <$> get vkid
case mrsid of
Nothing -> do
rsid <- insert $ RemoteSharer luActor iid luInbox
when vkExists $ insert_ $ VerifKeySharedUsage vkid rsid
return $ Right ()
Just rsid -> runExceptT $ when vkExists $ do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit -> do
if reject
then do
room <- lift $ actorRoom limit rsid
unless room $ throwE "Actor key storage limit reached"
else lift $ makeActorRoomForUsage limit rsid
lift $ insert_ $ VerifKeySharedUsage vkid rsid
data VerifKeyDetail = VerifKeyDetail
{ vkdKeyId :: LocalURI
, vkdKey :: PublicKey
, vkdExpires :: Maybe UTCTime
, vkdActorId :: LocalURI
, vkdShared :: Bool
}
instance YesodHttpSig App where instance YesodHttpSig App where
data HttpSigVerResult App = HttpSigVerResult (Either String FedURI) data HttpSigVerResult App = HttpSigVerResult (Either String FedURI)
@ -863,9 +633,8 @@ instance YesodHttpSig App where
ua <- case mluActorHeader of ua <- case mluActorHeader of
Nothing -> throwE "Got a sig with an instance key, but actor header not specified!" Nothing -> throwE "Got a sig with an instance key, but actor header not specified!"
Just u -> return u Just u -> return u
manager <- getsYesod appHttpManager
let iid = verifKeyInstance vk let iid = verifKeyInstance vk
withHostLock' host $ keyListedByActorShared manager iid vkid host luKey ua withHostLock' host $ keyListedByActorShared iid vkid host luKey ua
return (ua, True) return (ua, True)
return return
( Right (verifKeyInstance vk, vkid) ( Right (verifKeyInstance vk, vkid)
@ -888,7 +657,7 @@ instance YesodHttpSig App where
if verify' (vkdKey vkd) && stillValid (vkdExpires vkd) if verify' (vkdKey vkd) && stillValid (vkdExpires vkd)
then case inboxOrVkid of then case inboxOrVkid of
Left uinb -> ExceptT $ withHostLock host $ runDB $ addVerifKey host uinb vkd Left uinb -> ExceptT $ withHostLock host $ runDB $ runExceptT $ addVerifKey host uinb vkd
Right _ids -> return () Right _ids -> return ()
else case inboxOrVkid of else case inboxOrVkid of
Left _uinb -> Left _uinb ->
@ -897,7 +666,7 @@ instance YesodHttpSig App where
else errTime else errTime
Right (iid, vkid) -> do Right (iid, vkid) -> do
let ua = vkdActorId vkd let ua = vkdActorId vkd
listed = withHostLock' host $ keyListedByActorShared manager iid vkid host luKey ua listed = withHostLock' host $ keyListedByActorShared iid vkid host luKey ua
(newKey, newExp) <- (newKey, newExp) <-
if vkdShared vkd if vkdShared vkd
then fetchKnownSharedKey manager listed sigAlgo host ua luKey then fetchKnownSharedKey manager listed sigAlgo host ua luKey
@ -948,63 +717,6 @@ instance YesodHttpSig App where
, vkdShared = s , vkdShared = s
} }
) )
addVerifKey h uinb vkd =
if vkdShared vkd
then addSharedKey h uinb vkd
else addPersonalKey h uinb vkd
where
addSharedKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = runExceptT $ do
(reject, roomModeA, roomModeI) <- do
s <- getsYesod appSettings
return (appRejectOnMaxKeys s, actorRoomMode s, instanceRoomMode s)
(iid, rsid, inew) <- lift $ instanceAndActor host luActor luInbox
case roomModeI of
RoomModeInstant ->
when reject $ throwE "Instance key storage limit is 0 and set to reject"
RoomModeCached m -> do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit ->
if reject
then when (isJust inew) $ do
room <- lift $ instanceRoom limit iid
unless room $ throwE "Instance key storage limit reached"
else when (isJust inew) $ lift $ makeInstanceRoom limit iid
vkid <- lift $ insert $ VerifKey luKey iid mexpires key Nothing
case roomModeA of
RoomModeInstant ->
when reject $ throwE "Actor key storage limit is 0 and set to reject"
RoomModeCached m -> do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit ->
if reject
then when (inew == Just False) $ do
room <- lift $ actorRoom limit rsid
unless room $ throwE "Actor key storage limit reached"
else when (inew == Just False) $ lift $ makeActorRoomForUsage limit rsid
lift $ insert_ $ VerifKeySharedUsage vkid rsid
where
instanceRoom n iid =
(< n) <$> count [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing]
addPersonalKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = runExceptT $ do
(reject, roomMode) <- do
s <- getsYesod appSettings
return (appRejectOnMaxKeys s, actorRoomMode s)
(iid, rsid, inew) <- lift $ instanceAndActor host luActor luInbox
case roomMode of
RoomModeInstant ->
when reject $ throwE "Actor key storage limit is 0 and set to reject"
RoomModeCached m -> do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit ->
if reject
then when (inew == Just False) $ do
room <- lift $ actorRoom limit rsid
unless room $ throwE "Actor key storage limit reached"
else when (inew == Just False) $ lift $ makeActorRoomForPersonalKey limit rsid
lift $ insert_ $ VerifKey luKey iid mexpires key (Just rsid)
updateVerifKey vkid vkd = updateVerifKey vkid vkd =
update vkid [VerifKeyExpires =. vkdExpires vkd, VerifKeyPublic =. vkdKey vkd] update vkid [VerifKeyExpires =. vkdExpires vkd, VerifKeyPublic =. vkdKey vkd]
withHostLock' h = ExceptT . withHostLock h . runExceptT withHostLock' h = ExceptT . withHostLock h . runExceptT

View file

@ -0,0 +1,433 @@
{- This file is part of Vervis.
-
- Written in 2019 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/>.
-}
-- | This module collects functions for working with the storage of remote
-- instances, actors and their details in our local database.
module Vervis.RemoteActorStore
( InstanceMutex ()
, newInstanceMutex
, YesodRemoteActorStore (..)
, withHostLock
, keyListedByActorShared
, VerifKeyDetail (..)
, addVerifKey
)
where
import Prelude
import Control.Concurrent.MVar (MVar, newEmptyMVar)
import Control.Concurrent.STM.TVar
import Control.Monad
import Control.Monad.STM
import Control.Monad.Trans.Except
import Data.Foldable
import Data.HashMap.Strict (HashMap)
import Data.Maybe
import Data.Text (Text)
import Data.Time.Clock
import Data.Traversable
import Database.Persist
import Database.Persist.Sql
import Network.HTTP.Client
import UnliftIO.MVar (withMVar)
import Yesod.Core
import Yesod.Persist.Core
import qualified Crypto.PubKey.Ed25519 as E
import qualified Data.HashMap.Strict as M
import Network.FedURI
import Web.ActivityPub
import Vervis.Model
newtype InstanceMutex = InstanceMutex (TVar (HashMap Text (MVar ())))
newInstanceMutex :: IO InstanceMutex
newInstanceMutex = InstanceMutex <$> newTVarIO M.empty
data RoomModeDB
= RoomModeNoLimit
| RoomModeLimit Int
data RoomMode
= RoomModeInstant
| RoomModeCached RoomModeDB
class Yesod site => YesodRemoteActorStore site where
siteInstanceMutex :: site -> InstanceMutex
siteInstanceRoomMode :: site -> Maybe Int
siteActorRoomMode :: site -> Maybe Int
siteRejectOnMaxKeys :: site -> Bool
-- TODO this is copied from stm-2.5, remove when we upgrade LTS
stateTVar :: TVar s -> (s -> (a, s)) -> STM a
stateTVar var f = do
s <- readTVar var
let (a, s') = f s -- since we destructure this, we are strict in f
writeTVar var s'
return a
withHostLock
:: YesodRemoteActorStore site
=> Text
-> HandlerFor site a
-> HandlerFor site a
withHostLock host action = do
InstanceMutex tvar <- getsYesod siteInstanceMutex
mvar <- liftIO $ do
existing <- M.lookup host <$> readTVarIO tvar
case existing of
Just v -> return v
Nothing -> do
v <- newEmptyMVar
atomically $ stateTVar tvar $ \ m ->
case M.lookup host m of
Just v' -> (v', m)
Nothing -> (v, M.insert host v m)
withMVar mvar $ const action
sumUpTo :: Int -> YesodDB site Int -> YesodDB site Int -> YesodDB site Bool
sumUpTo limit action1 action2 = do
n <- action1
if n <= limit
then do
m <- action2
return $ n + m <= limit
else return False
-- | Grab instance and remote sharer IDs from the DB, inserting new ones if
-- they can't be found in the DB. The @Maybe Bool@ indicates whether the IDs
-- are newly inserted record: 'Nothing' means they're both new. @Just True@
-- means the instance record existed but the remote sharer is new. @Just False@
-- means both the instance and remote sharer existed in the DB.
instanceAndActor
:: ( PersistUniqueWrite (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> Text
-> LocalURI
-> LocalURI
-> YesodDB site (InstanceId, RemoteSharerId, Maybe Bool)
instanceAndActor host luActor luInbox = do
(iid, inew) <- idAndNew <$> insertBy (Instance host)
let rs = RemoteSharer luActor iid luInbox
if inew
then do
rsid <- insert rs
return (iid, rsid, Nothing)
else do
(rsid, rsnew) <- idAndNew <$> insertBy rs
return (iid, rsid, Just rsnew)
where
idAndNew (Left (Entity iid _)) = (iid, False)
idAndNew (Right iid) = (iid, True)
actorRoom
:: ( PersistQueryRead (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> Int
-> RemoteSharerId
-> YesodDB site Bool
actorRoom limit rsid = do
sumUpTo limit
(count [VerifKeySharedUsageUser ==. rsid])
(count [VerifKeySharer ==. Just rsid])
getOldUsageId
:: ( PersistQueryRead (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> RemoteSharerId
-> YesodDB site (Maybe VerifKeySharedUsageId)
getOldUsageId rsid = fmap entityKey . listToMaybe <$> selectList [VerifKeySharedUsageUser ==. rsid] [Asc VerifKeySharedUsageId, LimitTo 1]
getOldPersonalKeyId
:: ( PersistQueryRead (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> RemoteSharerId
-> YesodDB site (Maybe VerifKeyId)
getOldPersonalKeyId rsid = fmap entityKey . listToMaybe <$> selectList [VerifKeySharer ==. Just rsid] [Asc VerifKeyExpires, Asc VerifKeyId, LimitTo 1]
makeActorRoomByPersonal
:: ( PersistQueryRead (YesodPersistBackend site)
, PersistStoreWrite (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> Int
-> RemoteSharerId
-> VerifKeyId
-> YesodDB site ()
makeActorRoomByPersonal limit rsid vkid = do
room <-
if limit <= 1
then return False
else (< limit-1) <$> count [VerifKeySharer ==. Just rsid, VerifKeyId !=. vkid]
unless room $ delete vkid
makeActorRoomByUsage
:: ( PersistQueryRead (YesodPersistBackend site)
, PersistStoreWrite (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> Int
-> RemoteSharerId
-> VerifKeySharedUsageId
-> YesodDB site ()
makeActorRoomByUsage limit rsid suid = do
room <-
if limit <= 1
then return False
else
sumUpTo (limit-1)
(count [VerifKeySharedUsageUser ==. rsid, VerifKeySharedUsageId !=. suid])
(count [VerifKeySharer ==. Just rsid])
unless room $ delete suid
-- | Checks whether the given actor has room left for a new shared key usage
-- record, and if not, deletes a record to make room for a new one. It prefers
-- to delete a usage record if any exist; otherwise it deletes a personal key.
--
-- The first parameter is the actor key storage limit, and it must be above
-- zero.
makeActorRoomForUsage
:: ( PersistQueryRead (YesodPersistBackend site)
, PersistStoreWrite (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> Int
-> RemoteSharerId
-> YesodDB site ()
makeActorRoomForUsage limit rsid = do
msuid <- getOldUsageId rsid
case msuid of
Nothing -> do
mvkid <- getOldPersonalKeyId rsid
case mvkid of
Nothing -> return ()
Just vkid -> makeActorRoomByPersonal limit rsid vkid
Just suid -> makeActorRoomByUsage limit rsid suid
-- | Checks whether the given actor has room left for a new personal key
-- record, and if not, deletes a record to make room for a new one. It prefers
-- to delete a personal key if any exist; otherwise it deletes a usage record.
--
-- The first parameter is the actor key storage limit, and it must be above
-- zero.
makeActorRoomForPersonalKey
:: ( PersistQueryRead (YesodPersistBackend site)
, PersistStoreWrite (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> Int
-> RemoteSharerId
-> YesodDB site ()
makeActorRoomForPersonalKey limit rsid = do
mvkid <- getOldPersonalKeyId rsid
case mvkid of
Nothing -> do
msuid <- getOldUsageId rsid
case msuid of
Nothing -> return ()
Just suid -> makeActorRoomByUsage limit rsid suid
Just vkid -> makeActorRoomByPersonal limit rsid vkid
-- | Checks whether the given instance has room left for a new shared key
-- record, and if not, deletes a record to make room for a new one.
--
-- The first parameter is the actor key storage limit, and it must be above
-- zero.
makeInstanceRoom
:: ( PersistQueryRead (YesodPersistBackend site)
, PersistStoreWrite (YesodPersistBackend site)
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
)
=> Int
-> InstanceId
-> YesodDB site ()
makeInstanceRoom limit iid = do
mvk <- listToMaybe <$> selectList [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing] [Asc VerifKeyExpires, Asc VerifKeyId, LimitTo 1]
case mvk of
Nothing -> return ()
Just (Entity vkid _) -> do
room <-
if limit <= 1
then return False
else (< limit-1) <$> count [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing, VerifKeyId !=. vkid]
unless room $ delete vkid
roomModeFromLimit :: Maybe Int -> RoomMode
roomModeFromLimit Nothing = RoomModeCached $ RoomModeNoLimit
roomModeFromLimit (Just limit) =
if limit <= 0
then RoomModeInstant
else RoomModeCached $ RoomModeLimit limit
-- | Given a shared key we have in our DB, verify that the given actor lists
-- this key, and update the DB accordingly.
--
-- * If the storage limit on actor keys is zero:
-- - If we're supposed to reject signatures when there's no room, raise
-- an error! We can't store anything with a limit of 0
-- - Otherwise, fetch the actor, store in DB if we don't have it, verify
-- usage via actor JSON. Usage isn't stored in the DB.
-- * If there's no storage limit, or it's above zero:
-- - If we know the actor and we have a record that it lists the key,
-- return success, no other action
-- - If we know the actor but we don't have a record of usage, fetch the
-- actor and verify usage. If the actor already has the maximal number of
-- keys: If we're supposed to reject signatures when there's no room,
-- raise an error. Otherwise, delete an old key/usage and store the new
-- usage in the DB.
-- - If we don't know the actor, fetch actor, verify usage, store actor and
-- usage in DB.
--
-- If we get success, that means the actor lists the key, and both the actor
-- and the usage exist in our DB now (if the storage limit isn't zero).
keyListedByActorShared
:: ( HasHttpManager site
, YesodPersist site
, YesodRemoteActorStore site
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
, PersistQueryRead (YesodPersistBackend site)
, PersistUniqueWrite (YesodPersistBackend site)
)
=> InstanceId
-> VerifKeyId
-> Text
-> LocalURI
-> LocalURI
-> ExceptT String (HandlerFor site) ()
keyListedByActorShared iid vkid host luKey luActor = do
manager <- getsYesod getHttpManager
reject <- getsYesod siteRejectOnMaxKeys
roomMode <- getsYesod $ roomModeFromLimit . siteActorRoomMode
case roomMode of
RoomModeInstant -> do
when reject $ throwE "Actor key storage limit is 0 and set to reject"
luInbox <- actorInbox <$> ExceptT (keyListedByActor manager host luKey luActor)
_ <- lift $ runDB $ insertUnique $ RemoteSharer luActor iid luInbox
return ()
RoomModeCached m -> do
mresult <- do
ments <- lift $ runDB $ do
mrs <- getBy $ UniqueRemoteSharer iid luActor
for mrs $ \ (Entity rsid _) ->
(rsid,) . isJust <$>
getBy (UniqueVerifKeySharedUsage vkid rsid)
return $
case ments of
Nothing -> Just Nothing
Just (rsid, used) ->
if used
then Nothing
else Just $ Just rsid
for_ mresult $ \ mrsid -> do
luInbox <- actorInbox <$> ExceptT (keyListedByActor manager host luKey luActor)
ExceptT $ runDB $ do
vkExists <- isJust <$> get vkid
case mrsid of
Nothing -> do
rsid <- insert $ RemoteSharer luActor iid luInbox
when vkExists $ insert_ $ VerifKeySharedUsage vkid rsid
return $ Right ()
Just rsid -> runExceptT $ when vkExists $ do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit -> do
if reject
then do
room <- lift $ actorRoom limit rsid
unless room $ throwE "Actor key storage limit reached"
else lift $ makeActorRoomForUsage limit rsid
lift $ insert_ $ VerifKeySharedUsage vkid rsid
data VerifKeyDetail = VerifKeyDetail
{ vkdKeyId :: LocalURI
, vkdKey :: E.PublicKey
, vkdExpires :: Maybe UTCTime
, vkdActorId :: LocalURI
, vkdShared :: Bool
}
addVerifKey
:: ( YesodRemoteActorStore site
, BaseBackend (YesodPersistBackend site) ~ SqlBackend
, PersistQueryRead (YesodPersistBackend site)
, PersistUniqueWrite (YesodPersistBackend site)
)
=> Text
-> LocalURI
-> VerifKeyDetail
-> ExceptT String (YesodDB site) ()
addVerifKey h uinb vkd =
if vkdShared vkd
then addSharedKey h uinb vkd
else addPersonalKey h uinb vkd
where
addSharedKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = do
reject <- getsYesod siteRejectOnMaxKeys
roomModeA <- getsYesod $ roomModeFromLimit . siteActorRoomMode
roomModeI <- getsYesod $ roomModeFromLimit . siteInstanceRoomMode
(iid, rsid, inew) <- lift $ instanceAndActor host luActor luInbox
case roomModeI of
RoomModeInstant ->
when reject $ throwE "Instance key storage limit is 0 and set to reject"
RoomModeCached m -> do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit ->
if reject
then when (isJust inew) $ do
room <- lift $ instanceRoom limit iid
unless room $ throwE "Instance key storage limit reached"
else when (isJust inew) $ lift $ makeInstanceRoom limit iid
vkid <- lift $ insert $ VerifKey luKey iid mexpires key Nothing
case roomModeA of
RoomModeInstant ->
when reject $ throwE "Actor key storage limit is 0 and set to reject"
RoomModeCached m -> do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit ->
if reject
then when (inew == Just False) $ do
room <- lift $ actorRoom limit rsid
unless room $ throwE "Actor key storage limit reached"
else when (inew == Just False) $ lift $ makeActorRoomForUsage limit rsid
lift $ insert_ $ VerifKeySharedUsage vkid rsid
where
instanceRoom n iid =
(< n) <$> count [VerifKeyInstance ==. iid, VerifKeySharer ==. Nothing]
addPersonalKey host luInbox (VerifKeyDetail luKey key mexpires luActor _) = do
reject <- getsYesod siteRejectOnMaxKeys
roomMode <- getsYesod $ roomModeFromLimit . siteActorRoomMode
(iid, rsid, inew) <- lift $ instanceAndActor host luActor luInbox
case roomMode of
RoomModeInstant ->
when reject $ throwE "Actor key storage limit is 0 and set to reject"
RoomModeCached m -> do
case m of
RoomModeNoLimit -> return ()
RoomModeLimit limit ->
if reject
then when (inew == Just False) $ do
room <- lift $ actorRoom limit rsid
unless room $ throwE "Actor key storage limit reached"
else when (inew == Just False) $ lift $ makeActorRoomForPersonalKey limit rsid
lift $ insert_ $ VerifKey luKey iid mexpires key (Just rsid)

View file

@ -166,6 +166,7 @@ library
Vervis.Patch Vervis.Patch
Vervis.Query Vervis.Query
Vervis.Readme Vervis.Readme
Vervis.RemoteActorStore
Vervis.Render Vervis.Render
Vervis.Role Vervis.Role
Vervis.Secure Vervis.Secure