packages feed

access-token-provider-0.1.1.0: tests/Security/AccessTokenProvider/Internal/Providers/Test.hs

{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE QuasiQuotes         #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Security.AccessTokenProvider.Internal.Providers.Test
  ( securityAccessTokenProviderInternalProvidersTest
  ) where

import           Control.Exception.Safe
import           Control.Lens
import           Control.Monad.IO.Class
import qualified Data.ByteString.Lazy                       as ByteString.Lazy
import           Data.Format
import qualified Data.List.NonEmpty                         as NonEmpty
import qualified Data.Map.Strict                            as Map
import           Data.Semigroup
import qualified Data.Text.Encoding                         as Text
import           Data.UUID                                  (UUID)
import           Network.HTTP.Client.Internal
import           Network.HTTP.Types.Status
import           Network.HTTP.Types.Version
import           System.Random
import           Test.Tasty
import           Test.Tasty.HUnit

import           Security.AccessTokenProvider
import qualified Security.AccessTokenProvider               as ATP
import           Security.AccessTokenProvider.Internal.Util
import           Test

securityAccessTokenProviderInternalProvidersTest :: [TestTree]
securityAccessTokenProviderInternalProvidersTest =
  [ testGroup "Security.AccessTokenProvider.Internal.Providers"
    [ testCase "SimpleFixed Provider reads from TOKEN"
        simpleFixedProviderReadsFromToken
    , testCase "Fixed Provider reads from ATP_CONF_FIXED"
        fixedProviderReadsFromConf
    , testCase "Fixed Provider reads from ATP_CONF_FIXED and lookup fails"
        fixedProviderReadsFromConfLookupFails
    , testCase "File Provider reads from ATP_CONF_FILE"
        fileProviderReadsFromConf
    , testCase "File Provider reads from ATP_CONF_FILE and lookup fails"
        fileProviderReadsFromConfLookupFails
    , testCase "Ropcg Provider reads from ATP_CONF_ROPCG"
        ropcgProviderReadsFromConf
    ]
  ]

simpleFixedProviderReadsFromToken :: Assertion
simpleFixedProviderReadsFromToken = do
  token <- tshow <$> (randomIO :: IO UUID)
  let testState = TestState
                  { _testStateFilesystem = Map.empty
                  , _testStateEnvironment = Map.fromList [ ("TOKEN", token) ]
                  , _testStateHttpResponse = Nothing
                  , _testStateHttpRequests = []
                  , _testStateLog = []
                  }
  evalTestStack testState $ do
    tokenProvider <- newWithProviders mockBackend
                     (defaultProviders <> pure probeProviderSimpleFixed)
                     (AccessTokenName "some-random-token-name")
    (AccessToken token') <- retrieveAccessToken tokenProvider
    liftIO $ token @=? token'

fixedProviderReadsFromConf :: Assertion
fixedProviderReadsFromConf = do
  token <- tshow <$> (randomIO :: IO UUID)
  let conf = [fmt|{"tokens": {"label1": "$token"}}|]
      testState = TestState
                  { _testStateFilesystem = Map.empty
                  , _testStateEnvironment = Map.fromList [ ("ATP_CONF_FIXED", conf) ]
                  , _testStateHttpResponse = Nothing
                  , _testStateHttpRequests = []
                  , _testStateLog = []
                  }
  evalTestStack testState $ do
    tokenProvider <- newWithBackend mockBackend (AccessTokenName "label1")
    (AccessToken token') <- retrieveAccessToken tokenProvider
    liftIO $ token @=? token'

fixedProviderReadsFromConfLookupFails :: Assertion
fixedProviderReadsFromConfLookupFails = do
  token <- tshow <$> (randomIO :: IO UUID)
  let conf = [fmt|{"tokens": {"label1": "$token"}}|]
      testState = TestState
                  { _testStateFilesystem = Map.empty
                  , _testStateEnvironment = Map.fromList [ ("ATP_CONF_FIXED", conf) ]
                  , _testStateHttpResponse = Nothing
                  , _testStateHttpRequests = []
                  , _testStateLog = []
                  }
  evalTestStack testState $ do
    Left _ <- tryAny $ newWithBackend mockBackend (AccessTokenName "label2")
    pure ()

fileProviderReadsFromConf :: Assertion
fileProviderReadsFromConf = do
  tokenText <- tshow <$> (randomIO :: IO UUID)
  let tokenBytes = Text.encodeUtf8 tokenText
      filename = "/a/b/c"
      conf = [fmt|{"tokens": {"label1": "$filename"}}|]
      testState = TestState
                  { _testStateFilesystem =
                      Map.fromList [ (filename, tokenBytes) ]
                  , _testStateEnvironment =
                      Map.fromList [ ("ATP_CONF_FILE", conf) ]
                  , _testStateHttpResponse = Nothing
                  , _testStateHttpRequests = []
                  , _testStateLog = []
                  }
  evalTestStack testState $ do
    tokenProvider <- newWithBackend mockBackend (AccessTokenName "label1")
    (AccessToken token') <- retrieveAccessToken tokenProvider
    liftIO $ tokenText @=? token'

fileProviderReadsFromConfLookupFails :: Assertion
fileProviderReadsFromConfLookupFails = do
  tokenText <- tshow <$> (randomIO :: IO UUID)
  let tokenBytes = Text.encodeUtf8 tokenText
      filename = "/a/b/c"
      conf = [fmt|{"tokens": {"label1": "$filename"}}|]
      testState = TestState
                  { _testStateFilesystem =
                      Map.fromList [ (filename, tokenBytes) ]
                  , _testStateEnvironment =
                      Map.fromList [ ("ATP_CONF_FILE", conf) ]
                  , _testStateHttpResponse = Nothing
                  , _testStateHttpRequests = []
                  , _testStateLog = []
                  }
  evalTestStack testState $ do
    Left _ <- tryAny $ newWithBackend mockBackend (AccessTokenName "label2")
    pure ()

ropcgProviderReadsFromConf :: Assertion
ropcgProviderReadsFromConf = do
  tokenText <- tshow <$> (randomIO :: IO UUID)
  let conf = [fmt|{ "provider": "ropcg",
                    "credentials_directory": "/credentials",
                    "auth_endpoint": "https://localhost",
                    "tokens": {"label1": {"scopes": ["foo"]}}
                  }|]
      rspBody = ByteString.Lazy.fromStrict . Text.encodeUtf8 $
        [fmt|{"scope":        "foo",
              "expires_in":   60,
              "token_type":   "test",
              "access_token": "$tokenText"
             }|]
      response = Response { responseStatus    = ok200
                          , responseVersion   = http20
                          , responseHeaders   = []
                          , responseBody      = rspBody
                          , responseCookieJar = CJ []
                          , responseClose'    = ResponseClose (pure ())
                          }
      userCredentials =
        "{ \"application_username\": \"some-application-username\", \
        \  \"application_password\": \"some-application-password\" }"
      clientCredentials =
        "{ \"client_id\":     \"some-client-id\", \
        \  \"client_secret\": \"some-client-secret\" }"

      testState = TestState
                  { _testStateFilesystem   = Map.fromList
                    [ ("/credentials/user.json",   userCredentials)
                    , ("/credentials/client.json", clientCredentials)
                    ]
                  , _testStateEnvironment  = Map.fromList [ ("ATP_CONF_ROPCG", conf) ]
                  , _testStateHttpResponse = Just response
                  , _testStateHttpRequests = []
                  , _testStateLog = []
                  }
  (_, testState') <- runTestStack testState $ do
    tokenProvider <- newWithBackend mockBackend (AccessTokenName "label1")
    (AccessToken token) <- retrieveAccessToken tokenProvider
    liftIO $ tokenText @=? token
    pure ()
  1 @=? length (testState'^.testStateHttpRequests)
  pure ()

retrieveSomeToken :: IO ()
retrieveSomeToken = do
  tokenProvider <- ATP.new (AccessTokenName "token-name")
  token <- ATP.retrieveAccessToken tokenProvider
  print token