feat(ldap): expose active directory errors
This commit is contained in:
parent
8eedfdd4c1
commit
51ed7e0a26
@ -2881,3 +2881,15 @@ SystemFaculty: Fakultätsmitglied
|
|||||||
|
|
||||||
ChangelogItemFeature: Feature
|
ChangelogItemFeature: Feature
|
||||||
ChangelogItemBugfix: Bugfix
|
ChangelogItemBugfix: Bugfix
|
||||||
|
|
||||||
|
InvalidCredentialsADNoSuchObject: Benutzereintrag existiert nicht
|
||||||
|
InvalidCredentialsADLogonFailure: Ungültiges Passwort
|
||||||
|
InvalidCredentialsADAccountRestriction: Kontobeschränkungen verhindern Login
|
||||||
|
InvalidCredentialsADInvalidLogonHours: Benutzer darf sich zur aktuellen Tageszeit nicht anmelden
|
||||||
|
InvalidCredentialsADInvalidWorkstation: Benutzer darf sich von diesem System aus nicht anmelden
|
||||||
|
InvalidCredentialsADPasswordExpired: Passwort abgelaufen
|
||||||
|
InvalidCredentialsADAccountDisabled: Benutzereintrag gesperrt
|
||||||
|
InvalidCredentialsADTooManyContextIds: Benutzereintrag trägt zu viele Sicherheitskennzeichen
|
||||||
|
InvalidCredentialsADAccountExpired: Benutzereintrag abgelaufen
|
||||||
|
InvalidCredentialsADPasswordMustChange: Passwort muss geändert werden
|
||||||
|
InvalidCredentialsADAccountLockedOut: Benutzereintrag wurde durch Eindringlingserkennung gesperrt
|
||||||
@ -2883,3 +2883,15 @@ SystemFaculty: Faculty member
|
|||||||
|
|
||||||
ChangelogItemFeature: Feature
|
ChangelogItemFeature: Feature
|
||||||
ChangelogItemBugfix: Bugfix
|
ChangelogItemBugfix: Bugfix
|
||||||
|
|
||||||
|
InvalidCredentialsADNoSuchObject: User entry does not exist
|
||||||
|
InvalidCredentialsADLogonFailure: Invalid passwod
|
||||||
|
InvalidCredentialsADAccountRestriction: Account restrictions are preventing login
|
||||||
|
InvalidCredentialsADInvalidLogonHours: User may not login at the current time of day
|
||||||
|
InvalidCredentialsADInvalidWorkstation: User may not login from this system
|
||||||
|
InvalidCredentialsADPasswordExpired: Password expired
|
||||||
|
InvalidCredentialsADAccountDisabled: Account disabled
|
||||||
|
InvalidCredentialsADTooManyContextIds: Account carries to many security identifiers
|
||||||
|
InvalidCredentialsADAccountExpired: Account expired
|
||||||
|
InvalidCredentialsADPasswordMustChange: Password needs to be changed
|
||||||
|
InvalidCredentialsADAccountLockedOut: Account disabled by intruder detection
|
||||||
|
|||||||
@ -70,6 +70,7 @@ dependencies:
|
|||||||
- blaze-html
|
- blaze-html
|
||||||
- conduit-resumablesink >=0.2
|
- conduit-resumablesink >=0.2
|
||||||
- parsec
|
- parsec
|
||||||
|
- parsec-numbers
|
||||||
- attoparsec
|
- attoparsec
|
||||||
- uuid
|
- uuid
|
||||||
- exceptions
|
- exceptions
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
module Auth.LDAP
|
module Auth.LDAP
|
||||||
( apLdap
|
( apLdap
|
||||||
|
, ADError(..), ADInvalidCredentials(..)
|
||||||
, campusLogin
|
, campusLogin
|
||||||
, CampusUserException(..)
|
, CampusUserException(..)
|
||||||
, campusUser, campusUser'
|
, campusUser, campusUser'
|
||||||
@ -26,6 +27,8 @@ import qualified Data.Text.Encoding as Text
|
|||||||
|
|
||||||
import qualified Yesod.Auth.Message as Msg
|
import qualified Yesod.Auth.Message as Msg
|
||||||
|
|
||||||
|
import Auth.LDAP.AD
|
||||||
|
|
||||||
|
|
||||||
data CampusLogin = CampusLogin
|
data CampusLogin = CampusLogin
|
||||||
{ campusIdent :: CI Text
|
{ campusIdent :: CI Text
|
||||||
@ -155,6 +158,13 @@ campusUserMatr' pool mode
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
newtype ADInvalidCredentials = ADInvalidCredentials ADError
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
|
isUnusualADError :: ADError -> Bool
|
||||||
|
isUnusualADError = flip notElem [ADNoSuchObject, ADLogonFailure]
|
||||||
|
|
||||||
|
|
||||||
campusForm :: ( RenderMessage (HandlerSite m) FormMessage
|
campusForm :: ( RenderMessage (HandlerSite m) FormMessage
|
||||||
, RenderMessage (HandlerSite m) (ValueRequired (HandlerSite m))
|
, RenderMessage (HandlerSite m) (ValueRequired (HandlerSite m))
|
||||||
, RenderMessage (HandlerSite m) CampusMessage
|
, RenderMessage (HandlerSite m) CampusMessage
|
||||||
@ -174,6 +184,7 @@ campusLogin :: forall site.
|
|||||||
, RenderMessage site CampusMessage
|
, RenderMessage site CampusMessage
|
||||||
, RenderMessage site AFormMessage
|
, RenderMessage site AFormMessage
|
||||||
, RenderMessage site (ValueRequired site)
|
, RenderMessage site (ValueRequired site)
|
||||||
|
, RenderMessage site ADInvalidCredentials
|
||||||
, Button site ButtonSubmit
|
, Button site ButtonSubmit
|
||||||
) => Failover (LdapConf, LdapPool) -> FailoverMode -> AuthPlugin site
|
) => Failover (LdapConf, LdapPool) -> FailoverMode -> AuthPlugin site
|
||||||
campusLogin pool mode = AuthPlugin{..}
|
campusLogin pool mode = AuthPlugin{..}
|
||||||
@ -203,6 +214,14 @@ campusLogin pool mode = AuthPlugin{..}
|
|||||||
$logErrorS apName $ "Error during login: " <> tshow err
|
$logErrorS apName $ "Error during login: " <> tshow err
|
||||||
observeLoginOutcome apName LoginError
|
observeLoginOutcome apName LoginError
|
||||||
loginErrorMessageI LoginR Msg.AuthError
|
loginErrorMessageI LoginR Msg.AuthError
|
||||||
|
Right (Left (Ldap.ResponseErrorCode _ errCode _ errTxt))
|
||||||
|
| Right adError <- parseADError errCode errTxt
|
||||||
|
, isUnusualADError adError -> do
|
||||||
|
$logInfoS apName [st|#{campusIdent}: #{toPathPiece adError}|]
|
||||||
|
observeLoginOutcome apName LoginADInvalidCredentials
|
||||||
|
setSessionJson SessionError . InternalError $ toPathPiece adError
|
||||||
|
MsgRenderer mr <- liftHandler getMsgRenderer
|
||||||
|
loginErrorMessage (tp LoginR) . mr $ ADInvalidCredentials adError
|
||||||
Right (Left bindErr) -> do
|
Right (Left bindErr) -> do
|
||||||
case bindErr of
|
case bindErr of
|
||||||
Ldap.ResponseErrorCode _ _ _ errTxt ->
|
Ldap.ResponseErrorCode _ _ _ errTxt ->
|
||||||
|
|||||||
76
src/Auth/LDAP/AD.hs
Normal file
76
src/Auth/LDAP/AD.hs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
module Auth.LDAP.AD
|
||||||
|
( ADError(..)
|
||||||
|
, parseADError
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import.NoFoundation hiding (try)
|
||||||
|
|
||||||
|
import Model.Types.TH.PathPiece
|
||||||
|
|
||||||
|
import qualified Data.IntMap.Strict as IntMap
|
||||||
|
import qualified Data.Map.Strict as Map
|
||||||
|
|
||||||
|
import Text.Parsec hiding ((<|>))
|
||||||
|
import Text.Parsec.String
|
||||||
|
import Text.ParserCombinators.Parsec.Number (hexnum)
|
||||||
|
|
||||||
|
import Ldap.Client (ResultCode(..))
|
||||||
|
|
||||||
|
|
||||||
|
-- | Copied from <https://ldapwiki.com/wiki/Common%20Active%20Directory%20Bind%20Errors>
|
||||||
|
data ADError
|
||||||
|
= ADNoSuchObject
|
||||||
|
| ADLogonFailure
|
||||||
|
| ADAccountRestriction
|
||||||
|
| ADInvalidLogonHours
|
||||||
|
| ADInvalidWorkstation
|
||||||
|
| ADPasswordExpired
|
||||||
|
| ADAccountDisabled
|
||||||
|
| ADTooManyContextIds
|
||||||
|
| ADAccountExpired
|
||||||
|
| ADPasswordMustChange
|
||||||
|
| ADAccountLockedOut
|
||||||
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
nullaryPathPiece ''ADError $ camelToPathPiece' 1
|
||||||
|
pathPieceJSON ''ADError
|
||||||
|
pathPieceJSONKey ''ADError
|
||||||
|
derivePersistFieldPathPiece ''ADError
|
||||||
|
|
||||||
|
|
||||||
|
fromADErrorCode :: ResultCode -> Word32 -> Maybe ADError
|
||||||
|
fromADErrorCode resCode subResCode = IntMap.lookup (fromIntegral subResCode) =<< Map.lookup resCode errorCodes
|
||||||
|
where
|
||||||
|
errorCodes = Map.fromList
|
||||||
|
[ ( InvalidCredentials
|
||||||
|
, IntMap.fromList
|
||||||
|
[ ( 0x525, ADNoSuchObject )
|
||||||
|
, ( 0x52e, ADLogonFailure )
|
||||||
|
, ( 0x52f, ADAccountRestriction )
|
||||||
|
, ( 0x530, ADInvalidLogonHours )
|
||||||
|
, ( 0x531, ADInvalidWorkstation )
|
||||||
|
, ( 0x532, ADPasswordExpired )
|
||||||
|
, ( 0x533, ADAccountDisabled )
|
||||||
|
, ( 0x568, ADTooManyContextIds )
|
||||||
|
, ( 0x701, ADAccountExpired )
|
||||||
|
, ( 0x773, ADPasswordMustChange )
|
||||||
|
, ( 0x775, ADAccountLockedOut )
|
||||||
|
, ( 0x80090346, ADAccountLockedOut )
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
parseADError :: ResultCode -> Text -> Either ParseError ADError
|
||||||
|
parseADError resCode = parse (pADError resCode <* eof) "LDAP" . unpack
|
||||||
|
|
||||||
|
pADError :: ResultCode -> Parser ADError
|
||||||
|
pADError resCode = do
|
||||||
|
void . manyTill anyChar . try $ string ": "
|
||||||
|
let pItem = asum
|
||||||
|
[ do
|
||||||
|
void $ string "data "
|
||||||
|
fmap Just $ hexnum >>= hoistMaybe . fromADErrorCode resCode
|
||||||
|
, Nothing <$ manyTill anyChar (lookAhead . try $ void (string ", ") <|> eof)
|
||||||
|
]
|
||||||
|
(hoistMaybe =<<) $ ala First foldMap <$> pItem `sepBy1` try (string ", ")
|
||||||
@ -231,6 +231,8 @@ embedRenderMessage ''UniWorX ''AuthenticationMode id
|
|||||||
|
|
||||||
embedRenderMessage ''UniWorX ''RatingValidityException id
|
embedRenderMessage ''UniWorX ''RatingValidityException id
|
||||||
|
|
||||||
|
embedRenderMessageVariant ''UniWorX ''ADInvalidCredentials ("InvalidCredentials" <>)
|
||||||
|
|
||||||
newtype ShortSex = ShortSex Sex
|
newtype ShortSex = ShortSex Sex
|
||||||
embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>)
|
embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>)
|
||||||
|
|
||||||
|
|||||||
@ -175,6 +175,7 @@ import Data.Word.Word24.Instances as Import ()
|
|||||||
import Control.Monad.Trans.Memo.StateCache.Instances as Import (hoistStateCache)
|
import Control.Monad.Trans.Memo.StateCache.Instances as Import (hoistStateCache)
|
||||||
import Database.Persist.Sql.Types.Instances as Import ()
|
import Database.Persist.Sql.Types.Instances as Import ()
|
||||||
import Control.Monad.Catch.Instances as Import ()
|
import Control.Monad.Catch.Instances as Import ()
|
||||||
|
import Ldap.Client.Instances as Import ()
|
||||||
|
|
||||||
import Crypto.Hash as Import (Digest, SHA3_256, SHA3_512)
|
import Crypto.Hash as Import (Digest, SHA3_256, SHA3_512)
|
||||||
import Crypto.Random as Import (ChaChaDRG, Seed)
|
import Crypto.Random as Import (ChaChaDRG, Seed)
|
||||||
|
|||||||
11
src/Ldap/Client/Instances.hs
Normal file
11
src/Ldap/Client/Instances.hs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Ldap.Client.Instances
|
||||||
|
(
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Ldap.Client
|
||||||
|
|
||||||
|
|
||||||
|
deriving instance Ord ResultCode
|
||||||
@ -305,6 +305,7 @@ observeFavouritesQuickActionsDuration act = do
|
|||||||
data LoginOutcome
|
data LoginOutcome
|
||||||
= LoginSuccessful
|
= LoginSuccessful
|
||||||
| LoginInvalidCredentials
|
| LoginInvalidCredentials
|
||||||
|
| LoginADInvalidCredentials
|
||||||
| LoginError
|
| LoginError
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
deriving anyclass (Universe, Finite)
|
deriving anyclass (Universe, Finite)
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
$newline never
|
||||||
|
Bessere Fehlermeldungen bei fehlgeschlagenem Login
|
||||||
2
templates/i18n/changelog/ldap-ad-errors.en-eu.hamlet
Normal file
2
templates/i18n/changelog/ldap-ad-errors.en-eu.hamlet
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
$newline never
|
||||||
|
Better error messages on failed login
|
||||||
18
test/Auth/LDAP/ADSpec.hs
Normal file
18
test/Auth/LDAP/ADSpec.hs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Auth.LDAP.ADSpec where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
|
||||||
|
import Auth.LDAP.AD
|
||||||
|
import Ldap.Client
|
||||||
|
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
describe "parseADError" $ do
|
||||||
|
it "parses some examples" . mapM_ exampleEntry $
|
||||||
|
[ ( InvalidCredentials, ADAccountDisabled, "80090308: LdapErr: DSID-0C090446, comment: AcceptSecurityContext error, data 533, v2580")
|
||||||
|
, ( InvalidCredentials, ADLogonFailure , "80090308: LdapErr: DSID-0C090446, comment: AcceptSecurityContext error, data 52e, v2580")
|
||||||
|
]
|
||||||
|
|
||||||
|
exampleEntry :: ( ResultCode, ADError, Text ) -> Expectation
|
||||||
|
exampleEntry ( resCode, adError, errMsg ) = example $ parseADError resCode errMsg `shouldBe` Right adError
|
||||||
Reference in New Issue
Block a user