Merge conflicts resolved

This commit is contained in:
SJost 2018-08-01 18:54:29 +02:00
commit 47d74577bc
23 changed files with 693 additions and 381 deletions

View File

@ -1,3 +1,7 @@
* Version 01.08.2018
Verbesserter Campus-Login
* Version 31.07.2018 * Version 31.07.2018
Viele Verbesserung zur Anzeige von Korrekturen Viele Verbesserung zur Anzeige von Korrekturen

View File

@ -20,14 +20,19 @@ stanzas:
ssl: true ssl: true
forward-env: forward-env:
- LDAPURI - LDAPHOST
- LDAPDN - LDAPTLS
- LDAPPW - LDAPPORT
- LDAPBN - LDAPUSER
- LDAPPASS
- LDAPBASE
- LDAPSCOPE
- LDAPTIMEOUT
- DUMMY_LOGIN - DUMMY_LOGIN
- DETAILED_LOGGING - DETAILED_LOGGING
- LOG_ALL - LOG_ALL
- PWFILE - PWFILE
- CRYPTOID_KEYFILE
# Use the following to automatically copy your bundle upon creation via `yesod # Use the following to automatically copy your bundle upon creation via `yesod
# keter`. Uses `scp` internally, so you can set it to a remote destination # keter`. Uses `scp` internally, so you can set it to a remote destination

View File

@ -20,10 +20,14 @@ stanzas:
ssl: true ssl: true
forward-env: forward-env:
- LDAPURI - LDAPHOST
- LDAPDN - LDAPTLS
- LDAPPW - LDAPPORT
- LDAPBN - LDAPUSER
- LDAPPASS
- LDAPBASE
- LDAPSCOPE
- LDAPTIMEOUT
- DETAILED_LOGGING - DETAILED_LOGGING
- LOG_ALL - LOG_ALL
- PWFILE - PWFILE

View File

@ -35,10 +35,14 @@ database:
poolsize: "_env:PGPOOLSIZE:10" poolsize: "_env:PGPOOLSIZE:10"
ldap: ldap:
uri: "_env:LDAPURI:ldap://localhost:389" host: "_env:LDAPHOST:"
dn: "_env:LDAPDN:uniworx" tls: "_env:LDAPTLS:"
password: "_env:LDAPPW:" port: "_env:LDAPPORT:389"
basename: "_env:LDAPBN:" user: "_env:LDAPUSER:"
pass: "_env:LDAPPASS:"
baseDN: "_env:LDAPBASE:"
scope: "_env:LDAPSCOPE:WholeSubtree"
timeout: "_env:LDAPTIMEOUT:5"
default-favourites: 12 default-favourites: 12
default-theme: Default default-theme: Default

5
messages/campus/de.msg Normal file
View File

@ -0,0 +1,5 @@
CampusIdentNote: Campus-Kennung bitte inkl. Domain-Teil (@campus.lmu.de) angeben.
CampusIdent: Campus-Kennung
CampusPassword: Passwort
CampusSubmit: Abschicken
CampusInvalidCredentials: Ungültige Logindaten

View File

@ -32,6 +32,7 @@ LectureStart: Beginn Vorlesungen
Course: Kurs Course: Kurs
CourseShort: Kürzel CourseShort: Kürzel
CourseCapacity: Kapazität CourseCapacity: Kapazität
CourseCapacityTip: Falls angegeben wird die Anzahl an Kursanmeldungen, die zugelassen werden, beschränkt
CourseNoCapacity: In diesem Kurs sind keine Plätze mehr frei. CourseNoCapacity: In diesem Kurs sind keine Plätze mehr frei.
CourseRegisterOk: Sie wurden angemeldet CourseRegisterOk: Sie wurden angemeldet
CourseDeregisterOk: Sie wurden abgemeldet CourseDeregisterOk: Sie wurden abgemeldet
@ -48,6 +49,22 @@ TermCourseListTitle tid@TermId: Kurse #{display tid}
CourseNewHeading: Neuen Kurs anlegen CourseNewHeading: Neuen Kurs anlegen
CourseEditHeading tid@TermId courseShortHand@CourseShorthand: Kurs #{display tid}-#{courseShortHand} editieren CourseEditHeading tid@TermId courseShortHand@CourseShorthand: Kurs #{display tid}-#{courseShortHand} editieren
CourseEditTitle: Kurs editieren/anlegen CourseEditTitle: Kurs editieren/anlegen
CourseMembers: Teilnehmer
CourseMembersCount num@Int64: #{display num}
CourseMembersCountLimited num@Int64 max@Int64: #{display num}/#{display max}
CourseName: Name
CourseDescription: Beschreibung
CourseDescriptionTip: Beliebiges HTML-Markup ist gestattet
CourseHomepage: Homepage
CourseShorthand: Kürzel
CourseShorthandUnique: Muss innerhalb des Semesters eindeutig sein
CourseSemester: Semester
CourseSchool: Institut
CourseSecretTip: Anmeldung zum Kurs erfordert Eingabe des Passworts, sofern gesetzt
CourseRegisterFromTip: Ohne Datum ist keine Anmeldung möglich
CourseRegisterToTip: Anmeldung darf auch ohne Begrenzung möglich sein
CourseDeregisterUntilTip: Abmeldung darf auch ohne Begrenzung möglich sein
Sheet: Blatt Sheet: Blatt
SheetList tid@TermId courseShortHand@CourseShorthand: #{display tid}-#{courseShortHand} Übersicht Übungsblätter SheetList tid@TermId courseShortHand@CourseShorthand: #{display tid}-#{courseShortHand} Übersicht Übungsblätter
@ -69,12 +86,19 @@ SheetSolution: Lösung
SheetSolutionFrom: Lösung ab SheetSolutionFrom: Lösung ab
SheetMarking: Hinweise für Korrektoren SheetMarking: Hinweise für Korrektoren
SheetType: Wertung SheetType: Wertung
SheetInvisible: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar! SheetInvisible: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar!
SheetInvisibleUntil mFrom@Text: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar bis #{mFrom}! SheetInvisibleUntil mFrom@Text: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar bis #{mFrom}!
SheetName: Name
SheetDescription: Hinweise für Teilnehmer
SheetGroup: Gruppenabgabe
SheetVisibleFrom: Sichtbar ab SheetVisibleFrom: Sichtbar ab
SheetVisibleFromTip: Ohne Datum wird das Blatt nie sichtbar, z.B. weil es noch nicht fertig ist
SheetActiveFrom: Aktiv ab SheetActiveFrom: Aktiv ab
SheetActiveFromTip: Abgabe und Download der Aufgabenstellung ist erst ab diesem Datum möglich
SheetActiveTo: Abgabefrist SheetActiveTo: Abgabefrist
SheetHintFromTip: Ohne Datum nie für Teilnehmer sichtbar, Korrektoren können diese Dateien immer herunterladen
SheetSolutionFromTip: Ohne Datum nie für Teilnehmer sichtbar, Korrektoren können diese Dateien immer herunterladen
SheetMarkingTip: Hinweise zur Korrektur, sichtbar nur für Korrektoren
SheetErrVisibility: Sichtbarkeit muss vor Beginn der Abgabefrist liegen SheetErrVisibility: Sichtbarkeit muss vor Beginn der Abgabefrist liegen
SheetErrDeadlineEarly: Ende der Abgabefrist muss nach deren Beginn liegen SheetErrDeadlineEarly: Ende der Abgabefrist muss nach deren Beginn liegen
@ -145,7 +169,8 @@ CorrectorsDefaulted: Korrektoren-Liste wurde aus bisherigen Übungsblättern die
Users: Benutzer Users: Benutzer
HomeHeading: Aktuelle Termine HomeHeading: Aktuelle Termine
LoginHeading: Login bitte mit "@campus.lmu.de" angeben LoginHeading: Authentifizierung
LoginTitle: Authentifizierung
ProfileHeading: Benutzerprofil und Einstellungen ProfileHeading: Benutzerprofil und Einstellungen
ProfileDataHeading: Gespeicherte Benutzerdaten ProfileDataHeading: Gespeicherte Benutzerdaten
ImpressumHeading: Impressum ImpressumHeading: Impressum
@ -201,6 +226,7 @@ RatingPoints: Punkte
RatingPercent: Erreicht RatingPercent: Erreicht
RatingFiles: Korrigierte Dateien RatingFiles: Korrigierte Dateien
PointsNotPositive: Punktzahl darf nicht negativ sein PointsNotPositive: Punktzahl darf nicht negativ sein
RatingPointsDone: Abgabe zählt als korrigiert, gdw. Punktezahl gesetzt ist
FileTitle: Dateiname FileTitle: Dateiname
FileModified: Letzte Änderung FileModified: Letzte Änderung
@ -211,10 +237,6 @@ RatingUpdated: Korrektur gespeichert
RatingDeleted: Korrektur zurückgesetzt RatingDeleted: Korrektur zurückgesetzt
RatingFilesUpdated: Korrigierte Dateien überschrieben RatingFilesUpdated: Korrigierte Dateien überschrieben
CourseMembers: Teilnehmer
CourseMembersCount num@Int64: #{display num}
CourseMembersCountLimited num@Int64 max@Int64: #{display num}/#{display max}
NoTableContent: Kein Tabelleninhalt NoTableContent: Kein Tabelleninhalt
NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
@ -235,3 +257,6 @@ LastEdit: Letzte Änderung
SubmissionFilesIgnored: Es wurden Dateien in der hochgeladenen Abgabe ignoriert: SubmissionFilesIgnored: Es wurden Dateien in der hochgeladenen Abgabe ignoriert:
SubmissionDoesNotExist smid@CryptoFileNameSubmission: Es existiert keine Abgabe mit Nummer #{toPathPiece smid}. SubmissionDoesNotExist smid@CryptoFileNameSubmission: Es existiert keine Abgabe mit Nummer #{toPathPiece smid}.
LDAPLoginTitle: Campus-Login
DummyLoginTitle: Development-Login

