yesod-test 1.4.3.1 → 1.4.4
raw patch · 4 files changed
+126/−3 lines, 4 filesdep ~yesod-core
Dependency ranges changed: yesod-core
Files
- ChangeLog.md +9/−0
- Yesod/Test.hs +68/−1
- test/main.hs +47/−0
- yesod-test.cabal +2/−2
ChangeLog.md view
@@ -1,3 +1,12 @@+## 1.4.4++test helpers for CRSF middleware such as addTokenFromCookie++## 1.4.3.2++* Add `addTokenFromCookie` and `addTokenFromCookieNamedToHeaderNamed`, which support the new CSRF token middleware [#1058](https://github.com/yesodweb/yesod/pull/1058)+* Add `getRequestCookies`, which returns the cookies from the most recent request [#1058](https://github.com/yesodweb/yesod/pull/1058)+ ## 1.4.3.1 * Improved README
Yesod/Test.hs view
@@ -77,6 +77,8 @@ , addToken_ , addNonce , addNonce_+ , addTokenFromCookie+ , addTokenFromCookieNamedToHeaderNamed -- * Assertions , assertEqual@@ -93,6 +95,7 @@ -- * Grab information , getTestYesod , getResponse+ , getRequestCookies -- * Debug output , printBody@@ -133,6 +136,7 @@ import qualified Web.Cookie as Cookie import qualified Blaze.ByteString.Builder as Builder import Data.Time.Clock (getCurrentTime)+import Control.Applicative ((<$>)) -- | The state used in a single test case defined using 'yit' --@@ -589,6 +593,65 @@ addToken :: RequestBuilder site () addToken = addToken_ "" +-- | Calls 'addTokenFromCookieNamedToHeaderNamed' with the 'defaultCsrfCookieName' and 'defaultCsrfHeaderName'.+--+-- Use this function if you're using the CSRF middleware from "Yesod.Core" and haven't customized the cookie or header name.+--+-- ==== __Examples__+--+-- > request $ do+-- > addTokenFromCookie+--+-- Since 1.4.3.2+addTokenFromCookie :: RequestBuilder site ()+addTokenFromCookie = addTokenFromCookieNamedToHeaderNamed defaultCsrfCookieName defaultCsrfHeaderName++-- | Looks up the CSRF token stored in the cookie with the given name and adds it to the request headers. An error is thrown if the cookie can't be found.+--+-- Use this function if you're using the CSRF middleware from "Yesod.Core" and have customized the cookie or header name.+--+-- See "Yesod.Core.Handler" for details on this approach to CSRF protection.+--+-- ==== __Examples__+--+-- > import Data.CaseInsensitive (CI)+-- > request $ do+-- > addTokenFromCookieNamedToHeaderNamed "cookieName" (CI "headerName")+--+-- Since 1.4.3.2+addTokenFromCookieNamedToHeaderNamed :: ByteString -- ^ The name of the cookie+ -> CI ByteString -- ^ The name of the header+ -> RequestBuilder site ()+addTokenFromCookieNamedToHeaderNamed cookieName headerName = do+ cookies <- getRequestCookies+ case M.lookup cookieName cookies of+ Just csrfCookie -> addRequestHeader (headerName, Cookie.setCookieValue csrfCookie)+ Nothing -> failure $ T.concat+ [ "addTokenFromCookieNamedToHeaderNamed failed to lookup CSRF cookie with name: "+ , T.pack $ show cookieName+ , ". Cookies were: "+ , T.pack $ show cookies+ ]++-- | Returns the 'Cookies' from the most recent request. If a request hasn't been made, an error is raised.+--+-- ==== __Examples__+--+-- > request $ do+-- > cookies <- getRequestCookies+-- > liftIO $ putStrLn $ "Cookies are: " ++ show cookies+--+-- Since 1.4.3.2+getRequestCookies :: RequestBuilder site Cookies+getRequestCookies = do+ requestBuilderData <- ST.get+ headers <- case simpleHeaders <$> rbdResponse requestBuilderData of+ Just h -> return h+ Nothing -> failure "getRequestCookies: No request has been made yet; the cookies can't be looked up."++ return $ M.fromList $ map (\c -> (Cookie.setCookieName c, c)) (parseSetCookies headers)++ -- | Perform a POST request to @url@. -- -- ==== __Examples__@@ -759,7 +822,7 @@ { httpVersion = H.http11 } }) app- let newCookies = map (Cookie.parseSetCookie . snd) $ DL.filter (("Set-Cookie"==) . fst) $ simpleHeaders response+ let newCookies = parseSetCookies $ simpleHeaders response cookies' = M.fromList [(Cookie.setCookieName c, c) | c <- newCookies] `M.union` cookies ST.put $ YesodExampleData app site cookies' (Just response) where@@ -845,6 +908,10 @@ , rawQueryString = H.renderQuery False urlQuery , queryString = urlQuery }+++parseSetCookies :: [H.Header] -> [Cookie.SetCookie]+parseSetCookies headers = map (Cookie.parseSetCookie . snd) $ DL.filter (("Set-Cookie"==) . fst) $ headers -- Yes, just a shortcut failure :: (MonadIO a) => T.Text -> a b
test/main.hs view
@@ -2,6 +2,10 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TypeFamilies #-}+ import Test.HUnit hiding (Test) import Test.Hspec @@ -166,7 +170,27 @@ statusIs 200 printBody bodyContains "Foo"+ describe "CSRF with cookies/headers" $ yesodSpec CsrfApp $ do+ yit "Should receive a CSRF cookie and add its value to the headers" $ do+ get ("/" :: Text)+ statusIs 200 + request $ do+ setMethod "POST"+ setUrl ("/" :: Text)+ addTokenFromCookie+ statusIs 200+ yit "Should 403 requests if we don't add the CSRF token" $ do+ get ("/" :: Text)+ statusIs 200++ request $ do+ setMethod "POST"+ setUrl ("/" :: Text)+ statusIs 403+++ instance RenderMessage LiteApp FormMessage where renderMessage _ _ = defaultFormMessage @@ -210,3 +234,26 @@ setMessage "Foo" redirect ("/cookie/home" :: Text) return ()++data CsrfApp = CsrfApp++mkYesod "CsrfApp" [parseRoutes|+/ HomeR GET POST+|]++instance Yesod CsrfApp where+ yesodMiddleware = defaultYesodMiddleware . defaultCsrfMiddleware++getHomeR :: Handler Html+getHomeR = defaultLayout+ [whamlet|+ <p>+ Welcome to my test application.+ |]++postHomeR :: Handler Html+postHomeR = defaultLayout+ [whamlet|+ <p>+ Welcome to my test application.+ |]
yesod-test.cabal view
@@ -1,5 +1,5 @@ name: yesod-test-version: 1.4.3.1+version: 1.4.4 license: MIT license-file: LICENSE author: Nubis <nubis@woobiz.com.ar>@@ -37,7 +37,7 @@ , time , blaze-builder , cookie- , yesod-core >= 1.4+ , yesod-core >= 1.4.14 exposed-modules: Yesod.Test Yesod.Test.CssQuery