Much cleaner storage of remote address within audit
This commit is contained in:
parent
14efbd8656
commit
9894dcd0fd
@ -3,8 +3,8 @@ TransactionLog
|
|||||||
time UTCTime
|
time UTCTime
|
||||||
instance InstanceId
|
instance InstanceId
|
||||||
initiator (CI Text) Maybe -- Case-insensitive user-identifier associated with performing this action
|
initiator (CI Text) Maybe -- Case-insensitive user-identifier associated with performing this action
|
||||||
|
remote IP Maybe -- Remote party that triggered this action via HTTP
|
||||||
info Value -- JSON-encoded `Transaction`
|
info Value -- JSON-encoded `Transaction`
|
||||||
request RequestInfo Maybe -- HTTP-Request info
|
|
||||||
-- Best guess of users affected by a change in database-state at time of transaction
|
-- Best guess of users affected by a change in database-state at time of transaction
|
||||||
TransactionLogAffected
|
TransactionLogAffected
|
||||||
transaction TransactionLogId
|
transaction TransactionLogId
|
||||||
|
|||||||
@ -118,6 +118,7 @@ dependencies:
|
|||||||
- hsass
|
- hsass
|
||||||
- semigroupoids
|
- semigroupoids
|
||||||
- http-types
|
- http-types
|
||||||
|
- ip
|
||||||
|
|
||||||
other-extensions:
|
other-extensions:
|
||||||
- GeneralizedNewtypeDeriving
|
- GeneralizedNewtypeDeriving
|
||||||
|
|||||||
47
src/Audit.hs
47
src/Audit.hs
@ -1,7 +1,8 @@
|
|||||||
module Audit
|
module Audit
|
||||||
( module Audit.Types
|
( module Audit.Types
|
||||||
, audit
|
, audit, audit'
|
||||||
, getRequestInfo
|
, AuditRemoteException(..)
|
||||||
|
, getRemote
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
|
||||||
@ -13,25 +14,27 @@ import Model
|
|||||||
|
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
import qualified Network.Wai as Wai
|
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) #-}
|
{-# ANN module ("HLint: ignore Use newtype instead of data" :: String) #-}
|
||||||
|
|
||||||
|
|
||||||
data RequestInfoException
|
data AuditRemoteException
|
||||||
= RICouldNotParseMethod Method
|
= ARUnsupportedSocketKind
|
||||||
deriving (Show, Generic, Typeable)
|
deriving (Show, Generic, Typeable)
|
||||||
|
|
||||||
instance Exception RequestInfoException
|
instance Exception AuditRemoteException
|
||||||
|
|
||||||
|
|
||||||
getRequestInfo :: (MonadHandler m, MonadThrow m) => m RequestInfo
|
getRemote :: (MonadHandler m, MonadThrow m) => m IP
|
||||||
getRequestInfo = do
|
getRemote = do
|
||||||
wai <- waiRequest
|
wai <- waiRequest
|
||||||
riMethod <- either (throwM . RICouldNotParseMethod) return . parseMethod $ Wai.requestMethod wai
|
case Wai.remoteHost wai of
|
||||||
let riRemote = Wai.remoteHost wai
|
Wai.SockAddrInet _ hAddr -> let (b1, b2, b3, b4) = Wai.hostAddressToTuple hAddr in return $ IP.ipv4 b1 b2 b3 b4
|
||||||
riPath = Wai.pathInfo wai
|
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
|
||||||
riQueryString = queryToQueryText $ Wai.queryString wai
|
_other -> throwM ARUnsupportedSocketKind
|
||||||
return RequestInfo{..}
|
|
||||||
|
|
||||||
|
|
||||||
audit :: ( AuthId site ~ Key User
|
audit :: ( AuthId site ~ Key User
|
||||||
@ -41,23 +44,35 @@ audit :: ( AuthId site ~ Key User
|
|||||||
, HasInstanceID site InstanceId
|
, HasInstanceID site InstanceId
|
||||||
, YesodAuthPersist site
|
, YesodAuthPersist site
|
||||||
)
|
)
|
||||||
=> [UserId] -- ^ Affected users
|
=> Transaction -- ^ Transaction to record
|
||||||
-> Transaction -- ^ Transaction to record
|
-> [UserId] -- ^ Affected users
|
||||||
-> YesodDB site ()
|
-> YesodDB site ()
|
||||||
-- ^ Log a transaction using information available from `HandlerT`:
|
-- ^ Log a transaction using information available from `HandlerT`:
|
||||||
--
|
--
|
||||||
-- - `transactionLogTime` is now
|
-- - `transactionLogTime` is now
|
||||||
-- - `transactionLogInitiator` is currently logged in user (or none)
|
-- - `transactionLogInitiator` is currently logged in user (or none)
|
||||||
-- - `transactionLogRequest` is current HTTP-Request
|
-- - `transactionLogRequest` is current HTTP-Request
|
||||||
audit affected (toJSON -> transactionLogInfo) = do
|
audit (toJSON -> transactionLogInfo) affected = do
|
||||||
uid <- liftHandlerT maybeAuthId
|
uid <- liftHandlerT maybeAuthId
|
||||||
|
|
||||||
transactionLogTime <- liftIO getCurrentTime
|
transactionLogTime <- liftIO getCurrentTime
|
||||||
transactionLogInstance <- getsYesod $ view instanceID
|
transactionLogInstance <- getsYesod $ view instanceID
|
||||||
transactionLogInitiator <- for uid $ fmap userIdent . getJust
|
transactionLogInitiator <- for uid $ fmap userIdent . getJust
|
||||||
transactionLogRequest <- Just <$> getRequestInfo
|
transactionLogRemote <- Just <$> getRemote
|
||||||
|
|
||||||
tlId <- insert TransactionLog{..}
|
tlId <- insert TransactionLog{..}
|
||||||
|
|
||||||
affectedUsers <- forM affected getJust
|
affectedUsers <- forM affected getJust
|
||||||
insertMany_ [ TransactionLogAffected tlId aident | aident <- userIdent <$> affectedUsers ]
|
insertMany_ [ TransactionLogAffected tlId aident | aident <- userIdent <$> affectedUsers ]
|
||||||
|
|
||||||
|
audit' :: ( AuthId site ~ Key User
|
||||||
|
, AuthEntity site ~ User
|
||||||
|
, IsSqlBackend (YesodPersistBackend site)
|
||||||
|
, SqlBackendCanWrite (YesodPersistBackend site)
|
||||||
|
, HasInstanceID site InstanceId
|
||||||
|
, YesodAuthPersist site
|
||||||
|
)
|
||||||
|
=> Transaction -- ^ Transaction to record
|
||||||
|
-> YesodDB site ()
|
||||||
|
-- ^ Special case of `audit` for when there are no affected users
|
||||||
|
audit' = flip audit []
|
||||||
|
|||||||
@ -183,7 +183,7 @@ termEditHandler term = do
|
|||||||
-- term <- runDB $ get $ TermKey termName
|
-- term <- runDB $ get $ TermKey termName
|
||||||
runDB $ do
|
runDB $ do
|
||||||
repsert tid res
|
repsert tid res
|
||||||
audit [] $ TransactionTermEdit tid
|
audit' $ TransactionTermEdit tid
|
||||||
-- VOR INTERNATIONALISIERUNG:
|
-- VOR INTERNATIONALISIERUNG:
|
||||||
-- let tid = termToText $ termName res
|
-- let tid = termToText $ termName res
|
||||||
-- let msg = "Semester " `T.append` tid `T.append` " erfolgreich editiert."
|
-- let msg = "Semester " `T.append` tid `T.append` " erfolgreich editiert."
|
||||||
|
|||||||
@ -9,7 +9,7 @@ module Model.Types
|
|||||||
, module Mail
|
, module Mail
|
||||||
, module Utils.DateTime
|
, module Utils.DateTime
|
||||||
, module Data.UUID.Types
|
, module Data.UUID.Types
|
||||||
, StdMethod, QueryText, SockAddr, PortNumber
|
, module Net.IP
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
@ -27,7 +27,6 @@ import Data.Universe
|
|||||||
import Data.Universe.Helpers
|
import Data.Universe.Helpers
|
||||||
import Data.UUID.Types (UUID)
|
import Data.UUID.Types (UUID)
|
||||||
import qualified Data.UUID.Types as UUID
|
import qualified Data.UUID.Types as UUID
|
||||||
import Data.Scientific (Scientific, toBoundedInteger)
|
|
||||||
|
|
||||||
import Data.Default
|
import Data.Default
|
||||||
|
|
||||||
@ -44,6 +43,7 @@ import Web.PathPieces
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import qualified Data.Text.Lens as Text
|
import qualified Data.Text.Lens as Text
|
||||||
|
import qualified Data.Text.Encoding as Text
|
||||||
|
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
|
|
||||||
@ -71,7 +71,6 @@ import Utils.DateTime (DateTimeFormat(..), SelDateTimeFormat(..))
|
|||||||
|
|
||||||
import Numeric.Natural
|
import Numeric.Natural
|
||||||
import Data.Word.Word24 (Word24)
|
import Data.Word.Word24 (Word24)
|
||||||
import Data.Word (Word16)
|
|
||||||
import Data.Bits
|
import Data.Bits
|
||||||
import Data.Ix
|
import Data.Ix
|
||||||
import Data.List (genericIndex, elemIndex)
|
import Data.List (genericIndex, elemIndex)
|
||||||
@ -83,9 +82,8 @@ import Data.Text.Metrics (damerauLevenshtein)
|
|||||||
|
|
||||||
import Data.Binary (Binary)
|
import Data.Binary (Binary)
|
||||||
|
|
||||||
import Network.HTTP.Types.URI (QueryText)
|
import Net.IP (IP)
|
||||||
import Network.HTTP.Types.Method (StdMethod(..))
|
import qualified Net.IP as IP
|
||||||
import Network.Socket
|
|
||||||
|
|
||||||
|
|
||||||
instance PathPiece UUID where
|
instance PathPiece UUID where
|
||||||
@ -793,35 +791,14 @@ deriveJSON defaultOptions
|
|||||||
derivePersistFieldJSON ''LecturerType
|
derivePersistFieldJSON ''LecturerType
|
||||||
|
|
||||||
|
|
||||||
deriving instance Generic StdMethod
|
instance PersistField IP where
|
||||||
deriving instance Generic SockAddr
|
toPersistValue = PersistDbSpecific . encodeUtf8 . IP.encode
|
||||||
|
fromPersistValue (PersistDbSpecific bs) = first tshow (Text.decodeUtf8' bs) >>= maybe (Left "Could not parse IP-address") Right . IP.decode
|
||||||
deriveJSON defaultOptions
|
fromPersistValue (PersistByteString bs) = first tshow (Text.decodeUtf8' bs) >>= maybe (Left "Could not parse IP-address") Right . IP.decode
|
||||||
{ constructorTagModifier = camelToPathPiece
|
fromPersistValue (PersistText t) = maybe (Left "Could not parse IP-address") Right $ IP.decode t
|
||||||
} ''StdMethod
|
fromPersistValue _ = Left "IP-address values must be converted from PersistDbSpecific, PersistText, or PersistByteString"
|
||||||
deriveJSON defaultOptions
|
instance PersistFieldSql IP where
|
||||||
{ constructorTagModifier = camelToPathPiece' 2
|
sqlType _ = SqlOther "inet"
|
||||||
} ''SockAddr
|
|
||||||
|
|
||||||
instance FromJSON PortNumber where
|
|
||||||
parseJSON = Aeson.withScientific "PortNumber" $ \sciNum -> case toBoundedInteger sciNum of
|
|
||||||
Just int -> return $ fromIntegral (int :: Word16)
|
|
||||||
Nothing -> fail "Expected whole number of plausible size to denote port"
|
|
||||||
instance ToJSON PortNumber where
|
|
||||||
toJSON = toJSON . (fromIntegral :: PortNumber -> Scientific)
|
|
||||||
|
|
||||||
|
|
||||||
data RequestInfo = RequestInfo
|
|
||||||
{ riMethod :: StdMethod
|
|
||||||
, riRemote :: SockAddr
|
|
||||||
, riPath :: [Text]
|
|
||||||
, riQueryString :: QueryText
|
|
||||||
} deriving (Eq, Ord, Show, Generic, Typeable)
|
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
|
||||||
} ''RequestInfo
|
|
||||||
derivePersistFieldJSON ''RequestInfo
|
|
||||||
|
|
||||||
|
|
||||||
-- Type synonyms
|
-- Type synonyms
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import ClassyPrelude.Yesod
|
|||||||
import Data.UUID (UUID)
|
import Data.UUID (UUID)
|
||||||
import qualified Control.Exception as Exception
|
import qualified Control.Exception as Exception
|
||||||
import Data.Aeson (Result (..), fromJSON, withObject
|
import Data.Aeson (Result (..), fromJSON, withObject
|
||||||
,(.!=), (.:?)
|
,(.!=), (.:?), withScientific
|
||||||
)
|
)
|
||||||
import qualified Data.Aeson.Types as Aeson
|
import qualified Data.Aeson.Types as Aeson
|
||||||
import Data.Aeson.TH
|
import Data.Aeson.TH
|
||||||
@ -272,6 +272,11 @@ deriveJSON
|
|||||||
}
|
}
|
||||||
''LogLevel
|
''LogLevel
|
||||||
|
|
||||||
|
instance FromJSON HaskellNet.PortNumber where
|
||||||
|
parseJSON = withScientific "PortNumber" $ \sciNum -> case toBoundedInteger sciNum of
|
||||||
|
Just int -> return $ fromIntegral (int :: Word16)
|
||||||
|
Nothing -> fail "Expected whole number of plausible size to denote port"
|
||||||
|
|
||||||
deriveFromJSON
|
deriveFromJSON
|
||||||
defaultOptions
|
defaultOptions
|
||||||
{ constructorTagModifier = unpack . intercalate "-" . Text.splitOn "_" . toLower . pack
|
{ constructorTagModifier = unpack . intercalate "-" . Text.splitOn "_" . toLower . pack
|
||||||
|
|||||||
Reference in New Issue
Block a user