{-# LANGUAGE OverloadedStrings #-} module EnforceHTTPSSpec where import Network.HTTP.Types import Network.Wai import Network.Wai.Middleware.EnforceHTTPS import Network.Wai.Test import Test.Hspec app :: EnforceHTTPSConfig -> Application app conf = withConfig conf $ -- reference to: https://en.wikipedia.org/wiki/Zork \_ respond -> respond $ responseLBS status200 [] "Hello, sailor" run :: EnforceHTTPSConfig -> Session a -> IO a run = flip runSession . app baseReq :: Request baseReq = defaultRequest { requestHeaderHost = Just "haskell.org" } spec :: Spec spec = do describe "Default settings" defaultSettingsSpec describe "`httpsHostname` setting" hostnameSpec describe "`httpsPort` setting" portSpec describe "`httpsIgnoreURL` setting" ignoreURLSpec describe "`httpsTemporary` setting" temporarySpec describe "`skipDefaultPort` setting" skipDefaultPortSpec describe "`httsRedirectMethods` setting" redirectMethodsSpec describe "`httpsDisallowStatus` settings" disallowStatusSpec describe "resolvers" resolversSpec defaultSettingsSpec :: Spec defaultSettingsSpec = do let withApp = run defaultConfig it "retuns 301 redirect on GET" $ withApp $ do res <- request $ baseReq assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "retuns 301 redirect on HEAD" $ withApp $ do res <- request $ baseReq { requestMethod = "HEAD" } assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "retuns 405 disallow on POST" $ withApp $ do res <- request $ baseReq { requestMethod = "POST" } assertStatus 405 res assertNoHeader "Location" res assertHeader "Allow" "GET, HEAD" res it "should not redirect secure request" $ withApp $ do res <- request $ baseReq { isSecure = True } assertStatus 200 res assertNoHeader "Location" res assertBody "Hello, sailor" res it "includes url path and params to redirect" $ withApp $ do res <- request $ baseReq { rawPathInfo = "/foo", rawQueryString = "?bar=baz" } assertStatus 301 res assertHeader "Location" "https://haskell.org/foo?bar=baz" res it "request without `Host` header fails with 400 (Bad Request)" $ withApp $ do res <- request defaultRequest assertStatus 400 res hostnameSpec :: Spec hostnameSpec = do let withApp = run $ defaultConfig { httpsHostname = Just "foo.com" } it "redirects to specified hostname" $ withApp $ do res <- request baseReq assertStatus 301 res assertHeader "Location" "https://foo.com" res it "redirects with path and params" $ withApp $ do res <- request $ baseReq { rawPathInfo = "/foo", rawQueryString = "?bar=baz" } assertStatus 301 res assertHeader "Location" "https://foo.com/foo?bar=baz" res portSpec :: Spec portSpec = do let withApp = run $ defaultConfig { httpsPort = 8443 } it "redirects to specified port" $ withApp $ do res <- request baseReq assertStatus 301 res assertHeader "Location" "https://haskell.org:8443" res it "redirects with path and params" $ withApp $ do res <- request $ baseReq { rawPathInfo = "/foo", rawQueryString = "?bar=baz" } assertStatus 301 res assertHeader "Location" "https://haskell.org:8443/foo?bar=baz" res it "removes doesn't stack ports" $ withApp $ do res <- request $ baseReq { requestHeaderHost = Just "localhost:8080" } assertStatus 301 res assertHeader "Location" "https://localhost:8443" res ignoreURLSpec :: Spec ignoreURLSpec = let withApp = run $ defaultConfig { httpsIgnoreURL = True } in it "redirect without path" $ withApp $ do res <- request $ baseReq { rawPathInfo = "/foo", rawQueryString = "?bar=baz" } assertStatus 301 res assertHeader "Location" "https://haskell.org" res temporarySpec :: Spec temporarySpec = let withApp = run $ defaultConfig { httpsTemporary = True } in it "redirect without path" $ withApp $ do res <- request baseReq assertStatus 307 res skipDefaultPortSpec :: Spec skipDefaultPortSpec = let withApp = run $ defaultConfig { httpsSkipDefaultPort = False } in it "redirect without path" $ withApp $ do res <- request baseReq assertStatus 301 res assertHeader "Location" "https://haskell.org:443" res redirectMethodsSpec :: Spec redirectMethodsSpec = do let withApp = run $ defaultConfig { httpsRedirectMethods = [ "TRACE" ] } it "default settings for GET is overwritten" $ withApp $ do res <- request baseReq assertStatus 405 res it "specified methods is redirected" $ withApp $ do res <- request $ baseReq { requestMethod = "TRACE" } assertStatus 301 res disallowStatusSpec :: Spec disallowStatusSpec = let withApp = run $ defaultConfig { httpsDisallowStatus = status403 } in it "returns specified status for disallowed method" $ withApp $ do res <- request $ baseReq { requestMethod = "POST" } assertStatus 403 res assertNoHeader "Allow" res resolversSpec :: Spec resolversSpec = do describe "x-forwarded-proto resolver" $ do let withApp = run $ defaultConfig { httpsIsSecure = xForwardedProto } it "retuns 301 redirect on GET without header" $ withApp $ do res <- request $ baseReq assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "retuns 301 redirect on GET if header value is http" $ withApp $ do res <- request $ baseReq { requestHeaders = [("x-forwarded-proto", "http")]} assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "returns 200 if header value is https" $ withApp $ do res <- request $ baseReq { requestHeaders = [("x-forwarded-proto", "https")]} assertStatus 200 res assertNoHeader "Location" res assertBody "Hello, sailor" res describe "azure's x-arr-ssl header" $ do let withApp = run $ defaultConfig { httpsIsSecure = azure } it "retuns 301 redirect on GET without header" $ withApp $ do res <- request $ baseReq assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "returns 200 if header is present" $ withApp $ do res <- request $ baseReq { requestHeaders = [("x-arr-ssl", "")]} assertStatus 200 res assertNoHeader "Location" res assertBody "Hello, sailor" res describe "custom proto header resolver" $ do let withApp = run $ defaultConfig { httpsIsSecure = customProtoHeader("x-proto") } it "retuns 301 redirect on GET without header" $ withApp $ do res <- request $ baseReq assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "retuns 301 redirect on GET if header value is http" $ withApp $ do res <- request $ baseReq { requestHeaders = [("x-proto", "http")]} assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "returns 200 if header value is https" $ withApp $ do res <- request $ baseReq { requestHeaders = [("x-proto", "https")]} assertStatus 200 res assertNoHeader "Location" res assertBody "Hello, sailor" res describe "forwarded header" $ do let withApp = run $ defaultConfig { httpsIsSecure = forwarded } it "retuns 301 redirect on GET without header" $ withApp $ do res <- request $ baseReq assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "retuns 301 redirect on GET if header value is http" $ withApp $ do res <- request $ baseReq { requestHeaders = [("forwarded", "proto=http")]} assertStatus 301 res assertHeader "Location" "https://haskell.org" res it "returns 200 if header value is https" $ withApp $ do res <- request $ baseReq { requestHeaders = [("forwarded", "proto=https")]} assertStatus 200 res assertNoHeader "Location" res assertBody "Hello, sailor" res