module Handler.Utils.Users ( computeUserAuthenticationDigest , Digest, SHA3_256 , constEq , GuessUserInfo(..) , guessUser ) where import Import import Auth.LDAP (campusUserMatr') import Crypto.Hash (Digest, SHA3_256, hashlazy) import Data.ByteArray (constEq) import qualified Data.Aeson as JSON import qualified Data.Set as Set import qualified Data.CaseInsensitive as CI import qualified Database.Esqueleto as E import qualified Database.Esqueleto.Utils as E computeUserAuthenticationDigest :: AuthenticationMode -> Digest SHA3_256 computeUserAuthenticationDigest = hashlazy . JSON.encode data GuessUserInfo = GuessUserMatrikelnummer { guessUserMatrikelnummer :: UserMatriculation } | GuessUserDisplayName { guessUserDisplayName :: UserDisplayName } | GuessUserSurname { guessUserSurname :: UserSurname } | GuessUserFirstName { guessUserFirstName :: UserFirstName } deriving (Eq, Ord, Read, Show, Generic, Typeable) instance Binary GuessUserInfo makeLenses_ ''GuessUserInfo guessUser :: Set GuessUserInfo -> DB (Maybe UserId) guessUser (Set.toList -> criteria) = $cachedHereBinary criteria $ go False where toSql user = \case GuessUserMatrikelnummer userMatriculation' -> user E.^. UserMatrikelnummer E.==. E.val (Just userMatriculation') GuessUserDisplayName userDisplayName' -> user E.^. UserDisplayName `E.hasInfix` E.val userDisplayName' GuessUserSurname userSurname' -> user E.^. UserSurname `E.hasInfix` E.val userSurname' GuessUserFirstName userFirstName' -> user E.^. UserFirstName `E.hasInfix` E.val userFirstName' go didLdap = do let retrieveUsers = E.select . E.from $ \user -> do E.where_ . E.and $ map (toSql user) criteria return user users <- retrieveUsers let users' = sortBy (flip closeness) users matchesMatriculation :: Entity User -> Maybe Bool matchesMatriculation = preview $ _entityVal . _userMatrikelnummer . to (\userMatr -> all ((== userMatr) . Just) $ criteria ^.. folded . _guessUserMatrikelnummer) closeness :: Entity User -> Entity User -> Ordering closeness = mconcat $ concat [ pure $ comparing (fmap Down . matchesMatriculation) , (criteria ^.. folded . _guessUserSurname) <&> \surn -> comparing (preview $ _entityVal . _userSurname . to CI.mk . only (CI.mk surn)) , (criteria ^.. folded . _guessUserFirstName) <&> \firstn -> comparing (preview $ _entityVal . _userFirstName . to CI.mk . only (CI.mk firstn)) , (criteria ^.. folded . _guessUserDisplayName) <&> \dispn -> comparing (preview $ _entityVal . _userDisplayName . to CI.mk . only (CI.mk dispn)) ] doLdap userMatr = do app <- getYesod let ldap = (,) <$> app ^. _appLdapConf <*> app ^. _appLdapPool fmap (fmap entityKey . join) . for ldap $ \(ldapConf, ldapPool) -> do ldapData <- campusUserMatr' ldapConf ldapPool userMatr for ldapData $ upsertCampusUser UpsertCampusUser case users' of x@(Entity pid _) : xs | [] <- xs , fromMaybe False (matchesMatriculation x) || didLdap -> return $ Just pid | x' : _ <- xs , fromMaybe False (matchesMatriculation x) || didLdap , GT <- x `closeness` x' -> return $ Just pid | not didLdap , userMatr : userMatrs' <- criteria ^.. folded . _guessUserMatrikelnummer , all (== userMatr) userMatrs' -> doLdap userMatr >>= maybe (go True) (return . Just) _other -> return Nothing