Better error handling and auth checking

This commit is contained in:
Michael Snoyman 2013-03-10 14:03:10 +02:00
parent dc79ddecd9
commit ee01aaf268
6 changed files with 51 additions and 53 deletions

View File

@ -291,13 +291,32 @@ $doctype 5
-- | A Yesod middleware, which will wrap every handler function. This -- | A Yesod middleware, which will wrap every handler function. This
-- allows you to run code before and after a normal handler. -- allows you to run code before and after a normal handler.
-- --
-- Default: Adds the response header \"Vary: Accept, Accept-Language\". -- Default: Adds the response header \"Vary: Accept, Accept-Language\" and
-- performs authorization checks.
-- --
-- Since: 1.1.6 -- Since: 1.1.6
yesodMiddleware :: GHandler sub a res -> GHandler sub a res yesodMiddleware :: GHandler sub a res -> GHandler sub a res
yesodMiddleware handler = do yesodMiddleware handler = do
setHeader "Vary" "Accept, Accept-Language" setHeader "Vary" "Accept, Accept-Language"
handler route <- getCurrentRoute
toMaster <- getRouteToMaster
case fmap toMaster route of
Nothing -> handler
Just url -> do
isWrite <- isWriteRequest url
ar <- isAuthorized url isWrite
case ar of
Authorized -> return ()
AuthenticationRequired -> do
master <- getYesod
case authRoute master of
Nothing ->
permissionDenied "Authentication required"
Just url' -> do
setUltDestCurrent
redirect url'
Unauthorized s' -> permissionDenied s'
handler
-- | Convert a widget to a 'PageContent'. -- | Convert a widget to a 'PageContent'.
widgetToPageContent :: (Eq (Route master), Yesod master) widgetToPageContent :: (Eq (Route master), Yesod master)

View File

