fix(failover): don't always record as failed
Also improve ldap error handling
This commit is contained in:
parent
d3c727bab6
commit
16643b6244
@ -132,7 +132,7 @@ mkYesodDispatch "UniWorX" resourcesUniWorX
|
|||||||
-- performs initialization and returns a foundation datatype value. This is also
|
-- performs initialization and returns a foundation datatype value. This is also
|
||||||
-- the place to put your migrate statements to have automatic database
|
-- the place to put your migrate statements to have automatic database
|
||||||
-- migrations handled by Yesod.
|
-- migrations handled by Yesod.
|
||||||
makeFoundation :: (MonadResource m, MonadUnliftIO m, MonadThrow m) => AppSettings -> m UniWorX
|
makeFoundation :: (MonadResource m, MonadUnliftIO m, MonadCatch m) => AppSettings -> m UniWorX
|
||||||
makeFoundation appSettings'@AppSettings{..} = do
|
makeFoundation appSettings'@AppSettings{..} = do
|
||||||
registerGHCMetrics
|
registerGHCMetrics
|
||||||
|
|
||||||
|
|||||||
@ -13,12 +13,9 @@ module Auth.LDAP
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Import.NoFoundation
|
import Import.NoFoundation
|
||||||
import Network.Connection
|
|
||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
import qualified Control.Monad.Catch as Exc
|
|
||||||
|
|
||||||
import Utils.Form
|
import Utils.Form
|
||||||
|
|
||||||
import qualified Ldap.Client as Ldap
|
import qualified Ldap.Client as Ldap
|
||||||
@ -92,9 +89,6 @@ ldapUserEmail = Ldap.Attr "mail" :|
|
|||||||
|
|
||||||
|
|
||||||
data CampusUserException = CampusUserLdapError LdapPoolError
|
data CampusUserException = CampusUserLdapError LdapPoolError
|
||||||
| CampusUserHostNotResolved String
|
|
||||||
| CampusUserLineTooLong
|
|
||||||
| CampusUserHostCannotConnect String [IOException]
|
|
||||||
| CampusUserNoResult
|
| CampusUserNoResult
|
||||||
| CampusUserAmbiguous
|
| CampusUserAmbiguous
|
||||||
deriving (Show, Eq, Generic, Typeable)
|
deriving (Show, Eq, Generic, Typeable)
|
||||||
@ -114,7 +108,7 @@ campusUserWith :: MonadUnliftIO m
|
|||||||
-> FailoverMode
|
-> FailoverMode
|
||||||
-> Creds site
|
-> Creds site
|
||||||
-> m (Ldap.AttrList [])
|
-> m (Ldap.AttrList [])
|
||||||
campusUserWith withLdap' pool mode Creds{..} = liftIO . (`catches` errHandlers) $ either (throwM . CampusUserLdapError) return <=< withLdap' _2 pool mode $ \(conf@LdapConf{..}, ldap) -> do
|
campusUserWith withLdap' pool mode Creds{..} = liftIO $ either (throwM . CampusUserLdapError) return <=< withLdap' _2 pool mode $ \(conf@LdapConf{..}, ldap) -> do
|
||||||
Ldap.bind ldap ldapDn ldapPassword
|
Ldap.bind ldap ldapDn ldapPassword
|
||||||
results <- case lookup "DN" credsExtra of
|
results <- case lookup "DN" credsExtra of
|
||||||
Just userDN -> do
|
Just userDN -> do
|
||||||
@ -126,11 +120,6 @@ campusUserWith withLdap' pool mode Creds{..} = liftIO . (`catches` errHandlers)
|
|||||||
[] -> throwM CampusUserNoResult
|
[] -> throwM CampusUserNoResult
|
||||||
[Ldap.SearchEntry _ attrs] -> return attrs
|
[Ldap.SearchEntry _ attrs] -> return attrs
|
||||||
_otherwise -> throwM CampusUserAmbiguous
|
_otherwise -> throwM CampusUserAmbiguous
|
||||||
where
|
|
||||||
errHandlers = [ Exc.Handler $ \LineTooLong -> throwM CampusUserLineTooLong
|
|
||||||
, Exc.Handler $ \(HostNotResolved host) -> throwM $ CampusUserHostNotResolved host
|
|
||||||
, Exc.Handler $ \(HostCannotConnect host excs) -> throwM $ CampusUserHostCannotConnect host excs
|
|
||||||
]
|
|
||||||
|
|
||||||
campusUserReTest :: MonadUnliftIO m => Failover (LdapConf, LdapPool) -> (Nano -> Bool) -> FailoverMode -> Creds site -> m (Ldap.AttrList [])
|
campusUserReTest :: MonadUnliftIO m => Failover (LdapConf, LdapPool) -> (Nano -> Bool) -> FailoverMode -> Creds site -> m (Ldap.AttrList [])
|
||||||
campusUserReTest pool doTest = campusUserWith (\l -> flip (withLdapFailoverReTest l) doTest) pool
|
campusUserReTest pool doTest = campusUserWith (\l -> flip (withLdapFailoverReTest l) doTest) pool
|
||||||
@ -148,18 +137,13 @@ campusUser' pool mode User{userIdent}
|
|||||||
|
|
||||||
|
|
||||||
campusUserMatr :: MonadUnliftIO m => Failover (LdapConf, LdapPool) -> FailoverMode -> UserMatriculation -> m (Ldap.AttrList [])
|
campusUserMatr :: MonadUnliftIO m => Failover (LdapConf, LdapPool) -> FailoverMode -> UserMatriculation -> m (Ldap.AttrList [])
|
||||||
campusUserMatr pool mode userMatr = liftIO . (`catches` errHandlers) $ either (throwM . CampusUserLdapError) return <=< withLdapFailover _2 pool mode $ \(conf@LdapConf{..}, ldap) -> do
|
campusUserMatr pool mode userMatr = liftIO $ either (throwM . CampusUserLdapError) return <=< withLdapFailover _2 pool mode $ \(conf@LdapConf{..}, ldap) -> do
|
||||||
Ldap.bind ldap ldapDn ldapPassword
|
Ldap.bind ldap ldapDn ldapPassword
|
||||||
results <- findUserMatr conf ldap userMatr []
|
results <- findUserMatr conf ldap userMatr []
|
||||||
case results of
|
case results of
|
||||||
[] -> throwM CampusUserNoResult
|
[] -> throwM CampusUserNoResult
|
||||||
[Ldap.SearchEntry _ attrs] -> return attrs
|
[Ldap.SearchEntry _ attrs] -> return attrs
|
||||||
_otherwise -> throwM CampusUserAmbiguous
|
_otherwise -> throwM CampusUserAmbiguous
|
||||||
where
|
|
||||||
errHandlers = [ Exc.Handler $ \LineTooLong -> throwM CampusUserLineTooLong
|
|
||||||
, Exc.Handler $ \(HostNotResolved host) -> throwM $ CampusUserHostNotResolved host
|
|
||||||
, Exc.Handler $ \(HostCannotConnect host excs) -> throwM $ CampusUserHostCannotConnect host excs
|
|
||||||
]
|
|
||||||
|
|
||||||
campusUserMatr' :: (MonadCatch m, MonadUnliftIO m) => Failover (LdapConf, LdapPool) -> FailoverMode -> UserMatriculation -> m (Maybe (Ldap.AttrList []))
|
campusUserMatr' :: (MonadCatch m, MonadUnliftIO m) => Failover (LdapConf, LdapPool) -> FailoverMode -> UserMatriculation -> m (Maybe (Ldap.AttrList []))
|
||||||
campusUserMatr' pool mode
|
campusUserMatr' pool mode
|
||||||
|
|||||||
@ -26,12 +26,13 @@ import Data.Time.Clock (NominalDiffTime)
|
|||||||
import Data.Dynamic
|
import Data.Dynamic
|
||||||
|
|
||||||
import Control.Monad.Trans.Resource (MonadResource)
|
import Control.Monad.Trans.Resource (MonadResource)
|
||||||
import qualified Control.Monad.Trans.Resource as Resource
|
|
||||||
import Control.Monad.Catch
|
import Control.Monad.Catch
|
||||||
|
|
||||||
import Control.Monad.Trans.Except (throwE)
|
import Control.Monad.Trans.Except (throwE)
|
||||||
import Data.Fixed (Nano)
|
import Data.Fixed (Nano)
|
||||||
|
|
||||||
|
import Network.Connection
|
||||||
|
|
||||||
|
|
||||||
type LdapPool = Pool LdapExecutor
|
type LdapPool = Pool LdapExecutor
|
||||||
data LdapExecutor = LdapExecutor
|
data LdapExecutor = LdapExecutor
|
||||||
@ -40,10 +41,11 @@ data LdapExecutor = LdapExecutor
|
|||||||
, ldapAsync :: Async ()
|
, ldapAsync :: Async ()
|
||||||
}
|
}
|
||||||
|
|
||||||
data LdapPoolError = LdapPoolTimeout | LdapError LdapError
|
data LdapPoolError = LdapPoolTimeout
|
||||||
|
| LdapError LdapError
|
||||||
|
| LdapLineTooLong | LdapHostNotResolved String | LdapHostCannotConnect String [IOException]
|
||||||
deriving (Eq, Show, Generic, Typeable)
|
deriving (Eq, Show, Generic, Typeable)
|
||||||
|
deriving anyclass (Exception)
|
||||||
instance Exception LdapPoolError
|
|
||||||
|
|
||||||
|
|
||||||
withLdap :: (MonadUnliftIO m, MonadCatch m, Typeable a) => LdapPool -> (Ldap -> m a) -> m (Either LdapPoolError a)
|
withLdap :: (MonadUnliftIO m, MonadCatch m, Typeable a) => LdapPool -> (Ldap -> m a) -> m (Either LdapPoolError a)
|
||||||
@ -56,7 +58,11 @@ withLdapFailoverReTest :: (MonadUnliftIO m, MonadCatch m, Typeable a) => Lens p
|
|||||||
withLdapFailoverReTest l@(flip withLens const -> proj) pool' doTest mode act = try . withFailoverReTest pool' doTest mode (either throwE return) $ \x -> withLdap (proj x) (\c -> act $ x & l .~ c)
|
withLdapFailoverReTest l@(flip withLens const -> proj) pool' doTest mode act = try . withFailoverReTest pool' doTest mode (either throwE return) $ \x -> withLdap (proj x) (\c -> act $ x & l .~ c)
|
||||||
|
|
||||||
|
|
||||||
createLdapPool :: ( MonadLoggerIO m, MonadResource m )
|
createLdapPool :: forall m.
|
||||||
|
( MonadLogger m
|
||||||
|
, MonadResource m, MonadUnliftIO m
|
||||||
|
, MonadCatch m
|
||||||
|
)
|
||||||
=> Ldap.Host
|
=> Ldap.Host
|
||||||
-> Ldap.PortNumber
|
-> Ldap.PortNumber
|
||||||
-> Int -- ^ Stripes
|
-> Int -- ^ Stripes
|
||||||
@ -65,16 +71,14 @@ createLdapPool :: ( MonadLoggerIO m, MonadResource m )
|
|||||||
-> Int -- ^ Limit
|
-> Int -- ^ Limit
|
||||||
-> m LdapPool
|
-> m LdapPool
|
||||||
createLdapPool host port stripes timeoutConn (round . (* 1e6) -> timeoutAct) limit = do
|
createLdapPool host port stripes timeoutConn (round . (* 1e6) -> timeoutAct) limit = do
|
||||||
logFunc <- askLoggerIO
|
|
||||||
|
|
||||||
let
|
let
|
||||||
mkExecutor :: Resource.InternalState -> IO LdapExecutor
|
mkExecutor :: m LdapExecutor
|
||||||
mkExecutor rSt = Resource.runInternalState ?? rSt $ do
|
mkExecutor = handleAny (\e -> $logErrorS "LdapExecuter" (tshow e) >> throwM e) . (`catches` convertErrors) $ do
|
||||||
ldapDestroy <- liftIO newEmptyTMVarIO
|
ldapDestroy <- liftIO newEmptyTMVarIO
|
||||||
ldapAct <- liftIO newEmptyTMVarIO
|
ldapAct <- liftIO newEmptyTMVarIO
|
||||||
|
|
||||||
let
|
let
|
||||||
ldapExec :: forall a m. (Typeable a, MonadUnliftIO m) => (Ldap -> m a) -> m (Either LdapPoolError a)
|
ldapExec :: forall a m'. (Typeable a, MonadUnliftIO m') => (Ldap -> m' a) -> m' (Either LdapPoolError a)
|
||||||
ldapExec act = withRunInIO $ \runInIO -> do
|
ldapExec act = withRunInIO $ \runInIO -> do
|
||||||
ldapAnswer <- newEmptyTMVarIO :: IO (TMVar (Either SomeException Dynamic))
|
ldapAnswer <- newEmptyTMVarIO :: IO (TMVar (Either SomeException Dynamic))
|
||||||
atomically $ putTMVar ldapAct (runInIO . fmap toDyn . act, ldapAnswer)
|
atomically $ putTMVar ldapAct (runInIO . fmap toDyn . act, ldapAnswer)
|
||||||
@ -87,7 +91,7 @@ createLdapPool host port stripes timeoutConn (round . (* 1e6) -> timeoutAct) lim
|
|||||||
, Handler $ return . Left . (id :: LdapPoolError -> LdapPoolError)
|
, Handler $ return . Left . (id :: LdapPoolError -> LdapPoolError)
|
||||||
]
|
]
|
||||||
|
|
||||||
go :: Maybe (TMVar (Maybe a)) -> Ldap -> LoggingT IO ()
|
go :: Maybe (TMVar (Maybe a)) -> Ldap -> m ()
|
||||||
go waiting ldap = do
|
go waiting ldap = do
|
||||||
$logDebugS "LdapExecutor" "Waiting"
|
$logDebugS "LdapExecutor" "Waiting"
|
||||||
for_ waiting $ atomically . flip putTMVar Nothing
|
for_ waiting $ atomically . flip putTMVar Nothing
|
||||||
@ -95,7 +99,7 @@ createLdapPool host port stripes timeoutConn (round . (* 1e6) -> timeoutAct) lim
|
|||||||
case instruction of
|
case instruction of
|
||||||
Nothing -> $logDebugS "LdapExecutor" "Terminating"
|
Nothing -> $logDebugS "LdapExecutor" "Terminating"
|
||||||
Just (act, returnRes) -> do
|
Just (act, returnRes) -> do
|
||||||
$logDebugS "LdapExecutor" "Executing"
|
$logDebugS "LdapExecutor" $ "Executing " <> tshow (host, port)
|
||||||
res <- try . withTimeout . liftIO $ act ldap
|
res <- try . withTimeout . liftIO $ act ldap
|
||||||
didReturn <- atomically $ tryPutTMVar returnRes res
|
didReturn <- atomically $ tryPutTMVar returnRes res
|
||||||
unless didReturn $
|
unless didReturn $
|
||||||
@ -103,22 +107,24 @@ createLdapPool host port stripes timeoutConn (round . (* 1e6) -> timeoutAct) lim
|
|||||||
either throwM (const $ return ()) res
|
either throwM (const $ return ()) res
|
||||||
`catches`
|
`catches`
|
||||||
[ Handler $ \case
|
[ Handler $ \case
|
||||||
Ldap.ResponseError _ -> return ()
|
Ldap.ResponseError Ldap.ResponseErrorCode{}
|
||||||
Ldap.DisconnectError _ -> return ()
|
-> return ()
|
||||||
other -> throwM other
|
other
|
||||||
|
-> throwM other
|
||||||
]
|
]
|
||||||
go Nothing ldap
|
go Nothing ldap
|
||||||
|
|
||||||
ldapAsync <- withTimeout $ do
|
ldapAsync <- withTimeout $ do
|
||||||
setup <- liftIO newEmptyTMVarIO
|
setup <- liftIO newEmptyTMVarIO
|
||||||
|
|
||||||
ldapAsync <- allocateAsync . flip runLoggingT logFunc $ do
|
ldapAsync <- allocateLinkedAsync . handleAny (atomically . void . tryPutTMVar setup . Just) $ do
|
||||||
$logDebugS "LdapExecutor" "Starting"
|
$logDebugS "LdapExecutor" $ "Starting " <> tshow (host, port)
|
||||||
res <- liftIO . Ldap.with host port $ flip runLoggingT logFunc . go (Just setup)
|
res <- withRunInIO $ \runInIO ->
|
||||||
|
Ldap.with host port $ runInIO . go (Just setup)
|
||||||
case res of
|
case res of
|
||||||
Left exc -> do
|
Left exc -> do
|
||||||
$logWarnS "LdapExecutor" $ tshow exc
|
$logWarnS "LdapExecutor" $ tshow exc
|
||||||
atomically . void . tryPutTMVar setup $ Just exc
|
atomically . void . tryPutTMVar setup . Just $ toException exc
|
||||||
Right res' -> return res'
|
Right res' -> return res'
|
||||||
|
|
||||||
maybe (return ()) throwM =<< atomically (takeTMVar setup)
|
maybe (return ()) throwM =<< atomically (takeTMVar setup)
|
||||||
@ -131,8 +137,14 @@ createLdapPool host port stripes timeoutConn (round . (* 1e6) -> timeoutAct) lim
|
|||||||
delExecutor LdapExecutor{..} = do
|
delExecutor LdapExecutor{..} = do
|
||||||
atomically . void $ tryPutTMVar ldapDestroy ()
|
atomically . void $ tryPutTMVar ldapDestroy ()
|
||||||
wait ldapAsync
|
wait ldapAsync
|
||||||
rSt <- view _2 <$> Resource.allocate Resource.createInternalState Resource.closeInternalState
|
withRunInIO $ \runInIO ->
|
||||||
liftIO $ createPool (mkExecutor rSt) delExecutor stripes timeoutConn limit
|
createPool (runInIO mkExecutor) delExecutor stripes timeoutConn limit
|
||||||
where
|
where
|
||||||
withTimeout :: forall m a. (MonadUnliftIO m, MonadThrow m) => m a -> m a
|
withTimeout :: forall m' a. (MonadUnliftIO m', MonadThrow m') => m' a -> m' a
|
||||||
withTimeout = maybe (throwM LdapPoolTimeout) return <=< timeout timeoutAct
|
withTimeout = maybe (throwM LdapPoolTimeout) return <=< timeout timeoutAct
|
||||||
|
|
||||||
|
convertErrors =
|
||||||
|
[ Handler $ \LineTooLong -> throwM LdapLineTooLong
|
||||||
|
, Handler $ \(HostNotResolved h) -> throwM $ LdapHostNotResolved h
|
||||||
|
, Handler $ \(HostCannotConnect h es) -> throwM $ LdapHostCannotConnect h es
|
||||||
|
]
|
||||||
|
|||||||
@ -85,13 +85,14 @@ withFailover f@Failover{..} mode detAcceptable act = do
|
|||||||
| otherwise -> throwM err
|
| otherwise -> throwM err
|
||||||
|
|
||||||
case (res', mode) of
|
case (res', mode) of
|
||||||
(Left err, FailoverUnlimited)
|
(Left err , FailoverUnlimited)
|
||||||
-> doRetry err
|
-> doRetry err
|
||||||
(Left err, FailoverLimited n)
|
(Left err , FailoverLimited n) | n > 0
|
||||||
| n > 0
|
-> doRetry err
|
||||||
-> doRetry err
|
(Left err , _)
|
||||||
_other
|
-> void recordFailure >> throwM err
|
||||||
-> void recordFailure >> either throwM return res'
|
(Right res'', _)
|
||||||
|
-> return res''
|
||||||
|
|
||||||
withFailoverReTest :: ( MonadIO m, MonadCatch m
|
withFailoverReTest :: ( MonadIO m, MonadCatch m
|
||||||
, Exception e
|
, Exception e
|
||||||
|
|||||||
Reference in New Issue
Block a user