fix(downloads): do download links via redirect

This commit is contained in:
Gregor Kleen 2020-12-08 17:34:39 +01:00
parent 7854222a8d
commit 3ba41d8f24
19 changed files with 182 additions and 300 deletions

View File

@ -83,15 +83,6 @@ instance Yesod UniWorX where
Nothing -> getApprootText guessApproot app req Nothing -> getApprootText guessApproot app req
Just root -> root Just root -> root
urlParamRenderOverride app route params = do
rApproot <- case authoritiveApproot route of
ApprootDefault -> mzero
rApproot -> return rApproot
guard $ views _appRoot ($ ApprootDefault) app /= views _appRoot ($ rApproot) app
approotText <- app ^. _appRoot . to ($ rApproot)
let (ps, params') = renderRoute route
return . joinPath app approotText ps $ params ++ params'
makeSessionBackend = UniWorX.makeSessionBackend makeSessionBackend = UniWorX.makeSessionBackend
maximumContentLength app _ = app ^. _appMaximumContentLength maximumContentLength app _ = app ^. _appMaximumContentLength

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ yesodMiddleware :: ( BearerAuthSite UniWorX
, BackendCompatible SqlBackend (YesodPersistBackend UniWorX) , BackendCompatible SqlBackend (YesodPersistBackend UniWorX)
) )
=> HandlerFor UniWorX res -> HandlerFor UniWorX res => HandlerFor UniWorX res -> HandlerFor UniWorX res
yesodMiddleware = cacheControlMiddleware . storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . observeYesodCacheSizeMiddleware . languagesMiddleware appLanguages . headerMessagesMiddleware . securityMiddleware . normalizeRouteMiddleware . updateFavouritesMiddleware . setActiveAuthTagsMiddleware yesodMiddleware = cacheControlMiddleware . storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . observeYesodCacheSizeMiddleware . languagesMiddleware appLanguages . headerMessagesMiddleware . securityMiddleware . normalizeRouteMiddleware . updateFavouritesMiddleware . setActiveAuthTagsMiddleware . normalizeApprootMiddleware
where where
dryRunMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a dryRunMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a
dryRunMiddleware handler = do dryRunMiddleware handler = do
@ -135,6 +135,30 @@ yesodMiddleware = cacheControlMiddleware . storeBearerMiddleware . csrfMiddlewar
handler handler
cacheControlMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a cacheControlMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a
cacheControlMiddleware = (addHeader "Vary" "Accept, Accept-Language" *>) cacheControlMiddleware = (addHeader "Vary" "Accept, Accept-Language" *>)
normalizeApprootMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a
normalizeApprootMiddleware handler = maybeT handler $ do
route <- MaybeT getCurrentRoute
reqHost <- MaybeT $ W.requestHeaderHost <$> waiRequest
let rApproot = authoritiveApproot route
app <- getYesod
approotHost <- hoistMaybe $ approotScopeHost rApproot app
let doRedirect = do
url <- approotRender rApproot route
$logErrorS "normalizeApprootMiddleware" url
redirect url
if | approotHost /= reqHost
, rApproot /= ApprootUserGenerated
-> doRedirect
| approotHost /= reqHost -> do
resp <- try $ lift handler
$logErrorS "normalizeApprootMiddleware" $ tshow (is _Right resp, preview _Left resp)
case resp of
Right _ -> doRedirect
Left sc | is _HCRedirect sc -> throwM sc
Left _ -> doRedirect
| otherwise -> lift handler
updateFavourites :: forall m backend. updateFavourites :: forall m backend.
( MonadHandler m, HandlerSite m ~ UniWorX ( MonadHandler m, HandlerSite m ~ UniWorX
@ -175,8 +199,7 @@ routeNormalizers :: forall m backend.
, BearerAuthSite UniWorX , BearerAuthSite UniWorX
) => [Route UniWorX -> WriterT Any (ReaderT backend m) (Route UniWorX)] ) => [Route UniWorX -> WriterT Any (ReaderT backend m) (Route UniWorX)]
routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .) routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .)
[ normalizeApproot [ normalizeRender
, normalizeRender
, ncSchool , ncSchool
, ncAllocation , ncAllocation
, ncCourse , ncCourse
@ -195,12 +218,6 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
, verifyMaterialVideo , verifyMaterialVideo
] ]
where where
normalizeApproot route = (route <$) . runMaybeT $ do
reqHost <- MaybeT $ W.requestHeaderHost <$> waiRequest
approotHost <- MaybeT . getsYesod . approotScopeHost $ authoritiveApproot route
when (approotHost /= reqHost) $
tell $ Any True
normalizeRender :: Route UniWorX -> WriterT Any (ReaderT SqlReadBackend (HandlerFor UniWorX)) (Route UniWorX) normalizeRender :: Route UniWorX -> WriterT Any (ReaderT SqlReadBackend (HandlerFor UniWorX)) (Route UniWorX)
normalizeRender route = route <$ do normalizeRender route = route <$ do
YesodRequest{..} <- liftHandler getRequest YesodRequest{..} <- liftHandler getRequest

View File

