access-token-provider-0.1.1.0: src/Security/AccessTokenProvider/Internal.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Security.AccessTokenProvider.Internal where
import Control.Arrow
import Control.Exception.Safe
import Control.Monad.IO.Unlift
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Katip as Katip
import Network.HTTP.Client
import Network.HTTP.Client.TLS
import qualified System.Environment as Env
import Say
import Security.AccessTokenProvider.Internal.Providers.File
import Security.AccessTokenProvider.Internal.Providers.Fixed
import Security.AccessTokenProvider.Internal.Providers.OAuth2.Ropcg
import Security.AccessTokenProvider.Internal.Types
import Security.AccessTokenProvider.Internal.Types.Severity
import Security.AccessTokenProvider.Internal.Util
import Security.AccessTokenProvider.Internal.Types.Severity (Severity)
namespace :: Text
namespace = "access-token-provider"
-- | Create a new access token provider, specifying backend and list
-- of providers.
newWithProviders
:: MonadThrow m
=> Backend m -- ^ Backend to use.
-> NonEmpty (AtpProbe m) -- ^ List of providers to use.
-> AccessTokenName -- ^ Name of the access token to create a
-- provider for.
-> m (AccessTokenProvider m t)
newWithProviders backend providers tokenName = do
let BackendLog { .. } = backendLog backend
logAddNamespace namespace $
probeProviders (NonEmpty.toList providers)
where probeProviders [] =
throwM $ AccessTokenProviderMissing tokenName
probeProviders (AtpProbe tryProvider : rest) = do
maybeProvider <- tryProvider backend tokenName
case maybeProvider of
Nothing ->
probeProviders rest
Just provider ->
pure provider
-- | Create a new access token provider using the default IO-based
-- backend and the default providers.
new
:: (MonadUnliftIO m, MonadMask m)
=> AccessTokenName -- ^ Name of the access token to create a
-- provider for.
-> m (AccessTokenProvider m t)
new = newWithProviders backendIO defaultProviders
-- | List of default providers: Fixed (environment) provider,
-- file-based provider, OAuth2
-- Resource-Owner-Password-Credentials-Grant provider.
defaultProviders :: (MonadUnliftIO m, MonadMask m)
=> NonEmpty (AtpProbe m)
defaultProviders =
probeProviderFixed :| [ probeProviderFile, probeProviderRopcg ]
httpBackendIO :: MonadIO m => BackendHttp m
httpBackendIO =
BackendHttp { httpRequestExecute = httpRequestExecuteIO }
where httpRequestExecuteIO :: MonadIO m => Request -> m (Response LazyByteString)
httpRequestExecuteIO request = do
manager <- liftIO getGlobalManager
liftIO $ httpLbs request manager
envBackendIO :: MonadIO m => BackendEnv m
envBackendIO =
BackendEnv { envLookup = envLookupIO }
where envLookupIO :: MonadIO m => Text -> m (Maybe Text)
envLookupIO =
Text.unpack
>>> Env.lookupEnv
>>> fmap (fmap Text.pack)
>>> liftIO
filesystemBackendIO :: MonadIO m => BackendFilesystem m
filesystemBackendIO =
BackendFilesystem { fileRead = fileReadIO }
where fileReadIO :: MonadIO m => FilePath -> m ByteString
fileReadIO = liftIO . ByteString.readFile
logBackendIO :: MonadIO m => BackendLog m
logBackendIO =
BackendLog { logAddNamespace = \ _namespace -> id
, logMsg = logMsgIO
}
where logMsgIO :: MonadIO m => Severity -> Text -> m ()
logMsgIO severity msg =
say $ "[" <> tshow severity <> "] " <> msg
logBackendKatip :: Katip.KatipContext m => BackendLog m
logBackendKatip =
BackendLog { logAddNamespace = \ nspace ->
Katip.katipAddNamespace (Katip.Namespace [nspace])
, logMsg = \ severity msg ->
Katip.logFM (toKatipSeverity severity) (Katip.ls msg)
}
-- | IO based backend using simple stdout logging via 'say'.
backendIO :: MonadIO m => Backend m
backendIO = Backend
{ backendHttp = httpBackendIO
, backendEnv = envBackendIO
, backendFilesystem = filesystemBackendIO
, backendLog = logBackendIO
}
-- | IO based backend using Katip for logging.
backendIOWithKatip :: Katip.KatipContext m => Backend m
backendIOWithKatip =
backendIO { backendLog = logBackendKatip }
toKatipSeverity :: Severity -> Katip.Severity
toKatipSeverity severity =
case severity of
Debug -> Katip.DebugS
Info -> Katip.InfoS
Warning -> Katip.WarningS
Error -> Katip.ErrorS
Alert -> Katip.AlertS
-- | Create a new access token provider, specifying the backend to
-- use, using the default providers.
newWithBackend
:: (MonadUnliftIO m, MonadMask m)
=> Backend m -- ^ Backend to ue.
-> AccessTokenName -- ^ Name of the access token to create a
-- provider for.
-> m (AccessTokenProvider m t)
newWithBackend backend = newWithProviders backend defaultProviders