yesod-core 1.4.23 → 1.4.23.1
raw patch · 6 files changed
+37/−10 lines, 6 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Yesod.Core: class RenderRoute site => Yesod site where approot = ApprootRelative errorHandler = defaultErrorHandler defaultLayout w = do { p <- widgetToPageContent w; msgs <- getMessages; withUrlRenderer (\ _render_a1jH3 -> do { id ((preEscapedText . pack) "<!DOCTYPE html>\n\ \<html><head><title>"); id (toHtml (pageTitle p)); id ((preEscapedText . pack) "</title>"); asHtmlUrl (pageHead p) _render_a1jH3; id ((preEscapedText . pack) "</head><body>"); mapM_ (\ (status_a1jH4, msg_a1jH5) -> do { id ((preEscapedText . pack) "<p class=\"message "); id (toHtml status_a1jH4); id ((preEscapedText . pack) "\">"); id (toHtml msg_a1jH5); id ((preEscapedText . pack) "</p>") }) msgs; asHtmlUrl (pageBody p) _render_a1jH3; id ((preEscapedText . pack) "</body></html>") }) } urlRenderOverride _ _ = Nothing urlParamRenderOverride y route params = addParams params <$> urlRenderOverride y route where addParams [] routeBldr = routeBldr addParams nonEmptyParams routeBldr = let routeBS = toByteString routeBldr qsSeparator = fromChar $ if elem '?' routeBS then '&' else '?' valueToMaybe t = if t == "" then Nothing else Just t queryText = map (id *** valueToMaybe) nonEmptyParams in copyByteString routeBS `mappend` qsSeparator `mappend` renderQueryText False queryText isAuthorized _ _ = return Authorized isWriteRequest _ = do { wai <- waiRequest; return $ W.requestMethod wai `notElem` ["GET", "HEAD", "OPTIONS", "TRACE"] } authRoute _ = Nothing cleanPath _ s = if corrected == s then Right $ map dropDash s else Left corrected where corrected = filter (not . null) s dropDash t | all (== '-') t = drop 1 t | otherwise = t joinPath _ ar pieces' qs' = fromText ar `mappend` encodePath pieces qs where pieces = if null pieces' then [""] else map addDash pieces' qs = map (encodeUtf8 *** go) qs' go "" = Nothing go x = Just $ encodeUtf8 x addDash t | all (== '-') t = cons '-' t | otherwise = t addStaticContent _ _ _ = return Nothing maximumContentLength _ _ = Just $ 2 * 1024 * 1024 makeLogger _ = defaultMakeLogger messageLoggerSource site = defaultMessageLoggerSource $ shouldLogIO site jsLoader _ = BottomOfBody makeSessionBackend _ = Just <$> defaultClientSessionBackend 120 defaultKeyFile fileUpload _ (KnownLength size) | size <= 50000 = FileUploadMemory lbsBackEnd fileUpload _ _ = FileUploadDisk tempFileBackEnd shouldLog _ = defaultShouldLog shouldLogIO a b c = return (shouldLog a b c) yesodMiddleware = defaultYesodMiddleware yesodWithInternalState _ _ = bracket createInternalState closeInternalState
+ Yesod.Core: class RenderRoute site => Yesod site where approot = ApprootRelative errorHandler = defaultErrorHandler defaultLayout w = do { p <- widgetToPageContent w; msgs <- getMessages; withUrlRenderer (\ _render_a1jMZ -> do { id ((preEscapedText . pack) "<!DOCTYPE html>\n\ \<html><head><title>"); id (toHtml (pageTitle p)); id ((preEscapedText . pack) "</title>"); asHtmlUrl (pageHead p) _render_a1jMZ; id ((preEscapedText . pack) "</head><body>"); mapM_ (\ (status_a1jN0, msg_a1jN1) -> do { id ((preEscapedText . pack) "<p class=\"message "); id (toHtml status_a1jN0); id ((preEscapedText . pack) "\">"); id (toHtml msg_a1jN1); id ((preEscapedText . pack) "</p>") }) msgs; asHtmlUrl (pageBody p) _render_a1jMZ; id ((preEscapedText . pack) "</body></html>") }) } urlRenderOverride _ _ = Nothing urlParamRenderOverride y route params = addParams params <$> urlRenderOverride y route where addParams [] routeBldr = routeBldr addParams nonEmptyParams routeBldr = let routeBS = toByteString routeBldr qsSeparator = fromChar $ if elem '?' routeBS then '&' else '?' valueToMaybe t = if t == "" then Nothing else Just t queryText = map (id *** valueToMaybe) nonEmptyParams in copyByteString routeBS `mappend` qsSeparator `mappend` renderQueryText False queryText isAuthorized _ _ = return Authorized isWriteRequest _ = do { wai <- waiRequest; return $ W.requestMethod wai `notElem` ["GET", "HEAD", "OPTIONS", "TRACE"] } authRoute _ = Nothing cleanPath _ s = if corrected == s then Right $ map dropDash s else Left corrected where corrected = filter (not . null) s dropDash t | all (== '-') t = drop 1 t | otherwise = t joinPath _ ar pieces' qs' = fromText ar `mappend` encodePath pieces qs where pieces = if null pieces' then [""] else map addDash pieces' qs = map (encodeUtf8 *** go) qs' go "" = Nothing go x = Just $ encodeUtf8 x addDash t | all (== '-') t = cons '-' t | otherwise = t addStaticContent _ _ _ = return Nothing maximumContentLength _ _ = Just $ 2 * 1024 * 1024 makeLogger _ = defaultMakeLogger messageLoggerSource site = defaultMessageLoggerSource $ shouldLogIO site jsLoader _ = BottomOfBody makeSessionBackend _ = Just <$> defaultClientSessionBackend 120 defaultKeyFile fileUpload _ (KnownLength size) | size <= 50000 = FileUploadMemory lbsBackEnd fileUpload _ _ = FileUploadDisk tempFileBackEnd shouldLog _ = defaultShouldLog shouldLogIO a b c = return (shouldLog a b c) yesodMiddleware = defaultYesodMiddleware yesodWithInternalState _ _ = bracket createInternalState closeInternalState
Files
- ChangeLog.md +5/−0
- Yesod/Core/Class/Yesod.hs +8/−4
- Yesod/Core/Handler.hs +16/−4
- Yesod/Core/Types.hs +1/−1
- test/YesodCoreTest/Csrf.hs +6/−0
- yesod-core.cabal +1/−1
ChangeLog.md view
@@ -1,3 +1,8 @@+## 1.4.23.1++* Don't allow sending multiple cookies with the same name to the client, in accordance with [RFC 6265](https://tools.ietf.org/html/rfc6265). This fixes an issue where multiple CSRF tokens were sent to the client. [#1258](https://github.com/yesodweb/yesod/pull/1258)+* Default CSRF tokens to the root path "/", fixing an issue where multiple tokens were stored in cookies, and using the wrong one led to CSRF errors [#1248](https://github.com/yesodweb/yesod/pull/1248)+ ## 1.4.23 * urlParamRenderOverride method for Yesod class [#1257](https://github.com/yesodweb/yesod/pull/1257)
Yesod/Core/Class/Yesod.hs view
@@ -419,9 +419,9 @@ -- all responses so that browsers will rewrite all http links to https -- until the timeout expires. For security, the max-age of the STS header -- should always equal or exceed the client sessions timeout. This defends--- against hijacking attacks on the sessions of users who attempt to access--- the site using an http url. This middleware makes a site functionally--- inaccessible over vanilla http in all standard browsers.+-- against SSL-stripping man-in-the-middle attacks. It is only effective if+-- a secure connection has already been made; Strict-Transport-Security+-- headers are ignored over HTTP. -- -- Since 1.4.7 sslOnlyMiddleware :: Yesod site@@ -491,13 +491,17 @@ -- | Calls 'csrfSetCookieMiddleware' with the 'defaultCsrfCookieName'. --+-- The cookie's path is set to @/@, making it valid for your whole website.+-- -- Since 1.4.14 defaultCsrfSetCookieMiddleware :: Yesod site => HandlerT site IO res -> HandlerT site IO res-defaultCsrfSetCookieMiddleware handler = csrfSetCookieMiddleware handler (def { setCookieName = defaultCsrfCookieName })+defaultCsrfSetCookieMiddleware handler = setCsrfCookie >> handler -- | Takes a 'SetCookie' and overrides its value with a CSRF token, then sets the cookie. See 'setCsrfCookieWithCookie'. -- -- For details, see the "AJAX CSRF protection" section of "Yesod.Core.Handler".+--+-- Make sure to set the 'setCookiePath' to the root path of your application, otherwise you'll generate a new CSRF token for every path of your app. If your app is run from from e.g. www.example.com\/app1, use @app1@. The vast majority of sites will just use @/@. -- -- Since 1.4.14 csrfSetCookieMiddleware :: Yesod site => HandlerT site IO res -> SetCookie -> HandlerT site IO res
Yesod/Core/Handler.hs view
@@ -615,10 +615,14 @@ -- | Bypass remaining handler code and output the given JSON with the given -- status code.--- +-- -- Since 1.4.18 sendStatusJSON :: (MonadHandler m, ToJSON c) => H.Status -> c -> m a+#if MIN_VERSION_aeson(0, 11, 0)+sendStatusJSON s v = sendResponseStatus s (toEncoding v)+#else sendStatusJSON s v = sendResponseStatus s (toJSON v)+#endif -- | Send a 201 "Created" response with the given route as the Location -- response header.@@ -724,7 +728,11 @@ -- | Set the cookie on the client. setCookie :: MonadHandler m => SetCookie -> m ()-setCookie = addHeaderInternal . AddCookie+setCookie sc = do+ addHeaderInternal (DeleteCookie name path)+ addHeaderInternal (AddCookie sc)+ where name = setCookieName sc+ path = maybe "/" id (setCookiePath sc) -- | Helper function for setCookieExpires value getExpires :: MonadIO m@@ -1354,7 +1362,7 @@ -- -- The form-based approach has the advantage of working for users with Javascript disabled, while adding the token to the headers with Javascript allows things like submitting JSON or binary data in AJAX requests. Yesod supports checking for a CSRF token in either the POST parameters of the form ('checkCsrfParamNamed'), the headers ('checkCsrfHeaderNamed'), or both options ('checkCsrfHeaderOrParam'). ----- The easiest way to check both sources is to add the 'defaultCsrfMiddleware' to your Yesod Middleware.+-- The easiest way to check both sources is to add the 'Yesod.Core.defaultCsrfMiddleware' to your Yesod Middleware. -- | The default cookie name for the CSRF token ("XSRF-TOKEN"). --@@ -1364,11 +1372,15 @@ -- | Sets a cookie with a CSRF token, using 'defaultCsrfCookieName' for the cookie name. --+-- The cookie's path is set to @/@, making it valid for your whole website.+-- -- Since 1.4.14 setCsrfCookie :: MonadHandler m => m ()-setCsrfCookie = setCsrfCookieWithCookie def { setCookieName = defaultCsrfCookieName }+setCsrfCookie = setCsrfCookieWithCookie def { setCookieName = defaultCsrfCookieName, setCookiePath = Just "/" } -- | Takes a 'SetCookie' and overrides its value with a CSRF token, then sets the cookie.+--+-- Make sure to set the 'setCookiePath' to the root path of your application, otherwise you'll generate a new CSRF token for every path of your app. If your app is run from from e.g. www.example.com\/app1, use @app1@. The vast majority of sites will just use @/@. -- -- Since 1.4.14 setCsrfCookieWithCookie :: MonadHandler m => SetCookie -> m ()
Yesod/Core/Types.hs view
@@ -323,7 +323,7 @@ ----- header stuff -- | Headers to be added to a 'Result'. data Header =- AddCookie SetCookie+ AddCookie SetCookie | DeleteCookie ByteString ByteString | Header ByteString ByteString deriving (Eq, Show)
test/YesodCoreTest/Csrf.hs view
@@ -45,6 +45,12 @@ assertStatus 200 res assertClientCookieExists "Should have an XSRF-TOKEN cookie" defaultCsrfCookieName + it "uses / as the path of the cookie" $ runner $ do -- https://github.com/yesodweb/yesod/issues/1247+ res <- request defaultRequest+ assertStatus 200 res+ cookiePath <- fmap setCookiePath requireCsrfCookie+ liftIO $ cookiePath `shouldBe` Just "/"+ it "200s write requests with the correct CSRF header, but no param" $ runner $ do getRes <- request defaultRequest assertStatus 200 getRes
yesod-core.cabal view
@@ -1,5 +1,5 @@ name: yesod-core-version: 1.4.23+version: 1.4.23.1 license: MIT license-file: LICENSE author: Michael Snoyman <michael@snoyman.com>