packages feed

wai-middleware-hmac-0.1.0.0: test/WaiMiddlewareHmacAuthSpec.hs

------------------------------------------------------------------------
-- |
-- Module      : WaiMiddlewareHmacAuthSpec
-- Description : Wai HMAC Auth Middleware Spec
-- Copyright   : (c) 2015 Christopher Reichert
-- License     : BSD3
-- Maintainer  : Christopher Reichert <creichert07@gmail.com>
-- Stability   : unstable
-- Portability : POSIX
--
--
-- TODO test corner clases
-- - test expired timestamp (set skew in settings)
-- - test missing headers
-- - timestamp modified
-- - resource modified
-- - changing up hashing algorithm somehow


{-# LANGUAGE OverloadedStrings #-}

module WaiMiddlewareHmacAuthSpec (
    spec
  ) where


import           Data.ByteString                 (ByteString)
import           Network.HTTP.Types              (status200, HeaderName)
import           Network.Wai
import           Network.Wai.Test
import           Test.Hspec
import           Test.HUnit                      hiding (Test)

import           Network.Wai.Middleware.HmacAuth



-- | Simple Hmac Authentication Spec
spec :: Spec
spec = describe "Network.Wai.Middleware.HmacAuth" $ do
    it "authenticates valid signatures" caseHmacAuth
    it "rejects invalid secrets"        caseHmacInvalidSecret
    it "rejects invalid signatures"     caseHmacInvalidSignature
    it "rejects invalid headers"        caseHmacInvalidHeader
    it "rejects method modificatied"    caseHmacMethodModified
    it "rejects request path modified"  caseHmacPathModified



-- | Simple Hmac Middleware App
--
-- This app has preloaded api keys to simulate
-- some database or service which can access the
-- private keys.
hmacAuthApp :: HashAlgorithm alg => HmacAuthSettings alg -> Application
hmacAuthApp stgs = hmacAuth lookupSecret stgs
                       $ \_ f -> f response
  where
    payload               = "{ \"api\", \"return data\" }"
    response              = responseLBS status200 [] payload
    -- Server-Side Api Credentials
    lookupSecret key      = return $ case lookup key creds of
                                       Nothing -> Nothing
                                       Just  s -> Just (Secret s)
    creds                 = [ (Key "key1", "secret1")
                            , (Key "key2", "secret2")
                            ]


-- defaults
key1, key2 :: ByteString
key1 = "key1"
key2 = "key2"

sec1 :: Secret
sec1 = Secret "secret1"

-- api key and secret shared in advance

timestamp   :: ByteString
timestamp    = "2015-01-01T00:00:00Z"


cfg :: HmacAuthSettings SHA512
cfg = defaultHmacAuthSettings


headers     :: [(HeaderName, ByteString)]
headers      = [ ("Content-Type", "application/json")
               , ("x-auth-timestamp", timestamp)
               ]

hmacReq     :: Request
hmacReq      = defaultRequest
               { requestMethod  = "GET"
               , rawPathInfo    = "/resource"
               , requestHeaders = headers
               }



-- | Test Hmac Authentication
caseHmacAuth :: Assertion
caseHmacAuth = do

    -- valid signature
    validSignatureReq   <- signRequest cfg (Secret "secret1") hmacReq
                           { requestHeaders = ("x-auth-key", key1)
                                                : headers
                           }


    -- test hmac authenticaation
    flip runSession (hmacAuthApp defaultHmacAuthSettings) $ do

        res <- request validSignatureReq

        -- Succesful verification
        assertStatus 200 res

        -- succesfully authentication request
        assertBody "{ \"api\", \"return data\" }" res



--  |
caseHmacInvalidSecret :: Assertion
caseHmacInvalidSecret = do
    invalidSecretReq <- signRequest cfg sec1 hmacReq
                          { requestHeaders = ("x-auth-key", key2)
                                               : headers
                          }

    flip runSession (hmacAuthApp defaultHmacAuthSettings) $
        request invalidSecretReq
          >>= assertStatus 401



-- | Invalid signatures are rejected
caseHmacInvalidSignature :: Assertion
caseHmacInvalidSignature = do
    -- test hmac authenticaation
    -- invalid secret
    invalidSignatureReq <- signRequest cfg (Secret "wJalrXUtnFEMI") hmacReq
                           { requestHeaders = ("x-auth-key", key1)
                                                : headers
                           }

    flip runSession (hmacAuthApp defaultHmacAuthSettings) $
        request invalidSignatureReq
          >>= assertStatus 401


caseHmacInvalidHeader :: Assertion
caseHmacInvalidHeader = do
    -- invalid headers
    invalidHeaderReq    <- signRequest cfg sec1 hmacReq
    -- test hmac authenticaation
    flip runSession (hmacAuthApp defaultHmacAuthSettings) $
        request invalidHeaderReq
          >>= assertStatus 401



caseHmacMethodModified :: Assertion
caseHmacMethodModified = do
    req <- signRequest cfg sec1 hmacReq
    flip runSession (hmacAuthApp defaultHmacAuthSettings) $
        -- reject request path modified after signature
        request req { rawPathInfo = "/etc/passwd" }
          >>= assertStatus 401



caseHmacPathModified :: Assertion
caseHmacPathModified =  do
    req <- signRequest cfg sec1 hmacReq
    -- test hmac authenticaation
    flip runSession (hmacAuthApp defaultHmacAuthSettings) $
      -- reject request path modified after signature
      request req { rawPathInfo = "/etc/passwd" }
        >>= assertStatus 401