@ -13,8 +13,6 @@ import qualified Data.Conduit.Combinators as C
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import Handler.Course.Show
data AllocationAddUserForm = AllocationAddUserForm data AllocationAddUserForm = AllocationAddUserForm
{ aauUser :: UserId { aauUser :: UserId
@ -121,7 +119,7 @@ allocationApplicationsForm aId courses FieldSettings{..} fvRequired = formToAFor
mApplicationTemplate <- runMaybeT $ do mApplicationTemplate <- runMaybeT $ do
guard hasApplicationTemplate guard hasApplicationTemplate
let Course{..} = course let Course{..} = course
liftHandler . runDB $ toTextUrl <=< withFileDownloadToken (courseRegisterTemplateSource courseTerm courseSchool courseShorthand) $ CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR toTextUrl $ CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR
over _2 (course, allocCourse, mApplicationTemplate, ) <$> applicationForm (Just aId) cId Nothing ApplicationFormMode{..} Nothing over _2 (course, allocCourse, mApplicationTemplate, ) <$> applicationForm (Just aId) cId Nothing ApplicationFormMode{..} Nothing
let appsRes = sequenceA $ view _1 <$> appsRes' let appsRes = sequenceA $ view _1 <$> appsRes'
appsViews = view _2 <$> appsRes' appsViews = view _2 <$> appsRes'

View File

@ -141,7 +141,7 @@ applicationForm maId@(is _Just -> isAlloc) cid muid ApplicationFormMode{..} mcsr
appFilesInfo <- for mApp $ \(Entity appId _) -> liftHandler . runDB $ do appFilesInfo <- for mApp $ \(Entity appId _) -> liftHandler . runDB $ do
hasFiles <- exists [ CourseApplicationFileApplication ==. appId ] hasFiles <- exists [ CourseApplicationFileApplication ==. appId ]
appCID <- encrypt appId appCID <- encrypt appId
appFilesLink <- toTextUrl <=< withFileDownloadToken (selectSource [ CourseApplicationFileApplication ==. appId ] []) $ CApplicationR courseTerm courseSchool courseShorthand appCID CAFilesR appFilesLink <- toTextUrl $ CApplicationR courseTerm courseSchool courseShorthand appCID CAFilesR
return (hasFiles, appFilesLink) return (hasFiles, appFilesLink)
let hasFiles = maybe False (view _1) appFilesInfo let hasFiles = maybe False (view _1) appFilesInfo

View File

@ -15,8 +15,6 @@ import Handler.Allocation.Application
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E import qualified Database.Esqueleto.Utils as E
import Handler.Course.Show
data NotifyNewCourseButton data NotifyNewCourseButton
= BtnNotifyNewCourseForceOn = BtnNotifyNewCourseForceOn
@ -178,7 +176,7 @@ postAShowR tid ssh ash = do
Just (Entity appId _) -> CApplicationR courseTerm courseSchool courseShorthand <$> encrypt appId <*> pure CAEditR Just (Entity appId _) -> CApplicationR courseTerm courseSchool courseShorthand <$> encrypt appId <*> pure CAEditR
mApplicationTemplate <- runMaybeT $ do mApplicationTemplate <- runMaybeT $ do
guard hasApplicationTemplate guard hasApplicationTemplate
liftHandler . runDB $ toTextUrl <=< withFileDownloadToken (courseRegisterTemplateSource courseTerm courseSchool courseShorthand) $ CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR toTextUrl $ CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR
let mApplyFormView' = view _1 <$> mApplyFormView let mApplyFormView' = view _1 <$> mApplyFormView
overrideVisible = not mayApply && is _Just mApp overrideVisible = not mayApply && is _Just mApp

View File

@ -104,7 +104,7 @@ courseRegisterForm (Entity cid Course{..}) = liftHandler $ do
appFilesInfo <- for application $ \(Entity appId _) -> liftHandler . runDB $ do appFilesInfo <- for application $ \(Entity appId _) -> liftHandler . runDB $ do
hasFiles <- exists [ CourseApplicationFileApplication ==. appId ] hasFiles <- exists [ CourseApplicationFileApplication ==. appId ]
appCID <- encrypt appId appCID <- encrypt appId
appFilesLink <- toTextUrl <=< withFileDownloadToken (selectSource [ CourseApplicationFileApplication ==. appId ] []) $ CApplicationR courseTerm courseSchool courseShorthand appCID CAFilesR appFilesLink <- toTextUrl $ CApplicationR courseTerm courseSchool courseShorthand appCID CAFilesR
return (hasFiles, appFilesLink) return (hasFiles, appFilesLink)
let hasFiles = maybe False (view _1) appFilesInfo let hasFiles = maybe False (view _1) appFilesInfo
filesMsg = bool MsgCourseRegistrationFiles MsgCourseApplicationFiles courseApplicationsRequired filesMsg = bool MsgCourseRegistrationFiles MsgCourseApplicationFiles courseApplicationsRequired

View File

@ -25,8 +25,6 @@ import qualified Data.Conduit.List as C
import Handler.Exam.List (mkExamTable) import Handler.Exam.List (mkExamTable)
import Handler.Course.News.Download
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCShowR tid ssh csh = do getCShowR tid ssh csh = do
@ -80,7 +78,7 @@ getCShowR tid ssh csh = do
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. E.val cid E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. E.val cid
mApplicationTemplate <- runMaybeT $ do mApplicationTemplate <- runMaybeT $ do
guard hasApplicationTemplate guard hasApplicationTemplate
lift . lift $ toTextUrl <=< withFileDownloadToken (courseRegisterTemplateSource tid ssh csh) $ CourseR tid ssh csh CRegisterTemplateR lift . lift . toTextUrl $ CourseR tid ssh csh CRegisterTemplateR
mApplication <- lift . fmap (listToMaybe =<<) . for mbAid $ \uid -> selectList [CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid, CourseApplicationAllocation ==. Nothing] [] mApplication <- lift . fmap (listToMaybe =<<) . for mbAid $ \uid -> selectList [CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid, CourseApplicationAllocation ==. Nothing] []
news' <- lift $ selectList [ CourseNewsCourse ==. cid ] [ Desc CourseNewsVisibleFrom, Desc CourseNewsTitle, Desc CourseNewsSummary, Desc CourseNewsContent ] news' <- lift $ selectList [ CourseNewsCourse ==. cid ] [ Desc CourseNewsVisibleFrom, Desc CourseNewsTitle, Desc CourseNewsSummary, Desc CourseNewsContent ]
cTime <- NTop . Just <$> liftIO getCurrentTime cTime <- NTop . Just <$> liftIO getCurrentTime
@ -98,8 +96,8 @@ getCShowR tid ssh csh = do
mayEditNews <- hasWriteAccessTo $ CNewsR tid ssh csh cID CNEditR mayEditNews <- hasWriteAccessTo $ CNewsR tid ssh csh cID CNEditR
mayDelete <- hasWriteAccessTo $ CNewsR tid ssh csh cID CNDeleteR mayDelete <- hasWriteAccessTo $ CNewsR tid ssh csh cID CNDeleteR
files <- lift . lift $ forM files'' $ \f@(_isDir, fPath) -> fmap (f ,) . toTextUrl <=< withFileDownloadToken (cnFileSource nId fPath) . CNewsR tid ssh csh cID $ CNFileR fPath files <- lift . lift $ forM files'' $ \f@(_isDir, fPath) -> fmap (f ,) . toTextUrl . CNewsR tid ssh csh cID $ CNFileR fPath
archiveUrl <- lift . lift $ toTextUrl <=< withFileDownloadToken (cnArchiveSource nId) $ CNewsR tid ssh csh cID CNArchiveR archiveUrl <- lift . lift . toTextUrl $ CNewsR tid ssh csh cID CNArchiveR
return (cID, n, visible, files, lastEditText, mayEditNews, mayDelete, archiveUrl) return (cID, n, visible, files, lastEditText, mayEditNews, mayDelete, archiveUrl)

View File

@ -83,9 +83,8 @@ getMaterialListR tid ssh csh = do
let matLink :: MaterialName -> Route UniWorX let matLink :: MaterialName -> Route UniWorX
matLink = CourseR tid ssh csh . flip MaterialR MShowR matLink = CourseR tid ssh csh . flip MaterialR MShowR
filesLink :: (MonadHandler m, HandlerSite m ~ UniWorX) filesLink :: MaterialName -> SomeRoute UniWorX
=> MaterialName -> m (SomeRoute UniWorX) filesLink mnm = SomeRoute . CourseR tid ssh csh $ MaterialR mnm MArchiveR
filesLink mnm = liftHandler . runDB $ withFileDownloadToken (materialArchiveSource tid ssh csh mnm) . CourseR tid ssh csh $ MaterialR mnm MArchiveR
materialModDateCell :: IsDBTable m a => Material -> DBCell m a materialModDateCell :: IsDBTable m a => Material -> DBCell m a
materialModDateCell Material{materialVisibleFrom, materialLastEdit} materialModDateCell Material{materialVisibleFrom, materialLastEdit}
@ -124,7 +123,7 @@ getMaterialListR tid ssh csh = do
, sortable (toNothingS "zip-archive") (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgMaterialFiles)) , sortable (toNothingS "zip-archive") (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgMaterialFiles))
$ \DBRow{ dbrOutput = (Entity _ Material{..}, E.Value fileNum) } -> if $ \DBRow{ dbrOutput = (Entity _ Material{..}, E.Value fileNum) } -> if
| fileNum == 0 -> mempty | fileNum == 0 -> mempty
| otherwise -> anchorCellM (filesLink materialName) iconFileDownload | otherwise -> anchorCell (filesLink materialName) iconFileDownload
, sortable (Just "visible-from") (i18nCell MsgAccessibleSince) , sortable (Just "visible-from") (i18nCell MsgAccessibleSince)
$ foldMap (dateTimeCellVisible now) . materialVisibleFrom . row2material $ foldMap (dateTimeCellVisible now) . materialVisibleFrom . row2material
, sortable (Just "last-edit") (i18nCell MsgFileModified) , sortable (Just "last-edit") (i18nCell MsgFileModified)
@ -177,10 +176,10 @@ getMFileR tid ssh csh mnm title = serveOneFile $ fileQuery .| C.map entityVal
getMVideoR :: TermId -> SchoolId -> CourseShorthand -> MaterialName -> CryptoUUIDMaterialFile -> Handler Html getMVideoR :: TermId -> SchoolId -> CourseShorthand -> MaterialName -> CryptoUUIDMaterialFile -> Handler Html
getMVideoR tid ssh csh mnm cID = do getMVideoR tid ssh csh mnm cID = do
mfId <- decrypt cID mfId <- decrypt cID
mf@MaterialFile{..} <- runDB $ get404 mfId MaterialFile{..} <- runDB $ get404 mfId
let mimeType = mimeLookup $ pack materialFileTitle let mimeType = mimeLookup $ pack materialFileTitle
mfile <- withFileDownloadToken (views (_FileReference . _1) yield mf) . CMaterialR tid ssh csh mnm $ MFileR materialFileTitle mfile = CMaterialR tid ssh csh mnm $ MFileR materialFileTitle
let mfileDownload = mfile & over (urlRouteParams $ Proxy @UniWorX) (\params -> bool ((toPathPiece GetDownload, mempty) : ) id (anyOf (folded . _1) (== toPathPiece GetDownload) params) params) let mfileDownload = mfile & over (urlRouteParams $ Proxy @UniWorX) (\params -> bool ((toPathPiece GetDownload, toPathPiece True) : ) id (anyOf (folded . _1) (== toPathPiece GetDownload) params) params)
mfileText <- toTextUrl mfile mfileText <- toTextUrl mfile
mfileDownloadText <- toTextUrl mfileDownload mfileDownloadText <- toTextUrl mfileDownload
unless (mimeType `Set.member` videoTypes) $ unless (mimeType `Set.member` videoTypes) $
@ -206,7 +205,7 @@ getMShowR tid ssh csh mnm = do
seeAllModificationTimestamps <- hasReadAccessTo $ CourseR tid ssh csh CNotesR -- ordinary users should not see modification dates older than visibility seeAllModificationTimestamps <- hasReadAccessTo $ CourseR tid ssh csh CNotesR -- ordinary users should not see modification dates older than visibility
(Entity _mid material@Material{materialType, materialDescription}, (Any hasFiles,fileTable), zipLink) <- runDB $ do (Entity _mid material@Material{materialType, materialDescription}, (Any hasFiles,fileTable), zipLink) <- runDB $ do
zipLink <- withFileDownloadToken (materialArchiveSource tid ssh csh mnm) $ CMaterialR tid ssh csh mnm MArchiveR let zipLink = CMaterialR tid ssh csh mnm MArchiveR
matEnt <- fetchMaterial tid ssh csh mnm matEnt <- fetchMaterial tid ssh csh mnm
let materialModDateCol :: (IsDBTable m c) => (t -> E.Value UTCTime) -> Colonnade Sortable t (DBCell m c) let materialModDateCol :: (IsDBTable m c) => (t -> E.Value UTCTime) -> Colonnade Sortable t (DBCell m c)
materialModDateCol = if seeAllModificationTimestamps materialModDateCol = if seeAllModificationTimestamps
@ -220,12 +219,12 @@ getMShowR tid ssh csh mnm = do
return matFile return matFile
, dbtRowKey = (E.^. MaterialFileId) , dbtRowKey = (E.^. MaterialFileId)
, dbtColonnade = widgetColonnade $ mconcat , dbtColonnade = widgetColonnade $ mconcat
[ fmap (<> indicatorCell) . sortable (Just "path") (i18nCell MsgFileTitle) $ \(dbrOutput -> Entity mfId mf@MaterialFile{..}) [ fmap (<> indicatorCell) . sortable (Just "path") (i18nCell MsgFileTitle) $ \(dbrOutput -> Entity mfId MaterialFile{..})
-> let matLink -> let matLink
| isVideo | isVideo
= SomeRoute . CourseR tid ssh csh . MaterialR mnm . MVideoR <$> encrypt mfId = CourseR tid ssh csh . MaterialR mnm . MVideoR <$> encrypt mfId
| otherwise | otherwise
= withFileDownloadToken (views (_FileReference . _1) yield mf) . CMaterialR tid ssh csh mnm $ MFileR materialFileTitle = pure . CMaterialR tid ssh csh mnm $ MFileR materialFileTitle
wgt = [whamlet| wgt = [whamlet|
$newline never $newline never
<span .file-path> <span .file-path>

View File

@ -54,10 +54,10 @@ getSheetListR tid ssh csh = do
[ icnCell & addIconFixedWidth [ icnCell & addIconFixedWidth
| let existingSFTs = hasSFT existFiles | let existingSFTs = hasSFT existFiles
, sft <- [minBound..maxBound] , sft <- [minBound..maxBound]
, let link = liftHandler . runDB . withFileDownloadToken (sheetFilesAllQuery tid ssh csh sheetName muid sft) . CSheetR tid ssh csh sheetName $ SZipR sft , let link = CSheetR tid ssh csh sheetName $ SZipR sft
, let icn = toWgt $ sheetFile2markup sft , let icn = toWgt $ sheetFile2markup sft
, let icnCell = if sft `elem` existingSFTs , let icnCell = if sft `elem` existingSFTs
then linkEitherCellM link (icn, [whamlet|&emsp;|]) then linkEitherCell link (icn, [whamlet|&emsp;|])
else spacerCell else spacerCell
] id & cellAttrs <>~ [("class","list--inline list--space-separated")] ] id & cellAttrs <>~ [("class","list--inline list--space-separated")]
, sortable (Just "submission-since") (i18nCell MsgSheetActiveFrom) , sortable (Just "submission-since") (i18nCell MsgSheetActiveFrom)

View File

@ -50,14 +50,14 @@ getSShowR tid ssh csh shn = do
) )
let colonnadeFiles = widgetColonnade $ mconcat let colonnadeFiles = widgetColonnade $ mconcat
[ sortable (Just "type") (i18nCell MsgSheetFileTypeHeader) $ \(_,_, E.Value ftype, _) -> [ sortable (Just "type") (i18nCell MsgSheetFileTypeHeader) $ \(_,_, E.Value ftype, _) ->
let link = liftHandler . runDB . withFileDownloadToken (sheetFilesAllQuery tid ssh csh shn muid ftype) . CSheetR tid ssh csh shn $ SZipR ftype let link = CSheetR tid ssh csh shn $ SZipR ftype
in tellCell (Any True) $ in tellCell (Any True) $
anchorCellM link [whamlet|#{sheetFile2markup ftype} _{ftype}|] anchorCell link [whamlet|#{sheetFile2markup ftype} _{ftype}|]
-- i18nCell ftype & cellContents %~ (\act -> act <* tell (Any True)) -- i18nCell ftype & cellContents %~ (\act -> act <* tell (Any True))
-- , colFilePath (view _1) (\row -> let fType = view _3 row in let fName = view _1 row in (CSheetR tid ssh csh shn (SFileR (E.unValue fType) (E.unValue fName)))) -- , colFilePath (view _1) (\row -> let fType = view _3 row in let fName = view _1 row in (CSheetR tid ssh csh shn (SFileR (E.unValue fType) (E.unValue fName))))
, sortable (Just "path") (i18nCell MsgFileTitle) $ \(E.Value fName,E.Value fMod,E.Value fType, E.Value fRef) -> anchorCellM , sortable (Just "path") (i18nCell MsgFileTitle) $ \(E.Value fName, _, E.Value fType, _) -> anchorCell
(withFileDownloadToken (yield $ FileReference fName fRef fMod) $ CSheetR tid ssh csh shn (SFileR fType fName)) (CSheetR tid ssh csh shn $ SFileR fType fName)
(str2widget fName) (str2widget fName)
, sortable (toNothing "visible") (i18nCell MsgVisibleFrom) , sortable (toNothing "visible") (i18nCell MsgVisibleFrom)
$ \(_, _ , E.Value ftype, _) -> sftVisible ftype $ \(_, _ , E.Value ftype, _) -> sftVisible ftype

View File

@ -17,8 +17,6 @@ import qualified Control.Monad.State.Class as State
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E import qualified Database.Esqueleto.Utils as E
import Handler.Submission.Download
correctionData :: TermId -> SchoolId -> CourseShorthand -> SheetName -> _ -- CryptoFileNameSubmission -> _ correctionData :: TermId -> SchoolId -> CourseShorthand -> SheetName -> _ -- CryptoFileNameSubmission -> _
correctionData tid ssh csh shn sub = E.select . E.from $ \((course `E.InnerJoin` sheet `E.InnerJoin` submission) `E.LeftOuterJoin` corrector) -> do correctionData tid ssh csh shn sub = E.select . E.from $ \((course `E.InnerJoin` sheet `E.InnerJoin` submission) `E.LeftOuterJoin` corrector) -> do
@ -146,7 +144,7 @@ postCorrectionR tid ssh csh shn cid = do
|] |]
siteLayout headingWgt $ do siteLayout headingWgt $ do
setTitleI heading setTitleI heading
urlArchive <- toTextUrl <=< liftHandler . runDB . withFileDownloadToken' (subArchiveSource tid ssh csh shn cid SubmissionCorrected) . CSubmissionR tid ssh csh shn cid $ SubArchiveR SubmissionCorrected urlArchive <- toTextUrl . CSubmissionR tid ssh csh shn cid $ SubArchiveR SubmissionCorrected
let userCorrection = $(widgetFile "correction-user") let userCorrection = $(widgetFile "correction-user")
$(widgetFile "correction") $(widgetFile "correction")
_ -> notFound _ -> notFound
@ -159,6 +157,6 @@ getCorrectionUserR tid ssh csh shn cid = do
[(Entity _ Course{..}, Entity _ Sheet{..}, Entity _ Submission{..}, corrector@(Just _), E.Value filesCorrected)] -> [(Entity _ Course{..}, Entity _ Sheet{..}, Entity _ Submission{..}, corrector@(Just _), E.Value filesCorrected)] ->
let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment
in defaultLayout $ do in defaultLayout $ do
urlArchive <- toTextUrl <=< liftHandler . runDB . withFileDownloadToken' (subArchiveSource courseTerm courseSchool courseShorthand sheetName cid SubmissionCorrected) . CSubmissionR courseTerm courseSchool courseShorthand sheetName cid $ SubArchiveR SubmissionCorrected urlArchive <- toTextUrl . CSubmissionR courseTerm courseSchool courseShorthand sheetName cid $ SubArchiveR SubmissionCorrected
$(widgetFile "correction-user") $(widgetFile "correction-user")
_ -> notFound _ -> notFound

View File

@ -27,8 +27,6 @@ import Text.Blaze (Markup)
import qualified Data.Aeson.Types as JSON import qualified Data.Aeson.Types as JSON
import Data.Aeson.Lens import Data.Aeson.Lens
import Handler.Submission.Download
import Handler.Submission.SubmissionUserInvite import Handler.Submission.SubmissionUserInvite
@ -491,12 +489,12 @@ submissionHelper tid ssh csh shn mcid = do
corrIsFile = fmap (isJust . submissionFileContent . entityVal) mCorr corrIsFile = fmap (isJust . submissionFileContent . entityVal) mCorr
Just isFile = origIsFile <|> corrIsFile Just isFile = origIsFile <|> corrIsFile
in if in if
| Just True <- origIsFile -> anchorCellM (subDownloadLink cid SubmissionOriginal fileTitle') [whamlet|#{fileTitle'}|] | Just True <- origIsFile -> anchorCell (subDownloadLink cid SubmissionOriginal fileTitle') [whamlet|#{fileTitle'}|]
| otherwise -> textCell $ bool (<> "/") id isFile fileTitle' | otherwise -> textCell $ bool (<> "/") id isFile fileTitle'
, guardOn showCorrection . sortable (toNothing "state") (i18nCell MsgCorState) $ \(_, mCorr) -> case mCorr of , guardOn showCorrection . sortable (toNothing "state") (i18nCell MsgCorState) $ \(_, mCorr) -> case mCorr of
Nothing -> cell mempty Nothing -> cell mempty
Just (Entity _ SubmissionFile{..}) Just (Entity _ SubmissionFile{..})
| isJust submissionFileContent -> anchorCellM (subDownloadLink cid SubmissionCorrected submissionFileTitle) (i18n MsgFileCorrected :: Widget) | isJust submissionFileContent -> anchorCell (subDownloadLink cid SubmissionCorrected submissionFileTitle) (i18n MsgFileCorrected :: Widget)
| otherwise -> i18nCell MsgCorrected | otherwise -> i18nCell MsgCorrected
, Just . sortable (Just "time") (i18nCell MsgFileModified) $ \(mOrig, mCorr) -> let , Just . sortable (Just "time") (i18nCell MsgFileModified) $ \(mOrig, mCorr) -> let
origTime = submissionFileModified . entityVal <$> mOrig origTime = submissionFileModified . entityVal <$> mOrig
@ -504,8 +502,7 @@ submissionHelper tid ssh csh shn mcid = do
Just fileTime = (max <$> origTime <*> corrTime) <|> origTime <|> corrTime Just fileTime = (max <$> origTime <*> corrTime) <|> origTime <|> corrTime
in dateTimeCell fileTime in dateTimeCell fileTime
] ]
subDownloadLink :: _ -> _ -> _ -> WidgetFor UniWorX _ subDownloadLink cid sft fileTitle' = CSubmissionR tid ssh csh shn cid $ SubDownloadR sft fileTitle'
subDownloadLink cid sft fileTitle' = liftHandler . runDB . withFileDownloadToken (subDownloadSource tid ssh csh shn cid sft fileTitle') . CSubmissionR tid ssh csh shn cid $ SubDownloadR sft fileTitle'
submissionFiles :: _ -> _ -> E.SqlQuery _ submissionFiles :: _ -> _ -> E.SqlQuery _
submissionFiles smid (sf1 `E.FullOuterJoin` sf2) = do submissionFiles smid (sf1 `E.FullOuterJoin` sf2) = do
E.on $ sf1 E.?. SubmissionFileTitle E.==. sf2 E.?. SubmissionFileTitle E.on $ sf1 E.?. SubmissionFileTitle E.==. sf2 E.?. SubmissionFileTitle
@ -555,7 +552,7 @@ submissionHelper tid ssh csh shn mcid = do
defaultLayout $ do defaultLayout $ do
setTitleI $ MsgSubmissionEditHead tid ssh csh shn setTitleI $ MsgSubmissionEditHead tid ssh csh shn
(urlArchive, urlOriginal) <- fmap ((,) <$> preview (_Just . _1) <*> preview (_Just . _2)) . for mcid $ \cID (urlArchive, urlOriginal) <- fmap ((,) <$> preview (_Just . _1) <*> preview (_Just . _2)) . for mcid $ \cID
-> let mkUrl sft = toTextUrl <=< withFileDownloadToken' (subArchiveSource tid ssh csh shn cID sft) . CSubmissionR tid ssh csh shn cID $ SubArchiveR sft -> let mkUrl sft = toTextUrl . CSubmissionR tid ssh csh shn cID $ SubArchiveR sft
in liftHandler . runDB $ (,) <$> mkUrl SubmissionCorrected <*> mkUrl SubmissionOriginal in liftHandler . runDB $ (,) <$> mkUrl SubmissionCorrected <*> mkUrl SubmissionOriginal
let correctionWdgt = guardOnM (showCorrection && maybe False submissionRatingDone msubmission) ((,) <$> msubmission <*> mcid) <&> \(Submission{..}, cid) -> let correctionWdgt = guardOnM (showCorrection && maybe False submissionRatingDone msubmission) ((,) <$> msubmission <*> mcid) <&> \(Submission{..}, cid) ->
let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment let ratingComment = assertM (not . null) $ Text.strip <$> submissionRatingComment

