module Audit ( module Audit.Types , AuditException(..) , audit, audit' , AuditRemoteException(..) , getRemote ) where import Import.NoModel import Model import Database.Persist.Sql import Audit.Types import Utils.Lens import qualified Network.Wai as Wai import qualified Network.Socket as Wai import qualified Net.IP as IP {-# ANN module ("HLint: ignore Use newtype instead of data" :: String) #-} data AuditRemoteException = ARUnsupportedSocketKind deriving (Show, Generic, Typeable) instance Exception AuditRemoteException getRemote :: (MonadHandler m, MonadThrow m) => m IP getRemote = do wai <- waiRequest case Wai.remoteHost wai of Wai.SockAddrInet _ hAddr -> let (b1, b2, b3, b4) = Wai.hostAddressToTuple hAddr in return $ IP.ipv4 b1 b2 b3 b4 Wai.SockAddrInet6 _ _ hAddr _ -> let (w1, w2, w3, w4, w5, w6, w7, w8) = Wai.hostAddress6ToTuple hAddr in return $ IP.ipv6 w1 w2 w3 w4 w5 w6 w7 w8 _other -> throwM ARUnsupportedSocketKind data AuditException = AuditRemoteException AuditRemoteException | AuditUserNotFound UserId deriving (Show, Generic, Typeable) instance Exception AuditException audit :: ( AuthId (HandlerSite m) ~ Key User , AuthEntity (HandlerSite m) ~ User , IsSqlBackend (YesodPersistBackend (HandlerSite m)) , SqlBackendCanWrite (YesodPersistBackend (HandlerSite m)) , HasInstanceID (HandlerSite m) InstanceId , YesodAuthPersist (HandlerSite m) , MonadHandler m , MonadCatch m ) => Transaction -- ^ Transaction to record -> [UserId] -- ^ Affected users -> ReaderT (YesodPersistBackend (HandlerSite m)) m () -- ^ Log a transaction using information available from `HandlerT`: -- -- - `transactionLogTime` is now -- - `transactionLogInitiator` is currently logged in user (or none) -- - `transactionLogRemote` is determined from current HTTP-Request audit (toJSON -> transactionLogInfo) affected = do uid <- liftHandlerT maybeAuthId transactionLogTime <- liftIO getCurrentTime transactionLogInstance <- getsYesod $ view instanceID transactionLogInitiator <- for uid $ \uid' -> maybe (throwM $ AuditUserNotFound uid') (return . userIdent) =<< get uid' transactionLogRemote <- handle (throwM . AuditRemoteException) $ Just <$> getRemote tlId <- insert TransactionLog{..} affectedUsers <- forM affected $ \uid' -> maybe (throwM $ AuditUserNotFound uid') (return . userIdent) =<< get uid' insertMany_ $ map (TransactionLogAffected tlId) affectedUsers audit' :: ( AuthId (HandlerSite m) ~ Key User , AuthEntity (HandlerSite m) ~ User , IsSqlBackend (YesodPersistBackend (HandlerSite m)) , SqlBackendCanWrite (YesodPersistBackend (HandlerSite m)) , HasInstanceID (HandlerSite m) InstanceId , YesodAuthPersist (HandlerSite m) , MonadHandler m , MonadCatch m ) => Transaction -- ^ Transaction to record -> ReaderT (YesodPersistBackend (HandlerSite m)) m () -- ^ Special case of `audit` for when there are no affected users audit' = flip audit []