chore(model): use more specific (new)types for ldap model

This commit is contained in:
Sarah Vaupel 2024-02-13 22:44:30 +01:00
parent 1180ef6fd0
commit 7ed5e7a326
6 changed files with 84 additions and 37 deletions

View File

@ -18,15 +18,15 @@ AuthSourceAzure
AuthSourceLdap AuthSourceLdap
host Text -- ^ LDAP host destination to connect to host Text -- ^ LDAP host destination to connect to
-- TODO: switch to url type -- TODO: switch to url type
port Natural -- ^ Port of the LDAP service to connect to port Word16 -- ^ Port of the LDAP service to connect to
-- TODO: is there a port type? Maybe merge with host and make primary key? -- TODO: Maybe merge with host and make primary key?
tls Bool -- ^ Whether to connect to the host via TLS tls Bool -- ^ Whether to connect to the host via TLS
user Text -- ^ User used for queries user LdapDn -- ^ User used for queries
pass Text -- ^ Password used for queries pass LdapPass -- ^ Password used for queries
baseDn Text base LdapDn -- ^ TODO documentation needed
scope LdapScope scope LdapScope -- ^ TODO documentation needed
timeout Natural -- ^ Query timeout in milliseconds timeout NominalDiffTime -- ^ Query timeout
searchTimeout Natural -- ^ Search query timeout in milliseconds searchTimeout Int32 -- ^ Search query timeout -- TODO: why not NominalDiffTime??
UniqueAuthSourceLdap host port -- TODO rethink! UniqueAuthSourceLdap host port -- TODO rethink!
deriving Show Eq Ord Generic deriving Show Eq Ord Generic

View File