View File

@ -18,6 +18,6 @@ downloadFiles = do
setContentDisposition' :: (MonadHandler m, HandlerSite m ~ UniWorX, YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId) => Maybe FilePath -> m () setContentDisposition' :: (MonadHandler m, HandlerSite m ~ UniWorX, YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId) => Maybe FilePath -> m ()
setContentDisposition' mFileName = do setContentDisposition' mFileName = do
wantsDownload <- or2M (hasGlobalGetParam GetDownload) downloadFiles wantsDownload <- maybeT downloadFiles . MaybeT $ lookupGlobalGetParam GetDownload
setContentDisposition (bool ContentInline ContentAttachment wantsDownload) mFileName setContentDisposition (bool ContentInline ContentAttachment wantsDownload) mFileName

View File

@ -1,6 +1,5 @@
module Handler.Utils.Download module Handler.Utils.Download
( withFileDownloadTokenMaybe', withFileDownloadToken, withFileDownloadToken' ( sendThisFile
, sendThisFile
, sendFileReference , sendFileReference
, serveOneFile , serveOneFile
, serveSomeFiles , serveSomeFiles
@ -22,6 +21,8 @@ import Handler.Utils.Zip
import Handler.Utils.ContentDisposition import Handler.Utils.ContentDisposition
import Handler.Utils.Files import Handler.Utils.Files
import qualified Network.Wai as W
data DownloadTokenRestriction data DownloadTokenRestriction
= DownloadRestrictSingle { downloadRestrictReference :: FileContentReference } = DownloadRestrictSingle { downloadRestrictReference :: FileContentReference }
@ -70,55 +71,90 @@ withFileDownloadTokenMaybe' mSource route = maybeT (return $ SomeRoute route) $
encodedBearer <- lift $ encodeBearer bearer encodedBearer <- lift $ encodeBearer bearer
lift . setDownload $ SomeRoute @UniWorX route lift . setDownload $ SomeRoute @UniWorX route
& over (urlRouteParams $ Proxy @UniWorX) ((toPathPiece GetBearer, toPathPiece encodedBearer) :) & over (urlRouteParams $ Proxy @UniWorX) (((toPathPiece GetBearer, toPathPiece encodedBearer) :) . filter (views _1 (maybe False (/= GetBearer) . fromPathPiece)))
where where
setDownload :: SomeRoute UniWorX -> m (SomeRoute UniWorX) setDownload :: SomeRoute UniWorX -> m (SomeRoute UniWorX)
setDownload route' = do setDownload route' = do
wantsDownload <- downloadFiles wantsDownload <- downloadFiles
defWantsDownload <- getsYesod $ views _appUserDefaults userDefaultDownloadFiles
let
addDownload params
| anyOf (folded . _1) (== toPathPiece GetDownload) params = params
| otherwise = (toPathPiece GetDownload, toPathPiece wantsDownload) : params
return $ route' return $ route'
& over (urlRouteParams $ Proxy @UniWorX) (bool id addDownload wantsDownload) & over (urlRouteParams $ Proxy @UniWorX) (bool id addDownload $ wantsDownload /= defWantsDownload)
where
addDownload params | anyOf (folded . _1) (== toPathPiece GetDownload) params = params
| otherwise = (toPathPiece GetDownload, mempty) : params
withFileDownloadToken' :: forall file url m. -- withFileDownloadToken' :: forall file url m.
( HasFileReference file -- ( HasFileReference file
, HasRoute UniWorX url -- , HasRoute UniWorX url
, MonadHandler m, HandlerSite m ~ UniWorX -- , MonadHandler m, HandlerSite m ~ UniWorX
, MonadCrypto m -- , MonadCrypto m
, MonadCryptoKey m ~ CryptoIDKey -- , MonadCryptoKey m ~ CryptoIDKey
, YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId -- , YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId
) -- )
=> ConduitT () (Either file DBFile) m () -- => ConduitT () (Either file DBFile) m ()
-> url -- -> url
-> m (SomeRoute UniWorX) -- -> m (SomeRoute UniWorX)
withFileDownloadToken' = withFileDownloadTokenMaybe' . Just . (.| C.map (first . view $ _FileReference . _1)) -- withFileDownloadToken' = withFileDownloadTokenMaybe' . Just . (.| C.map (first . view $ _FileReference . _1))
withFileDownloadToken :: forall file url m. -- withFileDownloadToken :: forall file url m.
( HasFileReference file -- ( HasFileReference file
, HasRoute UniWorX url -- , HasRoute UniWorX url
, MonadHandler m, HandlerSite m ~ UniWorX -- , MonadHandler m, HandlerSite m ~ UniWorX
, MonadCrypto m -- , MonadCrypto m
, MonadCryptoKey m ~ CryptoIDKey -- , MonadCryptoKey m ~ CryptoIDKey
, BearerAuthSite UniWorX -- , BearerAuthSite UniWorX
) -- )
=> ConduitT () file m () -- => ConduitT () file m ()
-> url -- -> url
-> m (SomeRoute UniWorX) -- -> m (SomeRoute UniWorX)
withFileDownloadToken = withFileDownloadToken' . (.| C.map Left) -- withFileDownloadToken = withFileDownloadToken' . (.| C.map Left)
ensureApprootUserGeneratedMaybe'
:: forall m.
( MonadHandler m, HandlerSite m ~ UniWorX
, MonadCrypto m
, MonadCryptoKey m ~ CryptoIDKey
, YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId
)
=> Maybe (ConduitT () (Either FileReference DBFile) m ())
-> m ()
ensureApprootUserGeneratedMaybe' source = maybeT (return ()) $ do
route <- (,) <$> MaybeT getCurrentRoute <*> fmap reqGetParams getRequest
$logErrorS "ensureApproot" $ tshow route
rApproot <- hoistMaybe <=< lift . runMaybeT $ do
reqHost <- MaybeT $ W.requestHeaderHost <$> waiRequest
let rApproot = authoritiveApproot $ urlRoute route
guard $ rApproot == ApprootUserGenerated
approotHost <- MaybeT . getsYesod $ approotScopeHost rApproot
guard $ approotHost /= reqHost
return rApproot
$logErrorS "ensureApproot" $ tshow rApproot
route' <- lift $ withFileDownloadTokenMaybe' source route
url <- approotRender rApproot route'
$logErrorS "ensureApprootUserGenerated" url
redirect url
-- | Simply send a `File`-Value -- | Simply send a `File`-Value
sendThisFile :: (YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId, YesodPersistRunner UniWorX) => DBFile -> HandlerFor UniWorX TypedContent sendThisFile :: ( YesodAuthPersist UniWorX
sendThisFile File{..} , AuthEntity UniWorX ~ User
| Just fileContent' <- fileContent = do , AuthId UniWorX ~ UserId
setCSPSandbox , YesodPersistRunner UniWorX
setContentDisposition' . Just $ takeFileName fileTitle , MonadCrypto (HandlerFor UniWorX), MonadCryptoKey (HandlerFor UniWorX) ~ CryptoIDKey
let cType = simpleContentType (mimeLookup $ pack fileTitle) <> "; charset=utf-8" ) => DBFile -> HandlerFor UniWorX TypedContent
respondSourceDB cType $ sendThisFile File{..} = do
fileContent' .| C.map toFlushBuilder ensureApprootUserGeneratedMaybe' Nothing
| otherwise = sendResponseStatus noContent204 () if
| Just fileContent' <- fileContent -> do
setCSPSandbox
setContentDisposition' . Just $ takeFileName fileTitle
let cType = simpleContentType (mimeLookup $ pack fileTitle) <> "; charset=utf-8"
respondSourceDB cType $
fileContent' .| C.map toFlushBuilder
| otherwise -> sendResponseStatus noContent204 ()
sendFileReference :: forall file a. sendFileReference :: forall file a.
( HasFileReference file ( HasFileReference file
@ -128,6 +164,7 @@ sendFileReference :: forall file a.
) )
=> file -> HandlerFor UniWorX a => file -> HandlerFor UniWorX a
sendFileReference (view (_FileReference . _1) -> fRef@FileReference{..}) = do sendFileReference (view (_FileReference . _1) -> fRef@FileReference{..}) = do
ensureApprootUserGeneratedMaybe' . Just . yield $ Left fRef
whenIsJust fileReferenceContent $ \fRef' -> do whenIsJust fileReferenceContent $ \fRef' -> do
dlRestr <- maybeCurrentBearerRestrictions dlRestr <- maybeCurrentBearerRestrictions
case dlRestr of case dlRestr of
@ -146,6 +183,7 @@ serveOneFile :: forall file.
) => ConduitT () file (YesodDB UniWorX) () -> HandlerFor UniWorX TypedContent ) => ConduitT () file (YesodDB UniWorX) () -> HandlerFor UniWorX TypedContent
serveOneFile source = do serveOneFile source = do
results <- runDB . runConduit $ source .| C.take 2 .| C.foldMap pure -- We don't need more than two files to make a decision below results <- runDB . runConduit $ source .| C.take 2 .| C.foldMap pure -- We don't need more than two files to make a decision below
ensureApprootUserGeneratedMaybe' . Just . yieldMany $ map (views (_FileReference . _1) Left) results
case results of case results of
[file] -> sendFileReference file [file] -> sendFileReference file
[] -> notFound [] -> notFound
@ -172,6 +210,7 @@ serveSomeFiles' :: forall file.
) => FilePath -> ConduitT () (Either file DBFile) (YesodDB UniWorX) () -> HandlerFor UniWorX TypedContent ) => FilePath -> ConduitT () (Either file DBFile) (YesodDB UniWorX) () -> HandlerFor UniWorX TypedContent
serveSomeFiles' archiveName source = do serveSomeFiles' archiveName source = do
(source', results) <- runDB $ runPeekN 2 source (source', results) <- runDB $ runPeekN 2 source
ensureApprootUserGeneratedMaybe' . Just . yieldMany $ over (traverse . _Left) (view $ _FileReference . _1) results
$logDebugS "serveSomeFiles" . tshow $ length results $logDebugS "serveSomeFiles" . tshow $ length results

