{-# 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