View File

@ -74,8 +74,6 @@ dependencies:
- generic-deriving - generic-deriving
- blaze-html - blaze-html
- conduit-resumablesink >=0.2 - conduit-resumablesink >=0.2
- yesod-auth-ldap
- LDAP
- parsec - parsec
- uuid - uuid
- exceptions - exceptions
@ -88,6 +86,7 @@ dependencies:
- th-lift-instances - th-lift-instances
- gitrev - gitrev
- Glob - Glob
- ldap-client
# The library contains all of our application code. The executable # The library contains all of our application code. The executable
# defined below is just a thin wrapper. # defined below is just a thin wrapper.

152
src/Auth/LDAP.hs Normal file
View File

@ -0,0 +1,152 @@
{-# LANGUAGE RecordWildCards
, OverloadedStrings
, TemplateHaskell
, TypeFamilies
, FlexibleContexts
, FlexibleInstances
, NoImplicitPrelude
, ScopedTypeVariables
#-}
module Auth.LDAP
( campusLogin
, campusUser
, CampusMessage(..)
, Ldap.AttrList, Ldap.Attr(..), Ldap.AttrValue
) where
import Import.NoFoundation
import Control.Lens
import Utils.Form
import qualified Ldap.Client as Ldap
import qualified Data.Text.Encoding as Text
import qualified Yesod.Auth.Message as Msg
data CampusLogin = CampusLogin { campusIdent, campusPassword :: Text }
data CampusMessage = MsgCampusIdentNote
| MsgCampusIdent
| MsgCampusPassword
| MsgCampusSubmit
| MsgCampusInvalidCredentials
findUser :: LdapConf -> Ldap.Ldap -> Text -> [Ldap.Attr] -> IO [Ldap.SearchEntry]
findUser LdapConf{..} ldap campusIdent = Ldap.search ldap ldapBase userSearchSettings userFilter
where
userFilter = userPrincipalName Ldap.:= Text.encodeUtf8 campusIdent
userSearchSettings = mconcat
[ Ldap.scope ldapScope
, Ldap.size 2
, Ldap.time ldapTimeout
, Ldap.derefAliases Ldap.DerefAlways
]
userPrincipalName :: Ldap.Attr
userPrincipalName = Ldap.Attr "userPrincipalName"
campusForm :: ( RenderMessage site FormMessage
, RenderMessage site CampusMessage
, Button site SubmitButton
, Show (ButtonCssClass site)
) => AForm (HandlerT site IO) CampusLogin
campusForm = CampusLogin
<$> areq textField (fslpI MsgCampusIdent "user.name@campus.lmu.de" & setTooltip MsgCampusIdentNote) Nothing
<*> areq passwordField (fslI MsgCampusPassword) Nothing
<* submitButton
campusLogin :: forall site.
( YesodAuth site
, RenderMessage site FormMessage
, RenderMessage site CampusMessage
, Button site SubmitButton
, Show (ButtonCssClass site)
) => LdapConf -> AuthPlugin site
campusLogin conf@LdapConf{..} = AuthPlugin{..}
where
apName = "LDAP"
apDispatch :: Text -> [Text] -> HandlerT Auth (HandlerT site IO) TypedContent
apDispatch "POST" [] = do
((loginRes, _), _) <- lift . runFormPost $ renderAForm FormStandard campusForm
case loginRes of
FormFailure errs -> do
forM_ errs $ addMessage "error" . toHtml
redirect LoginR
FormMissing -> redirect LoginR
FormSuccess CampusLogin{..} -> do
ldapResult <- liftIO . Ldap.with ldapHost ldapPort $ \ldap -> do
Ldap.bind ldap (Ldap.Dn campusIdent) (Ldap.Password $ Text.encodeUtf8 campusPassword)
Ldap.bind ldap ldapDn ldapPassword
findUser conf ldap campusIdent [userPrincipalName]
case ldapResult of
Left err
| Ldap.ResponseError (Ldap.ResponseErrorCode _ Ldap.InvalidCredentials _ _) <- err
-> do
$logDebugS "LDAP" "Invalid credentials"
loginErrorMessageI LoginR Msg.InvalidLogin
| otherwise -> do
$logErrorS "LDAP" $ "Error during login: " <> tshow err
loginErrorMessageI LoginR Msg.AuthError
Right searchResults
| [Ldap.SearchEntry (Ldap.Dn userDN) userAttrs] <- searchResults
, Just [principalName] <- lookup userPrincipalName userAttrs
, Right credsIdent <- Text.decodeUtf8' principalName
-> do
$logDebugS "LDAP" $ tshow searchResults
lift . setCredsRedirect $ Creds apName credsIdent [("DN", userDN)]
| otherwise -> do
$logWarnS "LDAP" $ "Could not extract principal name: " <> tshow searchResults
loginErrorMessageI LoginR Msg.AuthError
apDispatch _ _ = notFound
apLogin toMaster = do
(login, loginEnctype) <- handlerToWidget . generateFormPost $ renderAForm FormStandard campusForm
$(widgetFile "widgets/campus-login-form")
data CampusUserException = CampusUserLdapError Ldap.LdapError
| CampusUserNoResult
| CampusUserAmbiguous
deriving (Show, Eq, Typeable)
instance Exception CampusUserException
campusUser :: (MonadIO m, MonadThrow m) => LdapConf -> Creds site -> m (Ldap.AttrList [])
campusUser conf@LdapConf{..} Creds{..} = liftIO $ either (throwM . CampusUserLdapError) return <=< Ldap.with ldapHost ldapPort $ \ldap -> do
Ldap.bind ldap ldapDn ldapPassword
results <- case lookup "DN" credsExtra of
Just userDN -> do
let userFilter = Ldap.Present userPrincipalName
userSearchSettings = mconcat
[ Ldap.scope Ldap.BaseObject
, Ldap.size 2
, Ldap.time ldapTimeout
, Ldap.derefAliases Ldap.DerefAlways
]
Ldap.search ldap (Ldap.Dn userDN) userSearchSettings userFilter []
Nothing -> do
findUser conf ldap credsIdent []
case results of
[] -> throwM CampusUserNoResult
[Ldap.SearchEntry _ attrs] -> return attrs
_otherwise -> throwM CampusUserAmbiguous
-- ldapConfig :: UniWorX -> LDAPConfig
-- ldapConfig _app@(appSettings -> settings) = LDAPConfig
-- { usernameFilter = \u -> principalName <> "=" <> u
-- , identifierModifier
-- , ldapUri = appLDAPURI settings
-- , initDN = appLDAPDN settings
-- , initPass = appLDAPPw settings
-- , baseDN = appLDAPBaseName settings
-- , ldapScope = LdapScopeSubtree
-- }
-- where
-- principalName :: IsString a => a
-- principalName = "userPrincipalName"
-- identifierModifier _ entry = case lookup principalName $ leattrs entry of
-- Just [n] -> Text.pack n
-- _ -> error "Could not determine user principal name"

60
src/Auth/PWFile.hs Normal file
View File

@ -0,0 +1,60 @@
{-# LANGUAGE NoImplicitPrelude
, QuasiQuotes
, TemplateHaskell
, ViewPatterns
, RecordWildCards
, OverloadedStrings
, FlexibleContexts
, TypeFamilies
#-}
module Auth.PWFile
( maintenanceLogin
) where
import Import.NoFoundation
import Database.Persist.Sql (IsSqlBackend)
import qualified Data.Yaml as Yaml
import qualified Data.Text.Encoding as Text
import Yesod.Auth.Util.PasswordStore (verifyPassword)
maintenanceLogin :: ( YesodAuth site
, YesodPersist site
, IsSqlBackend (YesodPersistBackend site)
, PersistUniqueWrite (YesodPersistBackend site)
) => FilePath -> AuthPlugin site
maintenanceLogin fp = AuthPlugin{..}
where
apName = "PWFile"
apLogin = mempty
apDispatch "GET" [] = do
authData <- lookupBasicAuth
pwdata <- liftIO $ Yaml.decodeFileEither fp
addHeader "WWW-Authenticate" [st|Basic realm="uni2work maintenance auth" charset="UTF-8"|]
case pwdata of
Left err -> $logDebugS "Auth" $ tshow err
Right pws -> $logDebugS "Auth" $ tshow (length pws) <> " pw entries"
case (authData, pwdata) of
(Nothing, _) -> do
notAuthenticated
(Just (usr, (Text.encodeUtf8 -> pw)), Right pwdata')
| [ PWEntry{ pwUser = pwUser@(User{..}), pwHash = (Text.encodeUtf8 -> pwHash) } ]
<- [ pwe | pwe@PWEntry{..} <- pwdata'
, let User{..} = pwUser
, userIdent == usr
, userPlugin == apName
]
, verifyPassword pw pwHash
-> lift $ do
runDB . void $ insertUnique pwUser
setCredsRedirect $ Creds apName userIdent []
_ -> permissionDenied "Invalid auth"
apDispatch _ _ = notFound

View File

@ -55,6 +55,7 @@ instance (CI.FoldCase s, PathMultiPiece s) => PathMultiPiece (CI s) where
decCryptoIDs [ ''SubmissionId decCryptoIDs [ ''SubmissionId
, ''FileId , ''FileId
, ''UserId , ''UserId
, ''CourseId
] ]
instance {-# OVERLAPS #-} namespace ~ CryptoIDNamespace (CI FilePath) SubmissionId => PathPiece (E.CryptoID namespace (CI FilePath)) where instance {-# OVERLAPS #-} namespace ~ CryptoIDNamespace (CI FilePath) SubmissionId => PathPiece (E.CryptoID namespace (CI FilePath)) where

View File

@ -23,13 +23,11 @@ import Text.Jasmine (minifym)
-- Used only when in "auth-dummy-login" setting is enabled. -- Used only when in "auth-dummy-login" setting is enabled.
import Yesod.Auth.Message import Yesod.Auth.Message
import Yesod.Auth.Dummy import Yesod.Auth.Dummy
import Yesod.Auth.LDAP import Auth.LDAP
import Auth.PWFile
import qualified Network.Wai as W (requestMethod, pathInfo) import qualified Network.Wai as W (requestMethod, pathInfo)
import LDAP.Data (LDAPScope(..))
import LDAP.Search (LDAPEntry(..))
import Yesod.Default.Util (addStaticContentExternal) import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Core.Types (Logger) import Yesod.Core.Types (Logger)
import qualified Yesod.Core.Unsafe as Unsafe import qualified Yesod.Core.Unsafe as Unsafe
@ -43,8 +41,6 @@ import Data.ByteArray (convert)
import Crypto.Hash (Digest, SHAKE256) import Crypto.Hash (Digest, SHAKE256)
import Crypto.Hash.Conduit (sinkHash) import Crypto.Hash.Conduit (sinkHash)
import Yesod.Auth.Util.PasswordStore
import qualified Data.ByteString.Base64.URL as Base64 (encode) import qualified Data.ByteString.Base64.URL as Base64 (encode)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
@ -81,6 +77,7 @@ import Handler.Utils.Templates
import Handler.Utils.StudyFeatures import Handler.Utils.StudyFeatures
import Control.Lens import Control.Lens
import Utils import Utils
import Utils.Form
import Utils.Lens import Utils.Lens
import Data.Aeson import Data.Aeson
@ -164,7 +161,8 @@ data MenuTypes -- Semantische Rolle:
| PageActionSecondary { menuItem :: MenuItem } -- Seitenspezifische Aktion, selten | PageActionSecondary { menuItem :: MenuItem } -- Seitenspezifische Aktion, selten
-- Messages -- Messages
mkMessage "UniWorX" "messages" "de" mkMessage "UniWorX" "messages/uniworx" "de"
mkMessageVariant "UniWorX" "Campus" "messages/campus" "de"
-- This instance is required to use forms. You can modify renderMessage to -- This instance is required to use forms. You can modify renderMessage to
-- achieve customized and internationalized form validation messages. -- achieve customized and internationalized form validation messages.
@ -200,6 +198,16 @@ instance RenderMessage UniWorX SheetFileType where
instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where
renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route) renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route)
data instance ButtonCssClass UniWorX = BCDefault | BCPrimary | BCSuccess | BCInfo | BCWarning | BCDanger | BCLink
deriving (Enum, Eq, Ord, Bounded, Read, Show)
instance Button UniWorX SubmitButton where
label BtnSubmit = [whamlet|_{MsgBtnSubmit}|]
cssClass BtnSubmit = BCPrimary
getTimeLocale' :: [Lang] -> TimeLocale getTimeLocale' :: [Lang] -> TimeLocale
getTimeLocale' = $(timeLocaleMap [("de", "de_DE.utf8")]) getTimeLocale' = $(timeLocaleMap [("de", "de_DE.utf8")])
@ -504,13 +512,6 @@ instance Yesod UniWorX where
$logDebugS "normalizeRouteMiddleware" [st|Redirecting to #{tshow route'}|] $logDebugS "normalizeRouteMiddleware" [st|Redirecting to #{tshow route'}|]
redirectWith movedPermanently301 route' redirectWith movedPermanently301 route'
-- The following exception permits drive-by login via LDAP plugin. FIXME: Blocked by #17
isWriteRequest (AuthR (PluginR "LDAP" _)) = return False
isWriteRequest _ = do
wai <- waiRequest
return $ W.requestMethod wai `notElem`
["GET", "HEAD", "OPTIONS", "TRACE"]
defaultLayout widget = do defaultLayout widget = do
master <- getYesod master <- getYesod
mmsgs <- getMessages mmsgs <- getMessages
@ -1067,12 +1068,13 @@ instance YesodAuth UniWorX where
redirectToReferer _ = True redirectToReferer _ = True
loginHandler = do loginHandler = do
tp <- getRouteToParent toParent <- getRouteToParent
lift . authLayout $ do lift . defaultLayout $ do
master <- getYesod plugins <- getsYesod authPlugins
let authPlugins' = authPlugins master $logDebugS "Auth" $ "Enabled plugins: " <> Text.intercalate ", " (map apName plugins)
$logDebugS "Auth" $ "Enabled plugins: " <> Text.intercalate ", " (map apName authPlugins')
forM_ authPlugins' $ flip apLogin tp setTitleI MsgLoginTitle
$(widgetFile "login")
authenticate creds@(Creds{..}) = runDB . fmap (either id id) . runExceptT $ do authenticate creds@(Creds{..}) = runDB . fmap (either id id) . runExceptT $ do
let (userPlugin, userIdent) let (userPlugin, userIdent)
@ -1085,118 +1087,88 @@ instance YesodAuth UniWorX where
isPWFile = credsPlugin == "PWFile" isPWFile = credsPlugin == "PWFile"
uAuth = UniqueAuthentication userPlugin userIdent uAuth = UniqueAuthentication userPlugin userIdent
$logDebugS "auth" $ tshow ((userPlugin, userIdent), creds) $logDebugS "auth" $ tshow creds
when (isDummy || isPWFile) . (throwError =<<) . lift $
maybe (UserError $ IdentifierNotFound credsIdent) (Authenticated . entityKey) <$> getBy uAuth
let
userMatrikelnummer = lookup "LMU-Stud-Matrikelnummer" credsExtra
userEmail' = lookup "mail" credsExtra
userDisplayName' = lookup "displayName" credsExtra
userEmail <- maybe (throwError $ ServerError "Could not retrieve user email") (return . CI.mk) userEmail'
userDisplayName <- maybe (throwError $ ServerError "Could not retrieve user name") return userDisplayName'
AppSettings{..} <- getsYesod appSettings AppSettings{..} <- getsYesod appSettings
let case appLdapConf of
userMaxFavourites = appDefaultMaxFavourites Just ldapConf -> do
userTheme = appDefaultTheme ldapData <- campusUser ldapConf $ Creds userPlugin userIdent credsExtra
userDateTimeFormat = appDefaultDateTimeFormat $logDebugS "LDAP" $ "Successful LDAP lookup: " <> tshow ldapData
userDateFormat = appDefaultDateFormat
userTimeFormat = appDefaultTimeFormat let
newUser = User{..} userMatrikelnummer' = lookup (Attr "LMU-Stud-Matrikelnummer") ldapData
userUpdate = [ UserMatrikelnummer =. userMatrikelnummer userEmail' = lookup (Attr "mail") ldapData
, UserDisplayName =. userDisplayName userDisplayName' = lookup (Attr "displayName") ldapData
, UserEmail =. userEmail
userEmail <- if
| Just [bs] <- userEmail'
, Right userEmail <- Text.decodeUtf8' bs
-> return $ CI.mk userEmail
| otherwise
-> throwError $ ServerError "Could not retrieve user email"
userDisplayName <- if
| Just [bs] <- userDisplayName'
, Right userDisplayName <- Text.decodeUtf8' bs
-> return userDisplayName
| otherwise
-> throwError $ ServerError "Could not retrieve user name"
userMatrikelnummer <- if
| Just [bs] <- userMatrikelnummer'
, Right userMatrikelnummer <- Text.decodeUtf8' bs
-> return $ Just userMatrikelnummer
| Nothing <- userMatrikelnummer'
-> return Nothing
| otherwise
-> throwError $ ServerError "Could not decode user matriculation"
let
userMaxFavourites = appDefaultMaxFavourites
userTheme = appDefaultTheme
userDateTimeFormat = appDefaultDateTimeFormat
userDateFormat = appDefaultDateFormat
userTimeFormat = appDefaultTimeFormat
newUser = User{..}
userUpdate = [ UserMatrikelnummer =. userMatrikelnummer
, UserDisplayName =. userDisplayName
, UserEmail =. userEmail
] ]
userId <- lift $ entityKey <$> upsertBy uAuth newUser userUpdate userId <- lift $ entityKey <$> upsertBy uAuth newUser userUpdate
let let
userStudyFeatures = concat <$> mapM (parseStudyFeatures userId) userStudyFeatures' userStudyFeatures = concat <$> mapM (parseStudyFeatures userId) userStudyFeatures'
userStudyFeatures' = [ v | (k, v) <- credsExtra, k == "dfnEduPersonFeaturesOfStudy" ] userStudyFeatures' = do
(k, v) <- ldapData
guard $ k == Attr "dfnEduPersonFeaturesOfStudy"
v' <- v
Right str <- return $ Text.decodeUtf8' v'
return str
fs <- either (\err -> throwError . ServerError $ "Could not parse features of study: " <> err) return userStudyFeatures fs <- either (\err -> throwError . ServerError $ "Could not parse features of study: " <> err) return userStudyFeatures
lift $ deleteWhere [StudyFeaturesUser ==. userId] lift $ deleteWhere [StudyFeaturesUser ==. userId]
forM_ fs $ \StudyFeatures{..} -> do forM_ fs $ \StudyFeatures{..} -> do
lift . insertMaybe studyFeaturesDegree $ StudyDegree (unStudyDegreeKey studyFeaturesDegree) Nothing Nothing lift . insertMaybe studyFeaturesDegree $ StudyDegree (unStudyDegreeKey studyFeaturesDegree) Nothing Nothing
lift . insertMaybe studyFeaturesField $ StudyTerms (unStudyTermsKey studyFeaturesField) Nothing Nothing lift . insertMaybe studyFeaturesField $ StudyTerms (unStudyTermsKey studyFeaturesField) Nothing Nothing
lift $ insertMany_ fs
return $ Authenticated userId
Nothing -> (throwError =<<) . lift $
maybe (UserError $ IdentifierNotFound credsIdent) (Authenticated . entityKey) <$> getBy uAuth
lift $ insertMany_ fs
return $ Authenticated userId
where where
insertMaybe key val = get key >>= maybe (insert_ val) (\_ -> return ()) insertMaybe key val = get key >>= maybe (insert_ val) (\_ -> return ())
-- You can add other plugins like Google Email, email or OAuth here authPlugins (appSettings -> AppSettings{..}) = catMaybes
authPlugins app = [genericAuthLDAP $ ldapConfig app] ++ extraAuthPlugins [ campusLogin <$> appLdapConf
-- Enable authDummy login if enabled. , maintenanceLogin <$> appAuthPWFile
where extraAuthPlugins = [authDummy | appAuthDummyLogin $ appSettings app] , authDummy <$ guard appAuthDummyLogin
++ [authPWFile fp | fp <- maybeToList . appAuthPWFile $ appSettings app] ]
authHttpManager = getHttpManager authHttpManager = getHttpManager
authPWFile :: FilePath -> AuthPlugin UniWorX
authPWFile fp = AuthPlugin{..}
where
apName = "PWFile"
apLogin = mempty
apDispatch "GET" [] = do
authData <- lookupBasicAuth
pwdata <- liftIO $ Yaml.decodeFileEither fp
addHeader "WWW-Authenticate" [st|Basic realm="uni2work maintenance auth" charset="UTF-8"|]
case pwdata of
Left err -> $logDebugS "Auth" $ tshow err
Right pws -> $logDebugS "Auth" $ tshow (length pws) <> " pw entries"
case (authData, pwdata) of
(Nothing, _) -> do
notAuthenticated
(Just (usr, (Text.encodeUtf8 -> pw)), Right pwdata')
| [ PWEntry{ pwUser = pwUser@(User{..}), pwHash = (Text.encodeUtf8 -> pwHash) } ]
<- [ pwe | pwe@PWEntry{..} <- pwdata'
, let User{..} = pwUser
, userIdent == usr
, userPlugin == apName
]
, verifyPassword pw pwHash
-> lift $ do
runDB . void $ insertUnique pwUser
setCredsRedirect $ Creds apName userIdent []
_ -> permissionDenied "Invalid auth"
apDispatch _ _ = notFound
ldapConfig :: UniWorX -> LDAPConfig
ldapConfig _app@(appSettings -> settings) = LDAPConfig
{ usernameFilter = \u -> principalName <> "=" <> u
, identifierModifier
, ldapUri = appLDAPURI settings
, initDN = appLDAPDN settings
, initPass = appLDAPPw settings
, baseDN = appLDAPBaseName settings
, ldapScope = LdapScopeSubtree
}
where
principalName :: IsString a => a
principalName = "userPrincipalName"
identifierModifier _ entry = case lookup principalName $ leattrs entry of
Just [n] -> Text.pack n
_ -> error "Could not determine user principal name"
-- | Access function to determine if a user is logged in.
isAuthenticated :: Handler AuthResult
isAuthenticated = do
muid <- maybeAuthId
return $ case muid of
Nothing -> Unauthorized "You must login to access this page"
Just _ -> Authorized
instance YesodAuthPersist UniWorX instance YesodAuthPersist UniWorX
-- Useful when writing code that is re-usable outside of the Handler context. -- Useful when writing code that is re-usable outside of the Handler context.

View File

@ -33,7 +33,7 @@ instance PathPiece CreateButton where -- for displaying the button only, not
toPathPiece = showToPathPiece toPathPiece = showToPathPiece
fromPathPiece = readFromPathPiece fromPathPiece = readFromPathPiece
instance Button CreateButton where instance Button UniWorX CreateButton where
label CreateMath = [whamlet|Ma<i>thema</i>tik|] label CreateMath = [whamlet|Ma<i>thema</i>tik|]
label CreateInf = "Informatik" label CreateInf = "Informatik"

View File

@ -406,7 +406,7 @@ postCorrectionR tid csh shn cid = do
let ratingComment = fmap Text.strip submissionRatingComment >>= (\c -> c <$ guard (not $ null c)) let ratingComment = fmap Text.strip submissionRatingComment >>= (\c -> c <$ guard (not $ null c))
((corrResult, corrForm), corrEncoding) <- runFormPost . identForm FIDcorrection . renderAForm FormStandard $ (,) ((corrResult, corrForm), corrEncoding) <- runFormPost . identForm FIDcorrection . renderAForm FormStandard $ (,)
<$> aopt pointsField (fslpI MsgRatingPoints "Punktezahl" & setTooltip "Abgabe zählt als korrigiert, gdw. Punktezahl gesetzt ist") (Just $ submissionRatingPoints) <$> aopt pointsField (fslpI MsgRatingPoints "Punktezahl" & setTooltip MsgRatingPointsDone) (Just $ submissionRatingPoints)
<*> (((\t -> t <$ guard (not $ null t)) =<<) . fmap (Text.strip . unTextarea) <$> aopt textareaField (fslI MsgRatingComment) (Just $ Textarea <$> submissionRatingComment)) <*> (((\t -> t <$ guard (not $ null t)) =<<) . fmap (Text.strip . unTextarea) <$> aopt textareaField (fslI MsgRatingComment) (Just $ Textarea <$> submissionRatingComment))
<* submitButton <* submitButton

View File

@ -1,4 +1,4 @@
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards, NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE MultiWayIf #-}
@ -263,7 +263,7 @@ courseDeleteHandler = undefined
courseEditHandler :: Bool -> Maybe (Entity Course) -> Handler Html courseEditHandler :: Bool -> Maybe (Entity Course) -> Handler Html
courseEditHandler isGet course = do courseEditHandler isGet course = do
aid <- requireAuthId -- TODO: Verify that Editor is owner of the Course to be Edited!!! aid <- requireAuthId -- TODO: Verify that Editor is owner of the Course to be Edited!!!
((result, formWidget), formEnctype) <- runFormPost $ newCourseForm $ courseToForm <$> course ((result, formWidget), formEnctype) <- runFormPost . newCourseForm =<< for course courseToForm
case result of case result of
(FormSuccess res@( (FormSuccess res@(
CourseForm { cfCourseId = Nothing CourseForm { cfCourseId = Nothing
@ -296,10 +296,11 @@ courseEditHandler isGet course = do
addMessageI "danger" $ MsgCourseNewDupShort tid csh addMessageI "danger" $ MsgCourseNewDupShort tid csh
(FormSuccess res@( (FormSuccess res@(
CourseForm { cfCourseId = Just cid CourseForm { cfCourseId = Just cID
, cfShort = csh , cfShort = csh
, cfTerm = tid , cfTerm = tid
})) -> do -- edit existing course })) -> do -- edit existing course
cid <- decrypt cID
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
-- addMessage "debug" [shamlet| #{show res}|] -- addMessage "debug" [shamlet| #{show res}|]
runDB $ do runDB $ do
@ -342,7 +343,7 @@ courseEditHandler isGet course = do
data CourseForm = CourseForm data CourseForm = CourseForm
{ cfCourseId :: Maybe CourseId -- Maybe CryptoUUIDCourse { cfCourseId :: Maybe CryptoUUIDCourse
, cfName :: CourseName , cfName :: CourseName
, cfDesc :: Maybe Html , cfDesc :: Maybe Html
, cfLink :: Maybe Text , cfLink :: Maybe Text
@ -357,24 +358,24 @@ data CourseForm = CourseForm
, cfDeRegUntil :: Maybe UTCTime , cfDeRegUntil :: Maybe UTCTime
} }
courseToForm :: Entity Course -> CourseForm courseToForm :: MonadCrypto m => Entity Course -> m CourseForm
courseToForm cEntity = CourseForm courseToForm (Entity cid Course{..}) = do
{ cfCourseId = Just $ entityKey cEntity cfCourseId <- Just <$> encrypt cid
, cfName = courseName course return $ CourseForm
, cfDesc = courseDescription course { cfCourseId
, cfLink = courseLinkExternal course , cfName = courseName
, cfShort = courseShorthand course , cfDesc = courseDescription
, cfTerm = courseTerm course , cfLink = courseLinkExternal
, cfSchool = courseSchool course , cfShort = courseShorthand
, cfCapacity = courseCapacity course , cfTerm = courseTerm
, cfSecret = courseRegisterSecret course , cfSchool = courseSchool
, cfMatFree = courseMaterialFree course , cfCapacity = courseCapacity
, cfRegFrom = courseRegisterFrom course , cfSecret = courseRegisterSecret
, cfRegTo = courseRegisterTo course , cfMatFree = courseMaterialFree
, cfDeRegUntil = courseDeregisterUntil course , cfRegFrom = courseRegisterFrom
} , cfRegTo = courseRegisterTo
where , cfDeRegUntil = courseDeregisterUntil
course = entityVal cEntity }
newCourseForm :: Maybe CourseForm -> Form CourseForm newCourseForm :: Maybe CourseForm -> Form CourseForm
newCourseForm template = identForm FIDcourse $ \html -> do newCourseForm template = identForm FIDcourse $ \html -> do
@ -385,30 +386,32 @@ newCourseForm template = identForm FIDcourse $ \html -> do
-- UUID.encrypt cidKey cid -- UUID.encrypt cidKey cid
(result, widget) <- flip (renderAForm FormStandard) html $ CourseForm (result, widget) <- flip (renderAForm FormStandard) html $ CourseForm
-- <$> pure cid -- $ join $ cfCourseId <$> template -- why doesnt this work? -- <$> pure cid -- $ join $ cfCourseId <$> template -- why doesnt this work?
<$> aopt hiddenField "KursId" (cfCourseId <$> template) <$> aopt hiddenField "courseId" (cfCourseId <$> template)
<*> areq (ciField textField) (fsb "Name") (cfName <$> template) <*> areq (ciField textField) (fslI MsgCourseName) (cfName <$> template)
<*> aopt htmlField (fsb "Beschreibung") (cfDesc <$> template) <*> aopt htmlField (fslI MsgCourseDescription
<*> aopt urlField (fsb "Homepage") (cfLink <$> template) & setTooltip MsgCourseDescriptionTip) (cfDesc <$> template)
<*> areq (ciField textField) (fsb "Kürzel" <*> aopt urlField (fslI MsgCourseHomepage) (cfLink <$> template)
<*> areq (ciField textField) (fslI MsgCourseShorthand
-- & addAttr "disabled" "disabled" -- & addAttr "disabled" "disabled"
& setTooltip "Muss innerhalb des Semesters eindeutig sein") & setTooltip MsgCourseShorthandUnique)
(cfShort <$> template) (cfShort <$> template)
<*> areq termActiveField (fsb "Semester") (cfTerm <$> template) <*> areq termActiveField (fslI MsgCourseSemester) (cfTerm <$> template)
<*> areq schoolField (fsb "Institut") (cfSchool <$> template) <*> areq schoolField (fslI MsgCourseSchool) (cfSchool <$> template)
<*> aopt (natField "Kapazität") (fslI MsgCourseCapacity <*> aopt (natField "Kapazität") (fslI MsgCourseCapacity
& setTooltip "Für unbeschränkte Kapazität KEINEN Wert angeben.") (cfCapacity <$> template) & setTooltip MsgCourseCapacityTip
) (cfCapacity <$> template)
<*> aopt textField (fslpI MsgCourseSecret "beliebige Zeichenkette" <*> aopt textField (fslpI MsgCourseSecret "beliebige Zeichenkette"
& setTooltip "Optional: Anmeldung zum Kurs benötigt ein Passwort") & setTooltip MsgCourseSecretTip)
(cfSecret <$> template) (cfSecret <$> template)
<*> areq checkBoxField (fslI MsgMaterialFree)(cfMatFree <$> template) <*> areq checkBoxField (fslI MsgMaterialFree)(cfMatFree <$> template)
<*> aopt utcTimeField (fslpI MsgRegisterFrom "Datum, sonst KEINE Anmeldung" <*> aopt utcTimeField (fslpI MsgRegisterFrom "Datum"
& setTooltip "Ohne Datum ist keine Anmeldung zu diesem Kurs möglich!") & setTooltip MsgCourseRegisterFromTip)
(cfRegFrom <$> template) (cfRegFrom <$> template)
<*> aopt utcTimeField (fslpI MsgRegisterTo "Datum, sonst unbegr. Anmeldung" <*> aopt utcTimeField (fslpI MsgRegisterTo "Datum"
& setTooltip "Die Anmeldung darf ohne Begrenzung sein") & setTooltip MsgCourseRegisterToTip)
(cfRegTo <$> template) (cfRegTo <$> template)
<*> aopt utcTimeField (fslpI MsgDeRegUntil "Datum, sonst unbegr. Abmeldung" <*> aopt utcTimeField (fslpI MsgDeRegUntil "Datum"
& setTooltip "Die Abmeldung darf ohne Begrenzung sein") & setTooltip MsgCourseDeregisterUntilTip)
(cfDeRegUntil <$> template) (cfDeRegUntil <$> template)
<* submitButton <* submitButton
return $ case result of return $ case result of

View File

@ -104,29 +104,29 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do
mr <- getMsgRenderer mr <- getMsgRenderer
ctime <- liftIO $ getCurrentTime ctime <- liftIO $ getCurrentTime
(result, widget) <- flip (renderAForm FormStandard) html $ SheetForm (result, widget) <- flip (renderAForm FormStandard) html $ SheetForm
<$> areq (ciField textField) (fsb "Name") (sfName <$> template) <$> areq (ciField textField) (fslI MsgSheetName) (sfName <$> template)
<*> aopt htmlField (fsb "Hinweise für Teilnehmer") (sfDescription <$> template) <*> aopt htmlField (fslI MsgSheetDescription) (sfDescription <$> template)
<*> sheetTypeAFormReq (fsb "Bewertung") (sfType <$> template) <*> sheetTypeAFormReq (fslI MsgSheetType) (sfType <$> template)
<*> sheetGroupAFormReq (fsb "Abgabegruppengröße") (sfGrouping <$> template) <*> sheetGroupAFormReq (fslI MsgSheetGroup) (sfGrouping <$> template)
<*> aopt htmlField (fsb "Hinweise für Korrektoren") (sfMarkingText <$> template) <*> aopt htmlField (fslI MsgSheetMarking) (sfMarkingText <$> template)
<*> aopt utcTimeField (fslI MsgSheetVisibleFrom <*> aopt utcTimeField (fslI MsgSheetVisibleFrom
& setTooltip "Ohne Datum ist das Blatt komplett unsichtbar, z.B. weil es noch nicht fertig ist.") & setTooltip MsgSheetVisibleFromTip)
((sfVisibleFrom <$> template) <|> pure (Just ctime)) ((sfVisibleFrom <$> template) <|> pure (Just ctime))
<*> areq utcTimeField (fslI MsgSheetActiveFrom <*> areq utcTimeField (fslI MsgSheetActiveFrom
& setTooltip "Abgabe und Dateien zur Aufgabenstellung sind erst ab diesem Datum zugänglich") & setTooltip MsgSheetActiveFromTip)
(sfActiveFrom <$> template) (sfActiveFrom <$> template)
<*> areq utcTimeField (fslI MsgSheetActiveTo) (sfActiveTo <$> template) <*> areq utcTimeField (fslI MsgSheetActiveTo) (sfActiveTo <$> template)
<*> aopt (multiFileField $ oldFileIds SheetExercise) (fsb "Aufgabenstellung") (sfSheetF <$> template) <*> aopt (multiFileField $ oldFileIds SheetExercise) (fslI MsgSheetExercise) (sfSheetF <$> template)
<*> aopt utcTimeField (fslpI MsgSheetHintFrom "Datum, sonst nur für Korrektoren" <*> aopt utcTimeField (fslpI MsgSheetHintFrom "Datum, sonst nur für Korrektoren"
& setTooltip "Ohne Datum nie für Teilnehmer sichtbar, Korrektoren können diese Dateien immer herunterladen") & setTooltip MsgSheetHintFromTip)
(sfHintFrom <$> template) (sfHintFrom <$> template)
<*> aopt (multiFileField $ oldFileIds SheetHint) (fslI MsgSheetHint) (sfHintF <$> template) <*> aopt (multiFileField $ oldFileIds SheetHint) (fslI MsgSheetHint) (sfHintF <$> template)
<*> aopt utcTimeField (fslpI MsgSheetSolutionFrom "Datum, sonst nur für Korrektoren" <*> aopt utcTimeField (fslpI MsgSheetSolutionFrom "Datum, sonst nur für Korrektoren"
& setTooltip "Ohne Datum nie für Teilnehmer sichtbar, Korrektoren können diese Dateien immer herunterladen") & setTooltip MsgSheetSolutionFromTip)
(sfSolutionFrom <$> template) (sfSolutionFrom <$> template)
<*> aopt (multiFileField $ oldFileIds SheetSolution) (fslI MsgSheetSolution) (sfSolutionF <$> template) <*> aopt (multiFileField $ oldFileIds SheetSolution) (fslI MsgSheetSolution) (sfSolutionF <$> template)
<*> aopt (multiFileField $ oldFileIds SheetMarking) (fslI MsgSheetMarking <*> aopt (multiFileField $ oldFileIds SheetMarking) (fslI MsgSheetMarking
& setTooltip "Hinweise zur Korrektur, sichtbar nur für Korrektoren") (sfMarkingF <$> template) & setTooltip MsgSheetMarkingTip) (sfMarkingF <$> template)
<* submitButton <* submitButton
return $ case result of return $ case result of
FormSuccess sheetResult FormSuccess sheetResult

View File

@ -11,7 +11,12 @@
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
module Handler.Utils.Form where module Handler.Utils.Form
( module Handler.Utils.Form
, module Utils.Form
) where
import Utils.Form
import Handler.Utils.Form.Types import Handler.Utils.Form.Types
import Handler.Utils.Templates import Handler.Utils.Templates
@ -34,8 +39,6 @@ import qualified Data.Text as T
import Yesod.Form.Functions (parseHelper) import Yesod.Form.Functions (parseHelper)
import Yesod.Form.Bootstrap3 import Yesod.Form.Bootstrap3
import qualified Text.Blaze.Internal as Blaze (null)
import Web.PathPieces (showToPathPiece, readFromPathPiece) import Web.PathPieces (showToPathPiece, readFromPathPiece)
import Handler.Utils.Zip import Handler.Utils.Zip
@ -56,54 +59,10 @@ import Data.Scientific (Scientific)
import Data.Ratio import Data.Ratio
import Text.Read (readMaybe) import Text.Read (readMaybe)
------------------------------------------------
-- Unique Form Identifiers to avoid accidents --
------------------------------------------------
data FormIdentifier = FIDcourse | FIDsheet | FIDsubmission | FIDsettings | FIDcorrectors | FIDcorrectorTable | FIDcorrection | FIDcorrectionsUpload | FIDcorrectionUpload
deriving (Enum, Eq, Ord, Bounded, Read, Show)
identForm :: FormIdentifier -> Form a -> Form a
identForm fid = identifyForm (T.pack $ show fid)
{- Hinweise zur Erinnerung:
- identForm primär, wenn es mehr als ein Formular pro Handler gibt
- nur einmal pro makeForm reicht
-}
-------------------
-- Form Renderer --
-------------------
-- | Use this type to pass information to the form template
data FormLayout = FormStandard
renderAForm :: Monad m => FormLayout -> FormRender m a
renderAForm formLayout aform fragment = do
(res, (($ []) -> views)) <- aFormToForm aform
let widget = $(widgetFile "widgets/form")
return (res, widget)
---------------------------- ----------------------------
-- Buttons (new version ) -- -- Buttons (new version ) --
---------------------------- ----------------------------
data ButtonCssClass = BCDefault | BCPrimary | BCSuccess | BCInfo | BCWarning | BCDanger | BCLink
deriving (Enum, Eq, Ord, Bounded, Read, Show)
bcc2txt :: ButtonCssClass -> Text -- a Hack; maybe define Read/Show manually
bcc2txt bcc = T.pack $ "btn-" ++ (Char.toLower <$> (drop 2 $ show bcc))
class (Enum a, Bounded a, Ord a, PathPiece a) => Button a where
label :: a -> Widget
label = toWidget . toPathPiece
cssClass :: a -> ButtonCssClass
cssClass _ = BCDefault
data BtnDelete = BtnDelete | BtnAbort data BtnDelete = BtnDelete | BtnAbort
deriving (Enum, Eq, Ord, Bounded, Read, Show) deriving (Enum, Eq, Ord, Bounded, Read, Show)
@ -111,27 +70,13 @@ instance PathPiece BtnDelete where -- for displaying the button only, not rea
toPathPiece = showToPathPiece toPathPiece = showToPathPiece
fromPathPiece = readFromPathPiece fromPathPiece = readFromPathPiece
instance Button BtnDelete where instance Button UniWorX BtnDelete where
label BtnDelete = [whamlet|_{MsgBtnDelete}|] label BtnDelete = [whamlet|_{MsgBtnDelete}|]
label BtnAbort = [whamlet|_{MsgBtnAbort}|] label BtnAbort = [whamlet|_{MsgBtnAbort}|]
cssClass BtnDelete = BCDanger cssClass BtnDelete = BCDanger
cssClass BtnAbort = BCDefault cssClass BtnAbort = BCDefault
data SubmitButton = BtnSubmit
deriving (Enum, Eq, Ord, Bounded, Read, Show)
instance PathPiece SubmitButton where
toPathPiece = showToPathPiece
fromPathPiece = readFromPathPiece
instance Button SubmitButton where
label BtnSubmit = [whamlet|_{MsgBtnSubmit}|]
cssClass BtnSubmit = BCPrimary
data RegisterButton = BtnRegister | BtnDeregister data RegisterButton = BtnRegister | BtnDeregister
deriving (Enum, Eq, Ord, Bounded, Read, Show) deriving (Enum, Eq, Ord, Bounded, Read, Show)
@ -139,7 +84,7 @@ instance PathPiece RegisterButton where
toPathPiece = showToPathPiece toPathPiece = showToPathPiece
fromPathPiece = readFromPathPiece fromPathPiece = readFromPathPiece
instance Button RegisterButton where instance Button UniWorX RegisterButton where
label BtnRegister = [whamlet|_{MsgBtnRegister}|] label BtnRegister = [whamlet|_{MsgBtnRegister}|]
label BtnDeregister = [whamlet|_{MsgBtnDeregister}|] label BtnDeregister = [whamlet|_{MsgBtnDeregister}|]
@ -153,7 +98,7 @@ instance PathPiece AdminHijackUserButton where
toPathPiece = showToPathPiece toPathPiece = showToPathPiece
fromPathPiece = readFromPathPiece fromPathPiece = readFromPathPiece
instance Button AdminHijackUserButton where instance Button UniWorX AdminHijackUserButton where
label BtnHijack = [whamlet|_{MsgBtnHijack}|] label BtnHijack = [whamlet|_{MsgBtnHijack}|]
cssClass BtnHijack = BCDefault cssClass BtnHijack = BCDefault
@ -166,7 +111,7 @@ instance Button AdminHijackUserButton where
-- instance PathPiece LinkButton where -- instance PathPiece LinkButton where
-- LinkButton route = ??? -- LinkButton route = ???
linkButton :: Widget -> ButtonCssClass -> Route UniWorX -> Widget linkButton :: Widget -> ButtonCssClass UniWorX -> Route UniWorX -> Widget
linkButton lbl cls url = [whamlet| <a href=@{url} .btn .#{bcc2txt cls} role=button>^{lbl} |] linkButton lbl cls url = [whamlet| <a href=@{url} .btn .#{bcc2txt cls} role=button>^{lbl} |]
-- [whamlet| -- [whamlet|
-- <form method=post action=@{url}> -- <form method=post action=@{url}>
@ -178,30 +123,6 @@ linkButton lbl cls url = [whamlet| <a href=@{url} .btn .#{bcc2txt cls} role=butt
simpleLink :: Widget -> Route UniWorX -> Widget simpleLink :: Widget -> Route UniWorX -> Widget
simpleLink lbl url = [whamlet| <a href=@{url}>^{lbl} |] simpleLink lbl url = [whamlet| <a href=@{url}>^{lbl} |]
buttonField :: Button a => a -> Field Handler a -- already validates that the correct button press was received (result only neccessary for combinedButtonField)
buttonField btn = Field {fieldParse, fieldView, fieldEnctype}
where
fieldEnctype = UrlEncoded
fieldView fid name attrs _val _ =
[whamlet|
<button .btn .#{bcc2txt $ cssClass btn} type=submit name=#{name} value=#{toPathPiece btn} *{attrs} ##{fid}>^{label btn}
|]
fieldParse [] _ = return $ Right Nothing
fieldParse [str] _
| str == toPathPiece btn = return $ Right $ Just btn
| otherwise = return $ Left "Wrong button value"
fieldParse _ _ = return $ Left "Multiple button values"
combinedButtonField :: Button a => [a] -> AForm Handler [Maybe a]
combinedButtonField btns = traverse b2f btns
where
b2f b = aopt (buttonField b) "" Nothing
submitButton :: AForm Handler ()
submitButton = void $ combinedButtonField [BtnSubmit]
{- {-
combinedButtonField :: Button a => [a] -> Form m -> Form (a,m) combinedButtonField :: Button a => [a] -> Form m -> Form (a,m)
@ -236,7 +157,7 @@ combinedButtonField btns inner csrf = do
-} -}
-- buttonForm :: Button a => Markup -> MForm (HandlerT UniWorX IO) (FormResult a, (WidgetT UniWorX IO ())) -- buttonForm :: Button a => Markup -> MForm (HandlerT UniWorX IO) (FormResult a, (WidgetT UniWorX IO ()))
buttonForm :: (Button a) => Form a buttonForm :: (Button UniWorX a) => Form a
buttonForm csrf = do buttonForm csrf = do
buttonIdent <- newFormIdent buttonIdent <- newFormIdent
let button b = mopt (buttonField b) ("n/a"{ fsName = Just buttonIdent }) Nothing let button b = mopt (buttonField b) ("n/a"{ fsName = Just buttonIdent }) Nothing
@ -444,88 +365,6 @@ fsm = bfs -- TODO: get rid of Bootstrap
fsb :: Text -> FieldSettings site -- DEPRECATED fsb :: Text -> FieldSettings site -- DEPRECATED
fsb = bfs -- Just to avoid annoying Ambiguous Type Errors fsb = bfs -- Just to avoid annoying Ambiguous Type Errors
fsl :: Text -> FieldSettings UniWorX
fsl lbl =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = []
}
fslI :: RenderMessage UniWorX msg => msg -> FieldSettings UniWorX
fslI lbl =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = []
}
fslp :: Text -> Text -> FieldSettings UniWorX
fslp lbl placeholder =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = [("placeholder", placeholder)]
}
fslpI :: RenderMessage UniWorX msg => msg -> Text -> FieldSettings UniWorX
fslpI lbl placeholder =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = [("placeholder", placeholder)]
}
addAttr :: Text -> Text -> FieldSettings site -> FieldSettings site
addAttr attr valu fs = fs { fsAttrs=newAttrs (fsAttrs fs) }
where
newAttrs :: [(Text,Text)] -> [(Text,Text)]
newAttrs [] = [(attr,valu)]
newAttrs (p@(a,v):t)
| attr==a = (a,T.append valu $ cons ' ' v):t
| otherwise = p:(newAttrs t)
addAttrs :: Text -> [Text] -> FieldSettings site -> FieldSettings site
addAttrs attr valus fs = fs { fsAttrs=newAttrs (fsAttrs fs) }
where
newAttrs :: [(Text,Text)] -> [(Text,Text)]
newAttrs [] = [(attr,T.intercalate " " valus)]
newAttrs (p@(a,v):t)
| attr==a = (a,T.intercalate " " (v:valus)):t
| otherwise = p:(newAttrs t)
addClass :: Text -> FieldSettings site -> FieldSettings site
addClass = addAttr "class"
addClasses :: [Text] -> FieldSettings site -> FieldSettings site
addClasses = addAttrs "class"
addName :: Text -> FieldSettings site -> FieldSettings site
addName nm fs = fs { fsName = Just nm }
addNameClass :: Text -> Text -> FieldSettings site -> FieldSettings site
addNameClass gName gClass fs = fs { fsName= Just gName, fsAttrs=("class",gClass):(fsAttrs fs) }
addIdClass :: Text -> Text -> FieldSettings site -> FieldSettings site
addIdClass gId gClass fs = fs { fsId= Just gId, fsAttrs=("class",gClass):(fsAttrs fs) }
setClass :: FieldSettings site -> Text -> FieldSettings site -- deprecated
setClass fs c = fs { fsAttrs=("class",c):(fsAttrs fs) }
setNameClass :: FieldSettings site -> Text -> Text -> FieldSettings site -- deprecated
setNameClass fs gName gClass = fs { fsName= Just gName, fsAttrs=("class",gClass):(fsAttrs fs) }
setTooltip :: String -> FieldSettings site -> FieldSettings site
setTooltip tt fs
| null tt = fs { fsTooltip = Nothing }
| otherwise = fs { fsTooltip = Just $ fromString tt
, fsAttrs=("data-tooltip",fromString tt):(fsAttrs fs) }
optionsPersistCryptoId :: forall site backend a msg. optionsPersistCryptoId :: forall site backend a msg.
( YesodPersist site ( YesodPersist site
, PersistQueryRead backend , PersistQueryRead backend

View File

@ -3,6 +3,8 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiWayIf #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Settings are centralized, as much as possible, into this file. This -- | Settings are centralized, as much as possible, into this file. This
-- includes database connection settings, static file locations, etc. -- includes database connection settings, static file locations, etc.
-- In addition, you can configure a number of different aspects of Yesod -- In addition, you can configure a number of different aspects of Yesod
@ -14,6 +16,7 @@ import ClassyPrelude.Yesod
import qualified Control.Exception as Exception import qualified Control.Exception as Exception
import Data.Aeson (Result (..), fromJSON, withObject, import Data.Aeson (Result (..), fromJSON, withObject,
(.!=), (.:?)) (.!=), (.:?))
import Data.Aeson.TH
import Data.FileEmbed (embedFile) import Data.FileEmbed (embedFile)
import Data.Yaml (decodeEither') import Data.Yaml (decodeEither')
import Database.Persist.Postgresql (PostgresConf) import Database.Persist.Postgresql (PostgresConf)
@ -24,6 +27,10 @@ import Yesod.Default.Util (WidgetFileSettings,
widgetFileNoReload, widgetFileNoReload,
widgetFileReload) widgetFileReload)
import qualified Data.Text.Encoding as Text
import qualified Ldap.Client as Ldap
import Model import Model
-- | Runtime settings to configure this application. These settings can be -- | Runtime settings to configure this application. These settings can be
@ -34,6 +41,7 @@ data AppSettings = AppSettings
-- ^ Directory from which to serve static files. -- ^ Directory from which to serve static files.
, appDatabaseConf :: PostgresConf , appDatabaseConf :: PostgresConf
-- ^ Configuration settings for accessing the database. -- ^ Configuration settings for accessing the database.
, appLdapConf :: Maybe LdapConf
, appRoot :: Maybe Text , appRoot :: Maybe Text
-- ^ Base for all generated URLs. If @Nothing@, determined -- ^ Base for all generated URLs. If @Nothing@, determined
-- from the request headers. -- from the request headers.
@ -45,11 +53,6 @@ data AppSettings = AppSettings
-- ^ Get the IP address from the header when logging. Useful when sitting -- ^ Get the IP address from the header when logging. Useful when sitting
-- behind a reverse proxy. -- behind a reverse proxy.
, appLDAPURI :: String
, appLDAPDN :: String
, appLDAPPw :: String
, appLDAPBaseName :: Maybe String
, appDetailedRequestLogging :: Bool , appDetailedRequestLogging :: Bool
-- ^ Use detailed request logging system -- ^ Use detailed request logging system
, appShouldLogAll :: Bool , appShouldLogAll :: Bool
@ -83,6 +86,35 @@ data AppSettings = AppSettings
} }
data LdapConf = LdapConf
{ ldapHost :: Ldap.Host, ldapPort :: Ldap.PortNumber
, ldapDn :: Ldap.Dn, ldapPassword :: Ldap.Password
, ldapBase :: Ldap.Dn
, ldapScope :: Ldap.Scope
, ldapTimeout :: Int32
}
deriveFromJSON defaultOptions ''Ldap.Scope
instance FromJSON LdapConf where
parseJSON = withObject "LdapConf" $ \o -> do
ldapTls <- o .:? "tls"
tlsSettings <- case ldapTls :: Maybe String of
Just spec
| spec == "insecure" -> return $ Just Ldap.insecureTlsSettings
| spec == "default" -> return $ Just Ldap.defaultTlsSettings
| null spec -> return Nothing
Nothing -> return Nothing
_otherwise -> fail "Could not parse LDAP TLSSettings"
ldapHost <- maybe Ldap.Plain (flip Ldap.Tls) tlsSettings <$> o .: "host"
ldapPort <- (fromIntegral :: Int -> Ldap.PortNumber) <$> o .: "port"
ldapDn <- Ldap.Dn <$> o .: "user"
ldapPassword <- Ldap.Password . Text.encodeUtf8 <$> o .: "pass"
ldapBase <- Ldap.Dn <$> o .: "baseDN"
ldapScope <- o .: "scope"
ldapTimeout <- o .: "timeout"
return LdapConf{..}
instance FromJSON AppSettings where instance FromJSON AppSettings where
parseJSON = withObject "AppSettings" $ \o -> do parseJSON = withObject "AppSettings" $ \o -> do
let defaultDev = let defaultDev =
@ -93,14 +125,15 @@ instance FromJSON AppSettings where
#endif #endif
appStaticDir <- o .: "static-dir" appStaticDir <- o .: "static-dir"
appDatabaseConf <- o .: "database" appDatabaseConf <- o .: "database"
let nonEmptyHost LdapConf{..} = case ldapHost of
Ldap.Tls host _ -> not $ null host
Ldap.Plain host -> not $ null host
appLdapConf <- (>>= (\c -> c <$ guard (nonEmptyHost c))) <$> o .:? "ldap"
appRoot <- o .:? "approot" appRoot <- o .:? "approot"
appHost <- fromString <$> o .: "host" appHost <- fromString <$> o .: "host"
appPort <- o .: "port" appPort <- o .: "port"
appIpFromHeader <- o .: "ip-from-header" appIpFromHeader <- o .: "ip-from-header"
( appLDAPURI, appLDAPDN, appLDAPPw, appLDAPBaseName )
<- (=<< o .: "ldap") . withObject "LDAP" $ \obj -> (,,,) <$> obj .: "uri" <*> obj .: "dn" <*> obj .: "password" <*> obj .:? "basename"
appDetailedRequestLogging <- o .:? "detailed-logging" .!= defaultDev appDetailedRequestLogging <- o .:? "detailed-logging" .!= defaultDev
appShouldLogAll <- o .:? "should-log-all" .!= defaultDev appShouldLogAll <- o .:? "should-log-all" .!= defaultDev
appReloadTemplates <- o .:? "reload-templates" .!= defaultDev appReloadTemplates <- o .:? "reload-templates" .!= defaultDev

190
src/Utils/Form.hs Normal file
View File

@ -0,0 +1,190 @@
{-# LANGUAGE NoImplicitPrelude
, TemplateHaskell
, ViewPatterns
, OverloadedStrings
, QuasiQuotes
, TemplateHaskell
, MultiParamTypeClasses
, TypeFamilies
, FlexibleContexts
, NamedFieldPuns
, ScopedTypeVariables
#-}
module Utils.Form where
import ClassyPrelude.Yesod
import Settings
import qualified Text.Blaze.Internal as Blaze (null)
import qualified Data.Text as T
import qualified Data.Char as Char
import Web.PathPieces
-------------------
-- Form Renderer --
-------------------
-- | Use this type to pass information to the form template
data FormLayout = FormStandard
renderAForm :: Monad m => FormLayout -> FormRender m a
renderAForm formLayout aform fragment = do
(res, (($ []) -> views)) <- aFormToForm aform
let widget = $(widgetFile "widgets/form")
return (res, widget)
--------------------
-- Field Settings --
--------------------
fsl :: Text -> FieldSettings site
fsl lbl =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = []
}
fslI :: RenderMessage site msg => msg -> FieldSettings site
fslI lbl =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = []
}
fslp :: Text -> Text -> FieldSettings site
fslp lbl placeholder =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = [("placeholder", placeholder)]
}
fslpI :: RenderMessage site msg => msg -> Text -> FieldSettings site
fslpI lbl placeholder =
FieldSettings { fsLabel = (SomeMessage lbl)
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs = [("placeholder", placeholder)]
}
addAttr :: Text -> Text -> FieldSettings site -> FieldSettings site
addAttr attr valu fs = fs { fsAttrs=newAttrs (fsAttrs fs) }
where
newAttrs :: [(Text,Text)] -> [(Text,Text)]
newAttrs [] = [(attr,valu)]
newAttrs (p@(a,v):t)
| attr==a = (a,T.append valu $ cons ' ' v):t
| otherwise = p:(newAttrs t)
addAttrs :: Text -> [Text] -> FieldSettings site -> FieldSettings site
addAttrs attr valus fs = fs { fsAttrs=newAttrs (fsAttrs fs) }
where
newAttrs :: [(Text,Text)] -> [(Text,Text)]
newAttrs [] = [(attr,T.intercalate " " valus)]
newAttrs (p@(a,v):t)
| attr==a = (a,T.intercalate " " (v:valus)):t
| otherwise = p:(newAttrs t)
addClass :: Text -> FieldSettings site -> FieldSettings site
addClass = addAttr "class"
addClasses :: [Text] -> FieldSettings site -> FieldSettings site
addClasses = addAttrs "class"
addName :: Text -> FieldSettings site -> FieldSettings site
addName nm fs = fs { fsName = Just nm }
addNameClass :: Text -> Text -> FieldSettings site -> FieldSettings site
addNameClass gName gClass fs = fs { fsName= Just gName, fsAttrs=("class",gClass):(fsAttrs fs) }
addIdClass :: Text -> Text -> FieldSettings site -> FieldSettings site
addIdClass gId gClass fs = fs { fsId= Just gId, fsAttrs=("class",gClass):(fsAttrs fs) }
setClass :: FieldSettings site -> Text -> FieldSettings site -- deprecated
setClass fs c = fs { fsAttrs=("class",c):(fsAttrs fs) }
setNameClass :: FieldSettings site -> Text -> Text -> FieldSettings site -- deprecated
setNameClass fs gName gClass = fs { fsName= Just gName, fsAttrs=("class",gClass):(fsAttrs fs) }
setTooltip :: RenderMessage site msg => msg -> FieldSettings site -> FieldSettings site
setTooltip msg fs = fs { fsTooltip = Just $ SomeMessage msg }
------------------------------------------------
-- Unique Form Identifiers to avoid accidents --
------------------------------------------------
data FormIdentifier = FIDcourse | FIDsheet | FIDsubmission | FIDsettings | FIDcorrectors | FIDcorrectorTable | FIDcorrection | FIDcorrectionsUpload | FIDcorrectionUpload
deriving (Enum, Eq, Ord, Bounded, Read, Show)
instance PathPiece FormIdentifier where
fromPathPiece = readFromPathPiece
toPathPiece = showToPathPiece
identForm :: (Monad m, PathPiece ident)
=> ident -- ^ Form identification
-> (Html -> MForm m (FormResult a, WidgetT (HandlerSite m) IO ()))
-> (Html -> MForm m (FormResult a, WidgetT (HandlerSite m) IO ()))
identForm = identifyForm . toPathPiece
{- Hinweise zur Erinnerung:
- identForm primär, wenn es mehr als ein Formular pro Handler gibt
- nur einmal pro makeForm reicht
-}
----------------------------
-- Buttons (new version ) --
----------------------------
data family ButtonCssClass site :: *
bcc2txt :: Show (ButtonCssClass site) => ButtonCssClass site -> Text -- a Hack; maybe define Read/Show manually
bcc2txt bcc = T.pack $ "btn-" ++ (Char.toLower <$> (drop 2 $ show bcc))
class (Enum a, Bounded a, Ord a, PathPiece a) => Button site a where
label :: a -> WidgetT site IO ()
label = toWidget . toPathPiece
cssClass :: a -> ButtonCssClass site
data SubmitButton = BtnSubmit
deriving (Enum, Eq, Ord, Bounded, Read, Show)
instance PathPiece SubmitButton where
toPathPiece = showToPathPiece
fromPathPiece = readFromPathPiece
buttonField :: forall site a. (Button site a, Show (ButtonCssClass site)) => a -> Field (HandlerT site IO) a -- already validates that the correct button press was received (result only neccessary for combinedButtonField)
buttonField btn = Field {fieldParse, fieldView, fieldEnctype}
where
fieldEnctype = UrlEncoded
fieldView fid name attrs _val _ = let
cssClass' :: ButtonCssClass site
cssClass' = cssClass btn
in [whamlet|
<button .btn .#{bcc2txt cssClass'} type=submit name=#{name} value=#{toPathPiece btn} *{attrs} ##{fid}>^{label btn}
|]
fieldParse [] _ = return $ Right Nothing
fieldParse [str] _
| str == toPathPiece btn = return $ Right $ Just btn
| otherwise = return $ Left "Wrong button value"
fieldParse _ _ = return $ Left "Multiple button values"
combinedButtonField :: (Button site a, Show (ButtonCssClass site)) => [a] -> AForm (HandlerT site IO) [Maybe a]
combinedButtonField btns = traverse b2f btns
where
b2f b = aopt (buttonField b) "" Nothing
submitButton :: (Button site SubmitButton, Show (ButtonCssClass site)) => AForm (HandlerT site IO) ()
submitButton = void $ combinedButtonField [BtnSubmit]

View File

@ -13,27 +13,17 @@ packages:
git: https://github.com/pngwjpgh/zip-stream.git git: https://github.com/pngwjpgh/zip-stream.git
commit: 9272bbed000928d500febad1cdc98d1da29d399e commit: 9272bbed000928d500febad1cdc98d1da29d399e
extra-dep: true extra-dep: true
- location:
git: https://github.com/mlitchard/yesod-auth-ldap.git
commit: 69e08ef687ab96df3352ff4267562135453c6f02
extra-dep: true
- location:
git: https://github.com/mlitchard/authenticate-ldap.git
commit: cc2770024766a8fa29d3086688df60aaf65fb954
extra-dep: true
- location: - location:
git: https://github.com/pngwjpgh/encoding.git git: https://github.com/pngwjpgh/encoding.git
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84 commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
extra-dep: true extra-dep: true
- location:
git: https://github.com/pngwjpgh/system-locale.git
commit: d803ce3607ac6813ac1a065acb423220f57dab3c
extra-dep: true
extra-deps: extra-deps:
- colonnade-1.2.0 - colonnade-1.2.0
- yesod-colonnade-1.2.0 - yesod-colonnade-1.2.0
- ldap-client-0.2.0
- conduit-resumablesink-0.2 - conduit-resumablesink-0.2
- uuid-crypto-1.4.0.0 - uuid-crypto-1.4.0.0
@ -42,6 +32,6 @@ extra-deps:
- cryptoids-types-0.0.0 - cryptoids-types-0.0.0
- cryptoids-class-0.0.0 - cryptoids-class-0.0.0
- LDAP-0.6.11 - system-locale-0.3.0.0
resolver: lts-10.5 resolver: lts-10.5

9
templates/login.hamlet Normal file
View File

@ -0,0 +1,9 @@
$forall AuthPlugin{..} <- plugins
$if apName == "LDAP"
<section>
<h2>_{MsgLDAPLoginTitle}
^{apLogin toParent}
$elseif apName == "dummy"
<section>
<h2>_{MsgDummyLoginTitle}
^{apLogin toParent}

View File

@ -0,0 +1,2 @@
<form method=POST action=@{toMaster $ PluginR "LDAP" []} enctype=#{loginEnctype}>
^{login}

View File

@ -0,0 +1,11 @@
^{csrf}
<table>
<tr>
<th>_{MsgCampusIdent}
<td>^{fvInput identView}
<tr>
<th>_{MsgCampusPassword}
<td>^{fvInput passwordView}
<tr>
<td colspan="2" style="text-align: right">
<button type="submit">_{MsgCampusSubmit}

View File

@ -8,3 +8,7 @@ $case formLayout
<label .form-group__label for=#{fvId view}>#{fvLabel view} <label .form-group__label for=#{fvId view}>#{fvLabel view}
<div .form-group__input> <div .form-group__input>
^{fvInput view} ^{fvInput view}
$maybe tooltip <- fvTooltip view
<div .js-tooltip>
<div .tooltip__handle>?
<div .tooltip__content>^{tooltip}