@ -39,7 +39,6 @@ import Web.Cookie (renderSetCookie)
import Yesod.Content import Yesod.Content
import Yesod.Core.Class import Yesod.Core.Class
import Yesod.Core.Types import Yesod.Core.Types
import Yesod.Handler
import Yesod.Internal (tokenKey) import Yesod.Internal (tokenKey)
import Yesod.Internal.Request (parseWaiRequest, import Yesod.Internal.Request (parseWaiRequest,
tooLargeResponse) tooLargeResponse)
@ -118,9 +117,8 @@ runHandler rhe@RunHandlerEnv {..} handler yreq = do
let headers = ghsHeaders state let headers = ghsHeaders state
let contents = either id (HCContent H.status200 . chooseRep) contents' let contents = either id (HCContent H.status200 . chooseRep) contents'
let handleError e = do let handleError e = do
yar <- eh e yreq yar <- rheOnError e yreq
{ reqOnError = safeEh { reqSession = finalSession
, reqSession = finalSession
} }
case yar of case yar of
YRPlain _ hs ct c sess -> YRPlain _ hs ct c sess ->
@ -160,7 +158,6 @@ runHandler rhe@RunHandlerEnv {..} handler yreq = do
finalSession finalSession
HCWai r -> return $ YRWai r HCWai r -> return $ YRWai r
where where
eh = reqOnError yreq
cts = reqAccept yreq cts = reqAccept yreq
initSession = reqSession yreq initSession = reqSession yreq
@ -229,6 +226,7 @@ runFakeHandler fakeSessionMap logger master handler = liftIO $ do
, rheSub = master , rheSub = master
, rheUpload = fileUpload master , rheUpload = fileUpload master
, rheLog = messageLoggerSource master $ logger master , rheLog = messageLoggerSource master $ logger master
, rheOnError = errHandler
} }
handler' handler'
errHandler err req = do errHandler err req = do
@ -263,7 +261,6 @@ runFakeHandler fakeSessionMap logger master handler = liftIO $ do
, reqWaiRequest = fakeWaiRequest , reqWaiRequest = fakeWaiRequest
, reqLangs = [] , reqLangs = []
, reqToken = Just "NaN" -- not a nonce =) , reqToken = Just "NaN" -- not a nonce =)
, reqOnError = errHandler
, reqAccept = [] , reqAccept = []
, reqSession = fakeSessionMap , reqSession = fakeSessionMap
} }
@ -279,30 +276,16 @@ defaultYesodRunner YesodRunnerEnv {..} handler' req
| KnownLength len <- requestBodyLength req, maxLen < len = return tooLargeResponse | KnownLength len <- requestBodyLength req, maxLen < len = return tooLargeResponse
| otherwise = do | otherwise = do
let dontSaveSession _ = return [] let dontSaveSession _ = return []
let onError _ = error "FIXME: Yesod.Internal.Core.defaultYesodRunner.onError"
(session, saveSession) <- liftIO $ do (session, saveSession) <- liftIO $ do
maybe (return (Map.empty, dontSaveSession)) (\sb -> sbLoadSession sb yreMaster req) yreSessionBackend maybe (return (Map.empty, dontSaveSession)) (\sb -> sbLoadSession sb yreMaster req) yreSessionBackend
rr <- liftIO $ parseWaiRequest req session onError (isJust yreSessionBackend) maxLen <$> newStdGen yreq <- liftIO $ parseWaiRequest req session (isJust yreSessionBackend) maxLen <$> newStdGen
let h = {-# SCC "h" #-} do
case yreRoute of
Nothing -> handler
Just url -> do
isWrite <- isWriteRequest $ yreToMaster url
ar <- isAuthorized (yreToMaster url) isWrite
case ar of
Authorized -> return ()
AuthenticationRequired ->
case authRoute yreMaster of
Nothing ->
permissionDenied "Authentication required"
Just url' -> do
setUltDestCurrent
redirect url'
Unauthorized s' -> permissionDenied s'
handler
let ra = resolveApproot yreMaster req let ra = resolveApproot yreMaster req
let log' = messageLoggerSource yreMaster yreLogger let log' = messageLoggerSource yreMaster yreLogger
rhe = RunHandlerEnv -- We set up two environments: the first one has a "safe" error handler
-- which will never throw an exception. The second one uses the
-- user-provided errorHandler function. If that errorHandler function
-- errors out, it will use the safeEh below to recover.
rheSafe = RunHandlerEnv
{ rheRender = yesodRender yreMaster ra { rheRender = yesodRender yreMaster ra
, rheRoute = yreRoute , rheRoute = yreRoute
, rheToMaster = yreToMaster , rheToMaster = yreToMaster
@ -310,16 +293,18 @@ defaultYesodRunner YesodRunnerEnv {..} handler' req
, rheSub = yreSub , rheSub = yreSub
, rheUpload = fileUpload yreMaster , rheUpload = fileUpload yreMaster
, rheLog = log' , rheLog = log'
, rheOnError = safeEh
} }
yar <- runHandler rhe h rr rhe = rheSafe
{ reqOnError = runHandler rhe . localNoCurrent . errorHandler { rheOnError = runHandler rheSafe . localNoCurrent . errorHandler
} }
yar <- runHandler rhe handler yreq
extraHeaders <- case yar of extraHeaders <- case yar of
(YRPlain _ _ ct _ newSess) -> do (YRPlain _ _ ct _ newSess) -> do
let nsToken = maybe let nsToken = maybe
newSess newSess
(\n -> Map.insert tokenKey (encodeUtf8 n) newSess) (\n -> Map.insert tokenKey (encodeUtf8 n) newSess)
(reqToken rr) (reqToken yreq)
sessionHeaders <- liftIO (saveSession nsToken) sessionHeaders <- liftIO (saveSession nsToken)
return $ ("Content-Type", ct) : map headerToPair sessionHeaders return $ ("Content-Type", ct) : map headerToPair sessionHeaders
_ -> return [] _ -> return []

View File

@ -105,10 +105,6 @@ data YesodRequest = YesodRequest
-- ^ An ordered list of the accepted content types. -- ^ An ordered list of the accepted content types.
-- --
-- Since 1.2.0 -- Since 1.2.0
, reqOnError :: !(ErrorResponse -> YesodApp)
-- ^ How to respond when an error is thrown internally.
--
-- Since 1.2.0
} }
-- | An augmented WAI 'W.Response'. This can either be a standard @Response@, -- | An augmented WAI 'W.Response'. This can either be a standard @Response@,
@ -177,6 +173,10 @@ data RunHandlerEnv sub master = RunHandlerEnv
, rheSub :: !sub , rheSub :: !sub
, rheUpload :: !(RequestBodyLength -> FileUpload) , rheUpload :: !(RequestBodyLength -> FileUpload)
, rheLog :: !(Loc -> LogSource -> LogLevel -> LogStr -> IO ()) , rheLog :: !(Loc -> LogSource -> LogLevel -> LogStr -> IO ())
, rheOnError :: !(ErrorResponse -> YesodApp)
-- ^ How to respond when an error is thrown internally.
--
-- Since 1.2.0
} }
data HandlerData sub master = HandlerData data HandlerData sub master = HandlerData