@ -65,7 +65,7 @@ findUser :: LdapConf
-> Text -- ^ needle -> Text -- ^ needle
-> [Ldap.Attr] -> [Ldap.Attr]
-> IO [Ldap.SearchEntry] -> IO [Ldap.SearchEntry]
findUser conf@LdapConf{..} ldap ident retAttrs = fromMaybe [] <$> findM (assertM (not . null) . lift . flip (Ldap.search ldap ldapBase $ userSearchSettings conf) retAttrs) userFilters findUser conf@LdapConf{..} ldap ident retAttrs = fromMaybe [] <$> findM (assertM (not . null) . lift . flip (Ldap.search ldap ldapConfBase $ userSearchSettings conf) retAttrs) userFilters
where where
userFilters = userFilters =
[ ldapUserPrincipalName Ldap.:= Text.encodeUtf8 ident [ ldapUserPrincipalName Ldap.:= Text.encodeUtf8 ident
@ -85,7 +85,7 @@ findUserMatr :: LdapConf
-> Text -- ^ matriculation needle -> Text -- ^ matriculation needle
-> [Ldap.Attr] -> [Ldap.Attr]
-> IO [Ldap.SearchEntry] -> IO [Ldap.SearchEntry]
findUserMatr conf@LdapConf{..} ldap userMatr retAttrs = fromMaybe [] <$> findM (assertM (not . null) . lift . flip (Ldap.search ldap ldapBase $ userSearchSettings conf) retAttrs) userFilters findUserMatr conf@LdapConf{..} ldap userMatr retAttrs = fromMaybe [] <$> findM (assertM (not . null) . lift . flip (Ldap.search ldap ldapConfBase $ userSearchSettings conf) retAttrs) userFilters
where where
userFilters = userFilters =
[ ldapUserFraportPersonalnummer Ldap.:= Text.encodeUtf8 userMatr [ ldapUserFraportPersonalnummer Ldap.:= Text.encodeUtf8 userMatr
@ -94,9 +94,9 @@ findUserMatr conf@LdapConf{..} ldap userMatr retAttrs = fromMaybe [] <$> findM (
userSearchSettings :: LdapConf userSearchSettings :: LdapConf
-> Ldap.Mod Ldap.Search -> Ldap.Mod Ldap.Search
userSearchSettings LdapConf{..} = mconcat userSearchSettings LdapConf{..} = mconcat
[ Ldap.scope ldapScope [ Ldap.scope ldapConfScope
, Ldap.size 2 , Ldap.size 2
, Ldap.time ldapSearchTimeout , Ldap.time ldapConfSearchTimeout
, Ldap.derefAliases Ldap.DerefAlways , Ldap.derefAliases Ldap.DerefAlways
] ]
@ -147,7 +147,7 @@ ldapUserWith :: ( MonadUnliftIO m
-> Creds site -> Creds site
-> m (Either LdapUserException (Ldap.AttrList [])) -> m (Either LdapUserException (Ldap.AttrList []))
ldapUserWith withLdap' (conf@LdapConf{..}, pool) Creds{..} = either (throwM . LdapUserLdapError) return <=< withLdap' pool $ \ldap -> liftIO . runExceptT $ do ldapUserWith withLdap' (conf@LdapConf{..}, pool) Creds{..} = either (throwM . LdapUserLdapError) return <=< withLdap' pool $ \ldap -> liftIO . runExceptT $ do
lift $ Ldap.bind ldap ldapDn ldapPassword lift $ Ldap.bind ldap ldapConfDn ldapConfPassword
results <- case lookup "DN" credsExtra of results <- case lookup "DN" credsExtra of
Just userDN -> do Just userDN -> do
let userFilter = Ldap.Present ldapUserPrincipalName let userFilter = Ldap.Present ldapUserPrincipalName
@ -224,7 +224,7 @@ ldapUserMatr :: ( MonadUnliftIO m
-> UserMatriculation -> UserMatriculation
-> m (Ldap.AttrList []) -> m (Ldap.AttrList [])
ldapUserMatr (conf@LdapConf{..}, pool) userMatr = either (throwM . LdapUserLdapError) return <=< withLdap pool $ \ldap -> liftIO $ do ldapUserMatr (conf@LdapConf{..}, pool) userMatr = either (throwM . LdapUserLdapError) return <=< withLdap pool $ \ldap -> liftIO $ do
Ldap.bind ldap ldapDn ldapPassword Ldap.bind ldap ldapConfDn ldapConfPassword
results <- findUserMatr conf ldap userMatr [] results <- findUserMatr conf ldap userMatr []
case results of case results of
[] -> throwM LdapUserNoResult [] -> throwM LdapUserNoResult
@ -286,7 +286,7 @@ ldapLogin conf@LdapConf{..} pool = AuthPlugin{..}
resp <- formResultMaybe loginRes $ \CampusLogin{ campusIdent = CI.original -> campusIdent, ..} -> Just <$> do resp <- formResultMaybe loginRes $ \CampusLogin{ campusIdent = CI.original -> campusIdent, ..} -> Just <$> do
ldapResult <- withLdap pool $ \ldap -> liftIO $ do ldapResult <- withLdap pool $ \ldap -> liftIO $ do
Ldap.bind ldap ldapDn ldapPassword Ldap.bind ldap ldapConfDn ldapConfPassword
searchResults <- findUser conf ldap campusIdent [ldapUserPrincipalName] searchResults <- findUser conf ldap campusIdent [ldapUserPrincipalName]
case searchResults of case searchResults of
[Ldap.SearchEntry (Ldap.Dn userDN) userAttrs] [Ldap.SearchEntry (Ldap.Dn userDN) userAttrs]

View File

@ -252,6 +252,7 @@ import Data.Encoding.UTF8 as Import (UTF8(UTF8))
import GHC.TypeLits as Import (KnownSymbol) import GHC.TypeLits as Import (KnownSymbol)
import Data.Word as Import (Word16)
import Data.Word.Word24 as Import import Data.Word.Word24 as Import
import Data.Kind as Import (Type, Constraint) import Data.Kind as Import (Type, Constraint)

View File

@ -43,15 +43,54 @@ import Data.Universe
import Data.Universe.Instances.Reverse () import Data.Universe.Instances.Reverse ()
import Data.Universe.Instances.Reverse.MonoTraversable () import Data.Universe.Instances.Reverse.MonoTraversable ()
import qualified Database.Esqueleto.Legacy as E
import Database.Persist.Sql import Database.Persist.Sql
import qualified Ldap.Client as Ldap
import Ldap.Client.Instances ()
import Servant.Docs (ToSample(..), samples) import Servant.Docs (ToSample(..), samples)
type AzureScopes = Set Text
-- Note: Ldap.Host also stores TLS settings, which we will generate ad-hoc based on AuthSourceLdapTls field instead. We therefore use Text to store the hostname only
-- newtype LdapHost = LdapHost { ldapHost :: Text }
-- deriving (Eq, Ord, Read, Show, Generic, Data)
-- deriving newtype (NFData, PathPiece, PersistField, PersistFieldSql)
-- instance E.SqlString LdapHost
-- makeLenses_ ''LdapHost
-- Note: Ldap.PortNumber comes from Network.Socket, which does not export the constructor of the newtype. Hence, no Data and Generic instances can be derived. But PortNumber is a member of Num, so we will use Word16 instead (Word16 is also used for storing the port number inside PortNumber)
-- newtype LdapPort = LdapPort { ldapPort :: Word16 }
-- deriving (Eq, Ord, Read, Show, Generic, Data)
-- deriving newtype (NFData, PathPiece, PersistField, PersistFieldSql)
-- instance E.SqlString LdapPort
-- makeLenses_ ''LdapPort
newtype LdapPass = LdapPass { ldapPass :: Ldap.Password }
deriving (Eq, Ord, Read, Show, Generic, Data)
deriving newtype (NFData, PersistField, PersistFieldSql)
instance E.SqlString LdapPass
makeLenses_ ''LdapPass
newtype LdapDn = LdapDn { ldapDn :: Ldap.Dn }
deriving (Eq, Ord, Read, Show, Generic, Data)
deriving newtype (NFData, PathPiece, PersistField, PersistFieldSql)
instance E.SqlString LdapDn
makeLenses_ ''LdapDn
newtype LdapScope = LdapScope { ldapScope :: Ldap.Scope }
deriving (Eq, Ord, Read, Show, Generic, Data)
deriving newtype (NFData, PathPiece, PersistField, PersistFieldSql)
instance E.SqlString LdapScope
makeLenses_ ''LdapScope
type UserEduPersonPrincipalName = Text type UserEduPersonPrincipalName = Text
-- | Supported protocols for external user sources used for authentication queries -- | Supported protocols for external user sources used for authentication queries
-- TODO: deprecated, delete
data AuthenticationProtocol data AuthenticationProtocol
= AuthAzure -- ^ Azure ADv2 (OAuth2) = AuthAzure -- ^ Azure ADv2 (OAuth2)
| AuthLdap -- ^ LDAP | AuthLdap -- ^ LDAP
@ -62,9 +101,14 @@ nullaryPathPiece ''AuthenticationProtocol $ camelToPathPiece' 1
pathPieceJSON ''AuthenticationProtocol pathPieceJSON ''AuthenticationProtocol
-- TODO: delete once identification using model table is implemented
type AuthenticationSourceIdent = Text type AuthenticationSourceIdent = Text
-------------------
----- AuthTag -----
-------------------
data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prädikate sind sortier nach Relevanz für Benutzer data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prädikate sind sortier nach Relevanz für Benutzer
= AuthAdmin = AuthAdmin
| AuthLecturer | AuthLecturer
@ -173,6 +217,10 @@ _ReducedActiveAuthTags = iso toReducedActiveAuthTags fromReducedActiveAuthTags
fromReducedActiveAuthTags (ReducedActiveAuthTags hm) = AuthTagActive $ \n -> fromMaybe (authTagIsActive def n) $ HashMap.lookup n hm fromReducedActiveAuthTags (ReducedActiveAuthTags hm) = AuthTagActive $ \n -> fromMaybe (authTagIsActive def n) $ HashMap.lookup n hm
-------------------
----- PredDNF -----
-------------------
data PredLiteral a = PLVariable { plVar :: a } | PLNegated { plVar :: a } data PredLiteral a = PLVariable { plVar :: a } | PLNegated { plVar :: a }
deriving (Eq, Ord, Read, Show, Data, Generic) deriving (Eq, Ord, Read, Show, Data, Generic)
deriving anyclass (Hashable, Binary, NFData) deriving anyclass (Hashable, Binary, NFData)
@ -214,7 +262,6 @@ parsePredDNF start = fmap (PredDNF . Set.mapMonotonic impureNonNull) . ofoldM pa
| otherwise | otherwise
= Left t = Left t
$(return []) $(return [])
instance ToJSON a => ToJSON (PredDNF a) where instance ToJSON a => ToJSON (PredDNF a) where

View File

@ -44,7 +44,6 @@ import Language.Haskell.TH.Syntax (Exp, Q)
import qualified Yesod.Auth.Util.PasswordStore as PWStore import qualified Yesod.Auth.Util.PasswordStore as PWStore
import qualified Data.Scientific as Scientific import qualified Data.Scientific as Scientific
import Data.Word (Word16)
import qualified Data.Text as Text import qualified Data.Text as Text

View File

@ -6,7 +6,7 @@
module Settings.Ldap module Settings.Ldap
( LdapConf(..) ( LdapConf(..)
, _ldapHost, _ldapDn, _ldapBase, _ldapScope, _ldapTimeout, _ldapSearchTimeout , _ldapConfHost, _ldapConfDn, _ldapConfBase, _ldapConfScope, _ldapConfTimeout, _ldapConfSearchTimeout
) where ) where
import ClassyPrelude import ClassyPrelude
@ -24,24 +24,24 @@ import qualified Ldap.Client as Ldap
data LdapConf = LdapConf data LdapConf = LdapConf
{ ldapHost :: Ldap.Host { ldapConfHost :: Ldap.Host
, ldapPort :: Ldap.PortNumber , ldapConfPort :: Ldap.PortNumber
, ldapDn :: Ldap.Dn , ldapConfDn :: Ldap.Dn
, ldapPassword :: Ldap.Password , ldapConfPassword :: Ldap.Password
, ldapBase :: Ldap.Dn , ldapConfBase :: Ldap.Dn
, ldapScope :: Ldap.Scope , ldapConfScope :: Ldap.Scope
, ldapTimeout :: NominalDiffTime , ldapConfTimeout :: NominalDiffTime
, ldapSearchTimeout :: Int32 , ldapConfSearchTimeout :: Int32
} deriving (Show) } deriving (Show)
makeLenses_ ''LdapConf makeLenses_ ''LdapConf
deriveFromJSON defaultOptions ''Ldap.Scope deriveFromJSON defaultOptions ''Ldap.Scope -- TODO: move to Ldap.Client.Instances
instance FromJSON LdapConf where instance FromJSON LdapConf where
parseJSON = withObject "LdapConf" $ \o -> do parseJSON = withObject "LdapConf" $ \o -> do
ldapTls <- o .:? "tls" ldapConfTls <- o .:? "tls"
tlsSettings <- case ldapTls :: Maybe String of tlsSettings <- case ldapConfTls :: Maybe String of
Just spec Just spec
| spec == "insecure" -> return $ Just Ldap.insecureTlsSettings | spec == "insecure" -> return $ Just Ldap.insecureTlsSettings
| spec == "default" -> return $ Just Ldap.defaultTlsSettings | spec == "default" -> return $ Just Ldap.defaultTlsSettings
@ -50,12 +50,12 @@ instance FromJSON LdapConf where
| null spec -> return Nothing | null spec -> return Nothing
Nothing -> return Nothing Nothing -> return Nothing
_otherwise -> fail "Could not parse LDAP TLSSettings" _otherwise -> fail "Could not parse LDAP TLSSettings"
ldapHost <- maybe Ldap.Plain (flip Ldap.Tls) tlsSettings <$> o .:? "host" .!= "" ldapConfHost <- maybe Ldap.Plain (flip Ldap.Tls) tlsSettings <$> o .:? "host" .!= ""
ldapPort <- (fromIntegral :: Int -> Ldap.PortNumber) <$> o .: "port" ldapConfPort <- (fromIntegral :: Int -> Ldap.PortNumber) <$> o .: "port"
ldapDn <- Ldap.Dn <$> o .:? "user" .!= "" ldapConfDn <- Ldap.Dn <$> o .:? "user" .!= ""
ldapPassword <- Ldap.Password . Text.encodeUtf8 <$> o .:? "pass" .!= "" ldapConfPassword <- Ldap.Password . Text.encodeUtf8 <$> o .:? "pass" .!= ""
ldapBase <- Ldap.Dn <$> o .:? "baseDN" .!= "" ldapConfBase <- Ldap.Dn <$> o .:? "baseDN" .!= ""
ldapScope <- o .: "scope" ldapConfScope <- o .: "scope"
ldapTimeout <- o .: "timeout" ldapConfTimeout <- o .: "timeout"
ldapSearchTimeout <- o .: "search-timeout" ldapConfSearchTimeout <- o .: "search-timeout"
return LdapConf{..} return LdapConf{..}