Luke Plant avatar Luke Plant committed 61c8c55

Added tests for CSRF protection.

Comments (0)

Files changed (3)

src/Ella/Processors/Security.hs

 module Ella.Processors.Security ( signedCookiesProcessor
+                                , CSRFProtection(..)
+                                , mkCSRFProtection
                                 )
 
 where
                  -> String -- ^ secret string used for hashing
                  -> CSRFProtection
 mkCSRFProtection baseCookie rejectView secret =
-    let tokenName = "csrfmiddlewaretoken"
+    let tokenName = "csrftoken"
         makeCsrfToken = randomStr 20
-        hashToken val = makeShaHash "csrf" secret val
         getTokenFromReq req = fromJust $ Map.lookup "csrftoken" $ environment req
         addTokenToReq req token = req { environment = Map.insert "csrftoken" token $ environment req }
 
                                  Just val -> return val
                                  _        -> makeCsrfToken
                            -- add token to environment in Request object
-                           let req2 = addTokenToReq req (hashToken token)
+                           let req2 = addTokenToReq req token
                            resp' <- view req2
                            case resp' of
                              Nothing -> return Nothing
           -- if POST request, reject if no cookie or no POST token or
           -- POST token doesn't match hash of cookie
           if requestMethod req == "POST"
-            then if isNothing incomingCookie || (fmap hashToken incomingCookie /= incomingToken)
+            then if isNothing incomingCookie || (incomingCookie /= incomingToken)
                    then rejectView req
                    else normalProc
             else normalProc

src/Ella/TestUtils.hs

                  , ("CONTENT_TYPE", "application/x-www-form-urlencoded")
                  , ("CONTENT_LENGTH", show $ BS.length encodedPostData)
                  ] encodedPostData utf8Encoding
+
+-- | Add cookie name/value pairs to a request
+addCookieValues cookies req = req { allCookies = (allCookies req) ++ cookies }

testsuite/Tests/Ella/Processors/Security.hs

 where
 
 import Data.Digest.Pure.SHA (showDigest, sha1)
-import Ella.GenUtils (utf8)
+import Ella.GenUtils (utf8, with)
 import Ella.Processors.Security
 import Ella.Request
 import Ella.Response
-import Ella.TestUtils (mkGetReq)
+import Ella.TestUtils (mkGetReq, mkPostReq, addCookieValues)
 import Test.HUnit
+import qualified Data.ByteString.Lazy.Char8 as BS
+import qualified Data.Map as Map
 
 scp_secret = "test"
 scp = signedCookiesProcessor scp_secret
       return (content resp == "name1=val1\nname3=val3\n")
     ) ~? "signedCookiesProcessor removes cookies that don't have correct hashes"
 
+
+-- CSRF view processor
+
+csrfTestView req = return $ Just $ buildResponse [ addContent "OK"
+                                             ] emptyResponse
+csrfRejectionView req = return $ Just $ buildResponse [ addContent "Rejected"
+                                                      , setStatus 403
+                                                      ] emptyResponse
+
+csrfProtection = mkCSRFProtection (Cookie { cookieName = "csrf"
+                                          , cookieValue = undefined
+                                          , cookieDomain = Nothing
+                                          , cookiePath = Nothing
+                                          , cookieExpires = Nothing
+                                          , cookieSecure = False })
+                 csrfRejectionView "secret"
+
+protectedView = (csrfProtectView csrfProtection) csrfTestView
+
+aCsrfToken = "01234567890123456789"
+-- Utility function for adding a valid CSRF cookie to a Request
+addCsrfCookie = addCookieValues [("csrf", aCsrfToken)]
+
+testCsrfGETAllowAll =
+    (do
+      let req = mkGetReq "/foo/"
+      Just resp <- protectedView req
+      return (content resp == "OK")
+    ) ~? "csrf protection allows through GET requests"
+
+testCsrfRejectMissingCookie =
+    (do
+      let req = mkPostReq "/foo/" [("name", "val")
+                                  ,("csrftoken", aCsrfToken)]
+      Just resp <- protectedView req
+      return (content resp == "Rejected")
+    ) ~? "csrf protection disallows POST requests without CSRF cookie"
+
+testCsrfRejectMissingToken =
+    (do
+      let req = mkPostReq "/foo/" [("name", "val")] `with` [ addCsrfCookie ]
+      Just resp <- protectedView req
+      return (content resp == "Rejected")
+    ) ~? "csrf protection disallows POST requests without CSRF token"
+
+testCsrfRejectMismatchedToken =
+    (do
+      let req = mkPostReq "/foo/" [("name", "val")
+                                  ,("csrftoken", "x")] `with` [ addCsrfCookie ]
+      Just resp <- protectedView req
+      return (content resp == "Rejected")
+    ) ~? "csrf protection disallows POST requests when cookie doesn't match token"
+
+testCsrfAcceptMatchingToken =
+    (do
+      let req = mkPostReq "/foo/" [("name", "val"),
+                                   ("csrftoken", aCsrfToken)] `with` [ addCsrfCookie ]
+      Just resp <- protectedView req
+      return (content resp == "OK")
+    ) ~? "csrf protection allows POST when cookie matches token"
+
+testCsrfSetsOutgoingCookie =
+    (do
+      let req = mkGetReq "/foo/"
+      Just resp <- protectedView req
+      case cookies resp of
+          [] -> return False
+          (c:cs) -> return (cookieName c == "csrf")
+    ) ~? "csrf protection sets outgoing cookie"
+
+testCsrfSetsSameOutgoingCookie =
+    (do
+      let req = mkGetReq "/foo/" `with` [ addCsrfCookie ]
+      Just resp <- protectedView req
+      case cookies resp of
+          [] -> return False
+          (c:cs) -> if (cookieName c == "csrf")
+                      then return (cookieValue c == aCsrfToken)
+                      else return False
+    ) ~? "csrf protection sets same outgoing cookie as incoming one, if it exists"
+
+testCsrfSetsTokenInRequestEnv =
+    (do
+      let req = mkGetReq "/foo/"
+          -- view that extracts 'csrftoken' from request environment field
+          view = \req -> return $ Just $ buildResponse [ addContent $ utf8 $ Map.findWithDefault "" "csrftoken" $ environment req ] utf8TextResponse
+      Just resp <- (csrfProtectView csrfProtection) view req
+      return ((BS.length $ content resp) > 1)
+    ) ~? "csrf processor puts token into request environment"
+
 tests = test [ testSignedCookiesProcessor1
              , testSignedCookiesProcessor2
              , testSignedCookiesProcessor3
              , testSignedCookiesProcessor4
+             , testCsrfGETAllowAll
+             , testCsrfRejectMissingCookie
+             , testCsrfRejectMissingToken
+             , testCsrfRejectMismatchedToken
+             , testCsrfAcceptMatchingToken
+             , testCsrfSetsOutgoingCookie
+             , testCsrfSetsSameOutgoingCookie
+             , testCsrfSetsTokenInRequestEnv
              ]
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.