View File

@ -70,12 +70,11 @@ tooLargeResponse = W.responseLBS
parseWaiRequest :: RandomGen g parseWaiRequest :: RandomGen g
=> W.Request => W.Request
-> SessionMap -> SessionMap
-> (ErrorResponse -> YesodApp)
-> Bool -> Bool
-> Word64 -- ^ max body size -> Word64 -- ^ max body size
-> g -> g
-> YesodRequest -> YesodRequest
parseWaiRequest env session onError useToken maxBodySize gen = parseWaiRequest env session useToken maxBodySize gen =
YesodRequest YesodRequest
{ reqGetParams = gets { reqGetParams = gets
, reqCookies = cookies , reqCookies = cookies
@ -86,7 +85,6 @@ parseWaiRequest env session onError useToken maxBodySize gen =
then Map.delete tokenKey session then Map.delete tokenKey session
else session else session
, reqAccept = httpAccept env , reqAccept = httpAccept env
, reqOnError = onError
} }
where where
gets = map (second $ fromMaybe "") gets = map (second $ fromMaybe "")

View File

@ -11,7 +11,6 @@ import Yesod.Request (YesodRequest (..))
import Test.Hspec import Test.Hspec
import Data.Monoid (mempty) import Data.Monoid (mempty)
import Data.Map (singleton) import Data.Map (singleton)
import Yesod.Core.Types (YesodApp, ErrorResponse)
randomStringSpecs :: Spec randomStringSpecs :: Spec
randomStringSpecs = describe "Yesod.Internal.Request.randomString" $ do randomStringSpecs = describe "Yesod.Internal.Request.randomString" $ do
@ -41,19 +40,19 @@ tokenSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqToken)" $ do
noDisabledToken :: Bool noDisabledToken :: Bool
noDisabledToken = reqToken r == Nothing where noDisabledToken = reqToken r == Nothing where
r = parseWaiRequest defaultRequest mempty onError False 1000 g r = parseWaiRequest defaultRequest mempty False 1000 g
ignoreDisabledToken :: Bool ignoreDisabledToken :: Bool
ignoreDisabledToken = reqToken r == Nothing where ignoreDisabledToken = reqToken r == Nothing where
r = parseWaiRequest defaultRequest (singleton "_TOKEN" "old") onError False 1000 g r = parseWaiRequest defaultRequest (singleton "_TOKEN" "old") False 1000 g
useOldToken :: Bool useOldToken :: Bool
useOldToken = reqToken r == Just "old" where useOldToken = reqToken r == Just "old" where
r = parseWaiRequest defaultRequest (singleton "_TOKEN" "old") onError True 1000 g r = parseWaiRequest defaultRequest (singleton "_TOKEN" "old") True 1000 g
generateToken :: Bool generateToken :: Bool
generateToken = reqToken r /= Nothing where generateToken = reqToken r /= Nothing where
r = parseWaiRequest defaultRequest (singleton "_TOKEN" "old") onError True 1000 g r = parseWaiRequest defaultRequest (singleton "_TOKEN" "old") True 1000 g
langSpecs :: Spec langSpecs :: Spec
@ -67,21 +66,21 @@ langSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqLangs)" $ do
respectAcceptLangs :: Bool respectAcceptLangs :: Bool
respectAcceptLangs = reqLangs r == ["en-US", "es", "en"] where respectAcceptLangs = reqLangs r == ["en-US", "es", "en"] where
r = parseWaiRequest defaultRequest r = parseWaiRequest defaultRequest
{ requestHeaders = [("Accept-Language", "en-US, es")] } mempty onError False 1000 g { requestHeaders = [("Accept-Language", "en-US, es")] } mempty False 1000 g
respectSessionLang :: Bool respectSessionLang :: Bool
respectSessionLang = reqLangs r == ["en"] where respectSessionLang = reqLangs r == ["en"] where
r = parseWaiRequest defaultRequest (singleton "_LANG" "en") onError False 1000 g r = parseWaiRequest defaultRequest (singleton "_LANG" "en") False 1000 g
respectCookieLang :: Bool respectCookieLang :: Bool
respectCookieLang = reqLangs r == ["en"] where respectCookieLang = reqLangs r == ["en"] where
r = parseWaiRequest defaultRequest r = parseWaiRequest defaultRequest
{ requestHeaders = [("Cookie", "_LANG=en")] { requestHeaders = [("Cookie", "_LANG=en")]
} mempty onError False 1000 g } mempty False 1000 g
respectQueryLang :: Bool respectQueryLang :: Bool
respectQueryLang = reqLangs r == ["en-US", "en"] where respectQueryLang = reqLangs r == ["en-US", "en"] where
r = parseWaiRequest defaultRequest { queryString = [("_LANG", Just "en-US")] } mempty onError False 1000 g r = parseWaiRequest defaultRequest { queryString = [("_LANG", Just "en-US")] } mempty False 1000 g
prioritizeLangs :: Bool prioritizeLangs :: Bool
prioritizeLangs = reqLangs r == ["en-QUERY", "en-COOKIE", "en-SESSION", "en", "es"] where prioritizeLangs = reqLangs r == ["en-QUERY", "en-COOKIE", "en-SESSION", "en", "es"] where
@ -90,10 +89,7 @@ prioritizeLangs = reqLangs r == ["en-QUERY", "en-COOKIE", "en-SESSION", "en", "e
, ("Cookie", "_LANG=en-COOKIE") , ("Cookie", "_LANG=en-COOKIE")
] ]
, queryString = [("_LANG", Just "en-QUERY")] , queryString = [("_LANG", Just "en-QUERY")]
} (singleton "_LANG" "en-SESSION") onError False 10000 g } (singleton "_LANG" "en-SESSION") False 10000 g
onError :: ErrorResponse -> YesodApp
onError _ = error "Yesod.InternalRequest.onError"
internalRequestTest :: Spec internalRequestTest :: Spec
internalRequestTest = describe "Test.InternalRequestTest" $ do internalRequestTest = describe "Test.InternalRequestTest" $ do

View File

@ -97,7 +97,6 @@ library
Yesod.Request Yesod.Request
Yesod.Widget Yesod.Widget
Yesod.Internal.TestApi Yesod.Internal.TestApi
Yesod.Core.Types
other-modules: Yesod.Internal other-modules: Yesod.Internal
Yesod.Internal.Cache Yesod.Internal.Cache
Yesod.Internal.Core Yesod.Internal.Core
@ -107,6 +106,7 @@ library
Yesod.Core.Trans.Class Yesod.Core.Trans.Class
Yesod.Core.Run Yesod.Core.Run
Yesod.Core.Class Yesod.Core.Class
Yesod.Core.Types
Paths_yesod_core Paths_yesod_core
ghc-options: -Wall ghc-options: -Wall