Beef up testing

This commit is contained in:
Gaël Deest 2022-03-25 09:06:29 +01:00
parent 426d3ce39b
commit 24531ac333

View file

@ -26,7 +26,7 @@ import Data.Aeson (FromJSON, ToJSON, Value,
import Data.Aeson.Lens (_JSON) import Data.Aeson.Lens (_JSON)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy as BSL
import Data.Text (Text) import Data.Text (Text, pack)
import Data.CaseInsensitive (mk) import Data.CaseInsensitive (mk)
import Data.Foldable (find) import Data.Foldable (find)
import Data.Monoid import Data.Monoid
@ -42,6 +42,7 @@ import Network.HTTP.Types (Status, status200,
import Network.Wai (responseLBS) import Network.Wai (responseLBS)
import Network.Wai.Handler.Warp (testWithApplication) import Network.Wai.Handler.Warp (testWithApplication)
import Network.Wreq (Options, auth, basicAuth, import Network.Wreq (Options, auth, basicAuth,
checkResponse,
cookieExpiryTime, cookies, cookieExpiryTime, cookies,
defaults, get, getWith, postWith, defaults, get, getWith, postWith,
header, oauth2Bearer, header, oauth2Bearer,
@ -185,8 +186,21 @@ cookieAuthSpec
it "fails with no XSRF header or cookie" $ \port -> property it "fails with no XSRF header or cookie" $ \port -> property
$ \(user :: User) -> do $ \(user :: User) -> do
jwt <- createJWT theKey (newJWSHeader ((), HS256)) (claims $ toJSON user) jwt <- createJWT theKey (newJWSHeader ((), HS256)) (claims $ toJSON user)
opts <- addJwtToCookie cookieCfg jwt opts' <- addJwtToCookie cookieCfg jwt
getWith opts (url port) `shouldHTTPErrorWith` status401 let opts = opts' & checkResponse .~ Just mempty
resp <- getWith opts (url port)
resp ^. responseStatus `shouldBe` status401
(resp ^. responseCookieJar) `shouldNotHaveCookies` ["XSRF-TOKEN"]
-- Validating that the XSRF cookie isn't added for UVerb routes either.
-- These routes can return a 401 response directly without using throwError / throwAll,
-- which revealed a bug:
--
-- https://github.com/haskell-servant/servant/issues/1570#issuecomment-1076374449
resp <- getWith opts (url port ++ "/uverb")
resp ^. responseStatus `shouldBe` status401
(resp ^. responseCookieJar) `shouldNotHaveCookies` ["XSRF-TOKEN"]
it "succeeds if XSRF header and cookie match, and JWT is valid" $ \port -> property it "succeeds if XSRF header and cookie match, and JWT is valid" $ \port -> property
$ \(user :: User) -> do $ \(user :: User) -> do
@ -408,14 +422,14 @@ type API auths
= Auth auths User :> = Auth auths User :>
( Get '[JSON] Int ( Get '[JSON] Int
:<|> ReqBody '[JSON] Int :> Post '[JSON] Int :<|> ReqBody '[JSON] Int :> Post '[JSON] Int
:<|> NamedRoutes DummyRoutes :<|> "named" :> NamedRoutes DummyRoutes
:<|> UVerb 'GET '[JSON] '[WithStatus 200 Int, WithStatus 500 Text]
:<|> "header" :> Get '[JSON] (Headers '[Header "Blah" Int] Int) :<|> "header" :> Get '[JSON] (Headers '[Header "Blah" Int] Int)
#if MIN_VERSION_servant_server(0,15,0) #if MIN_VERSION_servant_server(0,15,0)
:<|> "stream" :> StreamGet NoFraming OctetStream (SourceIO BS.ByteString) :<|> "stream" :> StreamGet NoFraming OctetStream (SourceIO BS.ByteString)
#endif #endif
:<|> "raw" :> Raw :<|> "raw" :> Raw
) )
:<|> "uverb" :> Auth auths User :> UVerb 'GET '[JSON] '[WithStatus 200 Int, WithStatus 401 Text, WithStatus 403 Text]
:<|> "login" :> ReqBody '[JSON] User :> Post '[JSON] (Headers '[ Header "Set-Cookie" SetCookie :<|> "login" :> ReqBody '[JSON] User :> Post '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
, Header "Set-Cookie" SetCookie ] NoContent) , Header "Set-Cookie" SetCookie ] NoContent)
:<|> "logout" :> Get '[JSON] (Headers '[ Header "Set-Cookie" SetCookie :<|> "logout" :> Get '[JSON] (Headers '[ Header "Set-Cookie" SetCookie
@ -486,7 +500,6 @@ server ccfg =
Authenticated usr -> getInt usr Authenticated usr -> getInt usr
:<|> postInt usr :<|> postInt usr
:<|> DummyRoutes { dummyInt = getInt usr } :<|> DummyRoutes { dummyInt = getInt usr }
:<|> respond (WithStatus @200 (42 :: Int))
:<|> getHeaderInt :<|> getHeaderInt
#if MIN_VERSION_servant_server(0,15,0) #if MIN_VERSION_servant_server(0,15,0)
:<|> return (S.source ["bytestring"]) :<|> return (S.source ["bytestring"])
@ -494,6 +507,11 @@ server ccfg =
:<|> raw :<|> raw
Indefinite -> throwAll err401 Indefinite -> throwAll err401
_ -> throwAll err403 _ -> throwAll err403
) :<|>
(\authResult -> case authResult of
Authenticated usr -> respond (WithStatus @200 (42 :: Int))
Indefinite -> respond (WithStatus @401 $ pack "Authentication required")
_ -> respond (WithStatus @403 $ pack "Forbidden")
) )
:<|> getLogin :<|> getLogin
:<|> getLogout :<|> getLogout
@ -574,6 +592,12 @@ shouldMatchCookieNames cj patterns
= fmap cookie_name (destroyCookieJar cj) = fmap cookie_name (destroyCookieJar cj)
`shouldMatchList` patterns `shouldMatchList` patterns
shouldNotHaveCookies :: HCli.CookieJar -> [BS.ByteString] -> Expectation
shouldNotHaveCookies cj patterns =
sequence_ $ (\cookieName -> cookieNames `shouldNotContain` [cookieName]) <$> patterns
where cookieNames :: [BS.ByteString]
cookieNames = cookie_name <$> destroyCookieJar cj
shouldMatchCookieNameValues :: HCli.CookieJar -> [(BS.ByteString, BS.ByteString)] -> Expectation shouldMatchCookieNameValues :: HCli.CookieJar -> [(BS.ByteString, BS.ByteString)] -> Expectation
shouldMatchCookieNameValues cj patterns shouldMatchCookieNameValues cj patterns
= fmap ((,) <$> cookie_name <*> cookie_value) (destroyCookieJar cj) = fmap ((,) <$> cookie_name <*> cookie_value) (destroyCookieJar cj)