packages feed

wai-middleware-auth-0.2.3.0: test/Spec/Network/Wai/Middleware/Auth/OAuth2.hs

{-# LANGUAGE OverloadedStrings #-}

module Spec.Network.Wai.Middleware.Auth.OAuth2 (tests) where

import           Control.Monad                          (void)
import           Data.Function                          ((&))
import qualified Data.Text                              as T
import qualified Data.Text.Encoding                     as TE
import           GHC.Exts                               (fromList)
import qualified Network.HTTP.Types.Status              as Status
import qualified Network.Wai                            as Wai
import           Network.Wai.Auth.Test                  (ChangeProvider,
                                                         FakeProviderConf(..),
                                                         fakeProvider,
                                                         const200, get)
import qualified Network.Wai.Handler.Warp               as Warp
import qualified Network.Wai.Middleware.Auth            as Auth
import           Network.Wai.Middleware.Auth.OAuth2     (OAuth2(..),
                                                         getAccessToken)
import           Network.Wai.Middleware.Auth.Provider   (Provider(..),
                                                         ProviderInfo(..))
import           Network.Wai.Test                       (Session, assertHeader,
                                                         assertStatus,
                                                         runSession,
                                                         setClientCookie)
import           Test.Tasty                             (TestTree, testGroup)
import           Test.Tasty.HUnit                       (testCase)
import qualified Web.Cookie                             as Cookie
import qualified Web.ClientSession

tests :: TestTree
tests = testGroup "Network.Wai.Auth.OAuth2"
  [ testCase "when a request without a session is made then redirect to re-authorize" $
      runSessionWithProvider const200 $ \host _ -> do
        redirect1 <- get "/hi"
        assertStatus 303 redirect1
        assertHeader "Location" "/prefix" redirect1
        redirect2 <- get "/prefix"
        assertStatus 303 redirect2
        assertHeader "location" "/prefix/oauth2" redirect2
        redirect3 <- get "/prefix/oauth2"
        assertStatus 303 redirect3
        assertHeader
          "location"
          (TE.encodeUtf8 host <> "/authorize?scope=scope1%2Cscope2&client_id=client-id&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%2Fprefix%2Foauth2%2Fcomplete")
          redirect3

  , testCase "when a request is made with a valid session then pass the request through" $
      runSessionWithProvider const200 $ \_ _ -> do
        createSession
        response <- get "/some/endpoint"
        assertStatus 200 response

  , testCase "when an access token expired and no refresh token is available then redirect to re-authorize" $
      runSessionWithProvider const200 $ \_ changeProvider -> do
        changeProvider (\c -> c { accessTokenExpiresIn = -600, returnRefreshToken = False })
        createSession
        response <- get "/some/endpoint"
        assertStatus 303 response

  , testCase "when an access token expired then use a refresh token" $
      runSessionWithProvider const200 $ \_ changeProvider -> do
        changeProvider (\c -> c { accessTokenExpiresIn = -600 })
        createSession
        response <- get "/some/endpoint"
        assertStatus 200 response

  , testCase "when a request is made with an invalid session redirect to re-authorize" $
      runSessionWithProvider const200 $ \_ _ -> do
        -- First create a known valid session, so we can see that it's the act
        -- of corrupting it that makes the test fail.
        createSession
        setClientCookie
          Cookie.defaultSetCookie
            { Cookie.setCookieName = "auth-cookie"
            , Cookie.setCookieValue = "garbage"
            }
        response <- get "/some/endpoint"
        assertStatus 303 response

  , testCase "when a request is made to the complete endpoint then create a session" $
      runSessionWithProvider const200 $ \_ _ -> do
        response <- get "/prefix/oauth2/complete?code=1234"
        assertStatus 303 response
        assertHeader "location" "/" response

  , testCase "when a request with a valid session is made then the app can access the session" $
      let app req respond = 
            case getAccessToken req of
              Nothing -> respond $ Wai.responseLBS Status.badRequest400 [] ""
              Just _ -> respond $ Wai.responseLBS Status.ok200 [] ""
      in runSessionWithProvider app $ \_ _ -> do
          createSession
          response <- get "/some/endpoint"
          assertStatus 200 response
  ]

createSession :: Session ()
createSession = void $ get "/prefix/oauth2/complete?code=1234"

authSettings :: T.Text -> Auth.AuthSettings
authSettings host =
  Auth.defaultAuthSettings
    & Auth.setAuthProviders (fromList [("oauth2", provider host)])
    & Auth.setAuthPrefix "prefix"
    & Auth.setAuthCookieName "auth-cookie"
    & Auth.setAuthKey (snd <$> Web.ClientSession.randomKey)

provider :: T.Text -> Provider
provider host =
  Provider
    OAuth2
      { oa2ClientId            = "client-id"
      , oa2ClientSecret        = "client-secret"
      , oa2AuthorizeEndpoint   = host <> "/authorize"
      , oa2AccessTokenEndpoint = host <> "/token"
      , oa2Scope               = Just ["scope1", "scope2"]
      , oa2ProviderInfo        = 
          ProviderInfo
            { providerTitle    = ""
            , providerLogoUrl  = ""
            , providerDescr    = ""
            }
      }

runSessionWithProvider :: Wai.Application -> (T.Text -> ChangeProvider -> Session a) -> IO a
runSessionWithProvider app session = do
  (p, changeProvider) <- fakeProvider
  Warp.testWithApplication (pure p) $ \port -> do
    let host = "http://localhost:" <> T.pack (show port)
    middleware <- Auth.mkAuthMiddleware $ authSettings host
    let app' = middleware app
    runSession (session host changeProvider) app'