diff --git a/models/users.model b/models/users.model index bba5b18a1..681e09055 100644 --- a/models/users.model +++ b/models/users.model @@ -13,13 +13,11 @@ -- User json -- Each Uni2work user has a corresponding row in this table; created upon first login. + ident UserIdent -- Case-insensitive user-identifier surname UserSurname -- Display user names always through 'nameWidget displayName surname' displayName UserDisplayName displayEmail UserEmail email UserEmail -- Case-insensitive eMail address, used for sending TODO: make this nullable - ident UserIdent -- Case-insensitive user-identifier - authentication AuthenticationMode -- 'AuthLDAP' or ('AuthPWHash'+password-hash) -- TODO: redo (add InternalUser table for password hash) - lastAuthentication UTCTime Maybe -- last login date created UTCTime default=now() tokensIssuedAfter UTCTime Maybe -- do not accept bearer tokens issued before this time (accept all tokens if null) matrikelnummer UserMatriculation Maybe -- usually a number; AVS Personalnummer; nicht Fraport Personalnummer! @@ -49,18 +47,33 @@ User json -- Each Uni2work user has a corresponding row in this table; create prefersPostal Bool default=false -- user prefers letters by post instead of email examOfficeGetSynced Bool default=true -- whether synced status should be displayed for exam results by default examOfficeGetLabels Bool default=true -- whether labels should be displayed for exam results by default - UniqueAuthentication ident -- Column 'ident' can be used as a row-key in this table + UniqueUser ident -- Column 'ident' can be used as a row-key in this table UniqueEmail email -- Column 'email' can be used as a row-key in this table deriving Show Eq Ord Generic -- Haskell-specific settings for runtime-value representing a row in memory --- User authentication data fetched from external sources -ExternalUser - source Text -- External source ID - ident UserIdent -- External user ID - data Value "default='{}'::jsonb" -- Raw user data from external source - lastSourceSync UTCTime -- When was the entry last synced with the external source? - lastUserSync UTCTime Maybe -- When was the corresponding User entry last synced with this entry? TODO: maybe move to User instead - UniqueExternalUser source ident +-- | User authentication data, source-agnostic data +UserAuth + ident UserIdent -- Human-readable text uniquely identifying a user + lastLogin UTCTime -- When did the corresponding User last authenticate using this entry? + Primary ident + UniqueAuthentication ident + deriving Show Eq Ord Generic + +-- | User authentication data fetched from external user sources +ExternalAuth + ident UserIdent + source AuthenticationSourceIdent -- Identifier of the external source in the config + data Value "default='{}'::jsonb" -- Raw user data from external source + lastSync UTCTime -- When was the corresponding User entry last synced with this external source? + UniqueExternalAuth ident source -- At most one entry of this user per source + deriving Show Eq Ord Generic + +-- | FraDrive-specific user authentication data, internal logins have precedence over external authentication +InternalAuth + ident UserIdent + hash Text -- Hashed password + Primary ident + UniqueInternalAuth ident deriving Show Eq Ord Generic UserFunction -- Administratively assigned functions (lecturer, admin, evaluation, ...) diff --git a/src/Model/Types/Auth.hs b/src/Model/Types/Auth.hs index d1e3900ff..3a9538ff9 100644 --- a/src/Model/Types/Auth.hs +++ b/src/Model/Types/Auth.hs @@ -15,61 +15,51 @@ module Model.Types.Auth import ClassyPrelude.Yesod hiding (derivePersistFieldJSON, Proxy(..)) -import Utils - -import Data.Aeson -import Data.Aeson.TH import Model.Types.TH.JSON -import Data.Universe -import Data.Universe.Instances.Reverse () -import Data.Proxy -import Data.Data (Data) +import Model.Types.TH.PathPiece + +import Utils +import Utils.Lens.TH import Control.Lens -import qualified Data.Set as Set - -import qualified Data.Text as Text - -import qualified Data.HashMap.Strict as HashMap - +import Data.Aeson +import Data.Aeson.TH import qualified Data.Aeson.Types as Aeson - -import Data.CaseInsensitive (CI) +import qualified Data.Binary as Binary +import Data.Binary (Binary) +import Data.Binary.Instances.UnorderedContainers () import qualified Data.CaseInsensitive as CI +import Data.CaseInsensitive (CI) import Data.CaseInsensitive.Instances () - -import Data.Set.Instances () +import Data.Data (Data) +import qualified Data.HashMap.Strict as HashMap import Data.NonNull.Instances () +import Data.Proxy +import qualified Data.Set as Set +import Data.Set.Instances () +import qualified Data.Text as Text +import Data.Universe +import Data.Universe.Instances.Reverse () import Data.Universe.Instances.Reverse.MonoTraversable () -import Model.Types.TH.PathPiece import Database.Persist.Sql import Servant.Docs (ToSample(..), samples) -import Utils.Lens.TH - -import Data.Binary (Binary) -import qualified Data.Binary as Binary -import Data.Binary.Instances.UnorderedContainers () -data AuthenticationMode = AuthLDAP - | AuthAzure - | AuthPWHash { authPWHash :: Text } - | AuthNoLogin - deriving (Eq, Ord, Read, Show, Generic) +-- | Supported protocols for external user sources used for authentication queries +data AuthenticationProtocol + = AuthAzure -- ^ Azure ADv2 (OAuth2) + | AuthLdap -- ^ LDAP + deriving (Eq, Ord, Enum, Bounded, Read, Show, Data, Generic) + deriving anyclass (Universe, Finite, Hashable, NFData) -instance Hashable AuthenticationMode -instance NFData AuthenticationMode +nullaryPathPiece ''AuthenticationProtocol $ camelToPathPiece' 1 +pathPieceJSON ''AuthenticationProtocol -deriveJSON defaultOptions - { constructorTagModifier = camelToPathPiece' 1 - , fieldLabelModifier = camelToPathPiece' 1 - , sumEncoding = UntaggedValue - } ''AuthenticationMode -derivePersistFieldJSON ''AuthenticationMode +type AuthenticationSourceIdent = Text data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prädikate sind sortier nach Relevanz für Benutzer @@ -106,8 +96,8 @@ data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prä | AuthRegisterGroup | AuthEmpty | AuthSelf - | AuthIsLDAP - | AuthIsPWHash + | AuthIsExternal -- TODO: maybe distinguish between AuthenticationProtocols + | AuthIsInternal | AuthAuthentication | AuthNoEscalation | AuthRead