View File

@ -25,7 +25,6 @@ import Handler.Utils.Form
import Handler.Utils.Widgets import Handler.Utils.Widgets
import Handler.Utils.DateTime import Handler.Utils.DateTime
import Handler.Utils.StudyFeatures import Handler.Utils.StudyFeatures
import Handler.Utils.Download
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
@ -376,7 +375,7 @@ colApplicationFiles resultInfo = Colonnade.singleton (fromSortable header) body
| showLink | showLink
-> flip anchorCellM (asWidgetT $ toWidget iconApplicationFiles) $ do -> flip anchorCellM (asWidgetT $ toWidget iconApplicationFiles) $ do
cID <- encrypt appId cID <- encrypt appId
liftHandler . runDB . withFileDownloadToken (selectSource [ CourseApplicationFileApplication ==. appId ] []) $ CApplicationR tid ssh csh cID CAFilesR return $ CApplicationR tid ssh csh cID CAFilesR
| otherwise | otherwise
-> mempty -> mempty

View File

@ -60,22 +60,6 @@ data WorkflowCurrentState = WorkflowCurrentState
makePrisms ''WorkflowHistoryItemActor' makePrisms ''WorkflowHistoryItemActor'
data OneOrMany a = None | One a | Many
deriving (Eq, Ord, Read, Show, Functor, Traversable, Foldable, Generic, Typeable)
instance Semigroup (OneOrMany a) where
None <> x = x
x <> None = x
_ <> _ = Many
instance Monoid (OneOrMany a) where
mempty = None
oneOrMany :: b -> (a -> b) -> b -> OneOrMany a -> b
oneOrMany onNone onOne onMany = \case
None -> onNone
One x -> onOne x
Many -> onMany
getGWWWorkflowR, postGWWWorkflowR :: CryptoFileNameWorkflowWorkflow -> Handler Html getGWWWorkflowR, postGWWWorkflowR :: CryptoFileNameWorkflowWorkflow -> Handler Html
getGWWWorkflowR = postGWWWorkflowR getGWWWorkflowR = postGWWWorkflowR
@ -198,13 +182,13 @@ workflowR rScope cID = do
<> (compareUnicode `on` userDisplayName) uA uB <> (compareUnicode `on` userDisplayName) uA uB
<> comparing userIdent uA uB <> comparing userIdent uA uB
(WFPUser{}, _ ) -> GT (WFPUser{}, _ ) -> GT
forM payload' $ \(lblText, (otoList -> payloads, fRoute)) -> fmap ((lblText, ) . over _1 (sortBy payloadSort)) . mapMOf _2 (traverse toTextUrl <=< oneOrMany (return Nothing) (\fRef -> Just <$> withFileDownloadToken (yield fRef) fRoute) (Just <$> withFileDownloadTokenMaybe' Nothing fRoute)) <=< execWriterT @_ @(_, OneOrMany FileReference). forM_ payloads $ \case forM payload' $ \(lblText, (otoList -> payloads, fRoute)) -> fmap ((lblText, ) . over _1 (sortBy payloadSort)) . mapMOf _2 (traverse toTextUrl . bool Nothing (Just fRoute) . getAny) <=< execWriterT @_ @(_, Any). forM_ payloads $ \case
WorkflowFieldPayloadW (WFPText t ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPText t) WorkflowFieldPayloadW (WFPText t ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPText t)
WorkflowFieldPayloadW (WFPNumber n ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPNumber n) WorkflowFieldPayloadW (WFPNumber n ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPNumber n)
WorkflowFieldPayloadW (WFPBool b ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPBool b) WorkflowFieldPayloadW (WFPBool b ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPBool b)
WorkflowFieldPayloadW (WFPDay d ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPDay d) WorkflowFieldPayloadW (WFPDay d ) -> tell . (, mempty) . pure $ WorkflowFieldPayloadW (WFPDay d)
WorkflowFieldPayloadW (WFPFile fRef) -> tell (mempty, One fRef) WorkflowFieldPayloadW (WFPFile _ ) -> tell (mempty, Any True)
WorkflowFieldPayloadW (WFPUser uid ) -> tell . (, mempty) . pure . review (_WorkflowFieldPayloadW . _WorkflowFieldPayload) =<< lift (lift . lift $ getEntity uid) WorkflowFieldPayloadW (WFPUser uid) -> tell . (, mempty) . pure . review (_WorkflowFieldPayloadW . _WorkflowFieldPayload) =<< lift (lift . lift $ getEntity uid)
payloadChanges <- State.state $ \oldPayload -> payloadChanges <- State.state $ \oldPayload ->
( Map.filterWithKey (\k v -> Map.findWithDefault Set.empty k oldPayload /= v) currentPayload ( Map.filterWithKey (\k v -> Map.findWithDefault Set.empty k oldPayload /= v) currentPayload

View File

@ -1,18 +1,44 @@
module Utils.Approot module Utils.Approot
( approotScopeHost ( approotScopeHost, approotScopeBaseUrl, approotScopeHost'
, approotRender
) where ) where
import ClassyPrelude import ClassyPrelude.Yesod hiding (Proxy)
import Settings import Settings
import Utils.Route
import Network.URI (URI(URI), URIAuth(URIAuth))
import qualified Network.URI as URI import qualified Network.URI as URI
import Control.Lens import Control.Lens
import Data.Proxy
approotScopeHost :: HasAppSettings site => ApprootScope -> site -> Maybe ByteString
approotScopeHost rApproot app = do approotScopeHost' :: HasAppSettings site => (URI -> URIAuth -> a) -> ApprootScope -> site -> Maybe a
approotScopeHost' f rApproot app = do
approotText <- views _appRoot ($ rApproot) app approotText <- views _appRoot ($ rApproot) app
approotURI <- URI.parseURI $ unpack approotText approotURI <- URI.parseURI $ unpack approotText
approotAuthority <- URI.uriAuthority approotURI approotAuthority <- URI.uriAuthority approotURI
return . encodeUtf8 . pack $ URI.uriRegName approotAuthority <> URI.uriPort approotAuthority return $ f approotURI approotAuthority
approotScopeHost :: HasAppSettings site => ApprootScope -> site -> Maybe ByteString
approotScopeHost = approotScopeHost' $ \_ URIAuth{..}
-> encodeUtf8 . pack $ uriRegName <> uriPort
approotScopeBaseUrl :: HasAppSettings site => ApprootScope -> site -> Maybe Text
approotScopeBaseUrl = approotScopeHost' $ \URI{..} URIAuth{..}
-> pack $ uriScheme <> "//" <> uriRegName <> uriPort
approotRender :: forall url m.
( HasAppSettings (HandlerSite m)
, MonadHandler m
, Yesod (HandlerSite m)
, HasRoute (HandlerSite m) url
)
=> ApprootScope -> url -> m Text
approotRender rApproot route = do
app <- getYesod
approotHost <- maybe (getApprootText approot app <$> waiRequest) return $ approotScopeBaseUrl rApproot app
return . yesodRender app approotHost (urlRoute route) . withLens (urlRouteParams (Proxy @(HandlerSite m))) $ \g _ -> g route

View File

@ -1,10 +1,13 @@
{-# LANGUAGE UndecidableInstances #-}
module Utils.Route where module Utils.Route where
import Control.Lens import Control.Lens
import ClassyPrelude.Yesod -- hiding (foldlM) import ClassyPrelude.Yesod hiding (Proxy)
import Data.Kind (Type) import Data.Kind (Type)
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Proxy
class RedirectUrl site url => HasRoute site url where class RedirectUrl site url => HasRoute site url where
@ -46,3 +49,5 @@ instance HasRoute site (SomeRoute site) where
type RouteWithParams site (SomeRoute site) = SomeRoute site type RouteWithParams site (SomeRoute site) = SomeRoute site
urlRoute (SomeRoute url) = urlRoute url urlRoute (SomeRoute url) = urlRoute url
urlRouteParams pSite = lens (\(SomeRoute url) -> withLens (urlRouteParams pSite) $ \g _ -> g url) (\(SomeRoute url) params -> SomeRoute (urlRoute url :: Route site, params)) urlRouteParams pSite = lens (\(SomeRoute url) -> withLens (urlRouteParams pSite) $ \g _ -> g url) (\(SomeRoute url) params -> SomeRoute (urlRoute url :: Route site, params))
instance Eq (Route site) => Eq (SomeRoute site) where
(==) = (==) `on` (\(SomeRoute r) -> withLens (urlRouteParams $ Proxy @site) $ \g _ -> (urlRoute r :: Route site, sort $ g r))