packages feed

access-token-provider 0.1.0.0 → 0.1.1.0

raw patch · 14 files changed

+496/−452 lines, 14 filesdep +sayPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: say

API changes (from Hackage documentation)

- Security.AccessTokenProvider: class Monad m => MonadEnvironment m
- Security.AccessTokenProvider: class Monad m => MonadFilesystem m
- Security.AccessTokenProvider: class Monad m => MonadHttp m
- Security.AccessTokenProvider: environmentLookup :: (MonadEnvironment m, m ~ t n, MonadTrans t, MonadEnvironment n) => Text -> m (Maybe Text)
- Security.AccessTokenProvider: fileRead :: (MonadFilesystem m, m ~ t n, MonadTrans t, MonadFilesystem n) => FilePath -> m ByteString
- Security.AccessTokenProvider: httpRequestExecute :: (MonadHttp m, m ~ t n, MonadTrans t, MonadHttp n) => Request -> Manager -> m (Response LazyByteString)
- Security.AccessTokenProvider: providerProbeFile :: (KatipContext m, MonadFilesystem m, MonadCatch m, MonadEnvironment m, MonadUnliftIO m) => AccessTokenName -> m (Maybe (AccessTokenProvider m t))
- Security.AccessTokenProvider: providerProbeFixed :: (KatipContext m, MonadIO m, MonadThrow m, MonadEnvironment m) => AccessTokenName -> m (Maybe (AccessTokenProvider m t))
- Security.AccessTokenProvider: providerProbeRopcg :: (MonadMask m, MonadUnliftIO m, KatipContext m, MonadEnvironment m, MonadHttp m, MonadFilesystem m) => AccessTokenName -> m (Maybe (AccessTokenProvider m t))
- Security.AccessTokenProvider.Internal.Lenses: class HasToken s a | s -> a
- Security.AccessTokenProvider.Internal.Lenses: instance Security.AccessTokenProvider.Internal.Lenses.HasToken Security.AccessTokenProvider.Internal.Types.AtpConfFile (GHC.Base.Maybe GHC.IO.FilePath)
- Security.AccessTokenProvider.Internal.Lenses: instance Security.AccessTokenProvider.Internal.Lenses.HasToken Security.AccessTokenProvider.Internal.Types.AtpConfFixed (GHC.Base.Maybe Data.Text.Internal.Text)
- Security.AccessTokenProvider.Internal.Lenses: instance Security.AccessTokenProvider.Internal.Lenses.HasToken Security.AccessTokenProvider.Internal.Types.AtpConfRopcg (GHC.Base.Maybe Security.AccessTokenProvider.Internal.Types.AtpRopcgTokenDef)
- Security.AccessTokenProvider.Internal.Lenses: instance Security.AccessTokenProvider.Internal.Lenses.HasToken Security.AccessTokenProvider.Internal.Types.AtpPreconfRopcg (GHC.Base.Maybe Security.AccessTokenProvider.Internal.Types.AtpRopcgTokenDef)
- Security.AccessTokenProvider.Internal.Lenses: token :: HasToken s a => Lens' s a
- Security.AccessTokenProvider.Internal.Providers.Common: tryEnvDeserialization :: (MonadThrow m, MonadEnvironment m, FromJSON a, KatipContext m) => NonEmpty Text -> m (Maybe a)
- Security.AccessTokenProvider.Internal.Providers.Common: tryNewProvider :: (MonadIO m, MonadThrow m, KatipContext m) => AccessTokenName -> m (Maybe envConf) -> (envConf -> m conf) -> (AccessTokenName -> conf -> m (Maybe (AccessTokenProvider m t))) -> m (Maybe (AccessTokenProvider m t))
- Security.AccessTokenProvider.Internal.Providers.File: providerProbeFile :: (KatipContext m, MonadFilesystem m, MonadCatch m, MonadEnvironment m, MonadUnliftIO m) => AccessTokenName -> m (Maybe (AccessTokenProvider m t))
- Security.AccessTokenProvider.Internal.Providers.Fixed: providerProbeFixed :: (KatipContext m, MonadIO m, MonadThrow m, MonadEnvironment m) => AccessTokenName -> m (Maybe (AccessTokenProvider m t))
- Security.AccessTokenProvider.Internal.Providers.OAuth2.Ropcg: providerProbeRopcg :: (MonadMask m, MonadUnliftIO m, KatipContext m, MonadEnvironment m, MonadHttp m, MonadFilesystem m) => AccessTokenName -> m (Maybe (AccessTokenProvider m t))
- Security.AccessTokenProvider.Internal.Types: [$sel:_token:AtpConfFile] :: AtpConfFile -> Maybe FilePath
- Security.AccessTokenProvider.Internal.Types: [$sel:_token:AtpConfFixed] :: AtpConfFixed -> Maybe Text
- Security.AccessTokenProvider.Internal.Types: [$sel:_token:AtpConfRopcg] :: AtpConfRopcg -> Maybe AtpRopcgTokenDef
- Security.AccessTokenProvider.Internal.Types: [$sel:_token:AtpPreconfRopcg] :: AtpPreconfRopcg -> Maybe AtpRopcgTokenDef
- Security.AccessTokenProvider.Internal.Types: class Monad m => MonadEnvironment m
- Security.AccessTokenProvider.Internal.Types: class Monad m => MonadFilesystem m
- Security.AccessTokenProvider.Internal.Types: class Monad m => MonadHttp m
- Security.AccessTokenProvider.Internal.Types: environmentLookup :: (MonadEnvironment m, m ~ t n, MonadTrans t, MonadEnvironment n) => Text -> m (Maybe Text)
- Security.AccessTokenProvider.Internal.Types: fileRead :: (MonadFilesystem m, m ~ t n, MonadTrans t, MonadFilesystem n) => FilePath -> m ByteString
- Security.AccessTokenProvider.Internal.Types: httpRequestExecute :: (MonadHttp m, m ~ t n, MonadTrans t, MonadHttp n) => Request -> Manager -> m (Response LazyByteString)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadEnvironment GHC.Types.IO
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadEnvironment m => Security.AccessTokenProvider.Internal.Types.MonadEnvironment (Control.Monad.Trans.Maybe.MaybeT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadEnvironment m => Security.AccessTokenProvider.Internal.Types.MonadEnvironment (Control.Monad.Trans.Reader.ReaderT r m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadEnvironment m => Security.AccessTokenProvider.Internal.Types.MonadEnvironment (Control.Monad.Trans.State.Lazy.StateT s m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadEnvironment m => Security.AccessTokenProvider.Internal.Types.MonadEnvironment (Katip.Core.KatipT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadEnvironment m => Security.AccessTokenProvider.Internal.Types.MonadEnvironment (Katip.Monadic.KatipContextT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadFilesystem GHC.Types.IO
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadFilesystem m => Security.AccessTokenProvider.Internal.Types.MonadFilesystem (Control.Monad.Trans.Maybe.MaybeT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadFilesystem m => Security.AccessTokenProvider.Internal.Types.MonadFilesystem (Control.Monad.Trans.Reader.ReaderT r m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadFilesystem m => Security.AccessTokenProvider.Internal.Types.MonadFilesystem (Control.Monad.Trans.State.Lazy.StateT s m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadFilesystem m => Security.AccessTokenProvider.Internal.Types.MonadFilesystem (Katip.Core.KatipT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadFilesystem m => Security.AccessTokenProvider.Internal.Types.MonadFilesystem (Katip.Monadic.KatipContextT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadHttp GHC.Types.IO
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadHttp m => Security.AccessTokenProvider.Internal.Types.MonadHttp (Control.Monad.Trans.Maybe.MaybeT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadHttp m => Security.AccessTokenProvider.Internal.Types.MonadHttp (Control.Monad.Trans.Reader.ReaderT r m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadHttp m => Security.AccessTokenProvider.Internal.Types.MonadHttp (Control.Monad.Trans.State.Lazy.StateT s m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadHttp m => Security.AccessTokenProvider.Internal.Types.MonadHttp (Katip.Core.KatipT m)
- Security.AccessTokenProvider.Internal.Types: instance Security.AccessTokenProvider.Internal.Types.MonadHttp m => Security.AccessTokenProvider.Internal.Types.MonadHttp (Katip.Monadic.KatipContextT m)
+ Security.AccessTokenProvider: AtpProbe :: (forall t. Backend m -> AccessTokenName -> m (Maybe (AccessTokenProvider m t))) -> AtpProbe m
+ Security.AccessTokenProvider: defaultProviders :: (MonadUnliftIO m, MonadMask m) => NonEmpty (AtpProbe m)
+ Security.AccessTokenProvider: newWithBackend :: (MonadUnliftIO m, MonadMask m) => Backend m -> AccessTokenName -> m (AccessTokenProvider m t)
+ Security.AccessTokenProvider: newtype AtpProbe m
+ Security.AccessTokenProvider: probeProviderFile :: (MonadUnliftIO m, MonadCatch m) => AtpProbe m
+ Security.AccessTokenProvider: probeProviderFixed :: (MonadIO m, MonadCatch m) => AtpProbe m
+ Security.AccessTokenProvider: probeProviderRopcg :: (MonadMask m, MonadUnliftIO m) => AtpProbe m
+ Security.AccessTokenProvider: probeProviderSimpleFixed :: (MonadIO m, MonadCatch m) => AtpProbe m
+ Security.AccessTokenProvider.Internal: backendIO :: MonadIO m => Backend m
+ Security.AccessTokenProvider.Internal: backendIOWithKatip :: KatipContext m => Backend m
+ Security.AccessTokenProvider.Internal: defaultProviders :: (MonadUnliftIO m, MonadMask m) => NonEmpty (AtpProbe m)
+ Security.AccessTokenProvider.Internal: envBackendIO :: MonadIO m => BackendEnv m
+ Security.AccessTokenProvider.Internal: filesystemBackendIO :: MonadIO m => BackendFilesystem m
+ Security.AccessTokenProvider.Internal: httpBackendIO :: MonadIO m => BackendHttp m
+ Security.AccessTokenProvider.Internal: logBackendIO :: MonadIO m => BackendLog m
+ Security.AccessTokenProvider.Internal: logBackendKatip :: KatipContext m => BackendLog m
+ Security.AccessTokenProvider.Internal: newWithBackend :: (MonadUnliftIO m, MonadMask m) => Backend m -> AccessTokenName -> m (AccessTokenProvider m t)
+ Security.AccessTokenProvider.Internal: toKatipSeverity :: Severity -> Severity
+ Security.AccessTokenProvider.Internal.Providers.File: probeProviderFile :: (MonadUnliftIO m, MonadCatch m) => AtpProbe m
+ Security.AccessTokenProvider.Internal.Providers.Fixed: probeProviderFixed :: (MonadIO m, MonadCatch m) => AtpProbe m
+ Security.AccessTokenProvider.Internal.Providers.OAuth2.Ropcg: probeProviderRopcg :: (MonadMask m, MonadUnliftIO m) => AtpProbe m
+ Security.AccessTokenProvider.Internal.Providers.SimpleFixed: probeProviderSimpleFixed :: (MonadIO m, MonadCatch m) => AtpProbe m
+ Security.AccessTokenProvider.Internal.Types: Backend :: BackendHttp m -> BackendEnv m -> BackendFilesystem m -> BackendLog m -> Backend m
+ Security.AccessTokenProvider.Internal.Types: BackendEnv :: (Text -> m (Maybe Text)) -> BackendEnv m
+ Security.AccessTokenProvider.Internal.Types: BackendFilesystem :: (FilePath -> m ByteString) -> BackendFilesystem m
+ Security.AccessTokenProvider.Internal.Types: BackendHttp :: (Request -> m (Response LazyByteString)) -> BackendHttp m
+ Security.AccessTokenProvider.Internal.Types: BackendLog :: (forall a. Text -> m a -> m a) -> (Severity -> Text -> m ()) -> BackendLog m
+ Security.AccessTokenProvider.Internal.Types: [$sel:backendEnv:Backend] :: Backend m -> BackendEnv m
+ Security.AccessTokenProvider.Internal.Types: [$sel:backendFilesystem:Backend] :: Backend m -> BackendFilesystem m
+ Security.AccessTokenProvider.Internal.Types: [$sel:backendHttp:Backend] :: Backend m -> BackendHttp m
+ Security.AccessTokenProvider.Internal.Types: [$sel:backendLog:Backend] :: Backend m -> BackendLog m
+ Security.AccessTokenProvider.Internal.Types: [$sel:envLookup:BackendEnv] :: BackendEnv m -> Text -> m (Maybe Text)
+ Security.AccessTokenProvider.Internal.Types: [$sel:fileRead:BackendFilesystem] :: BackendFilesystem m -> FilePath -> m ByteString
+ Security.AccessTokenProvider.Internal.Types: [$sel:httpRequestExecute:BackendHttp] :: BackendHttp m -> Request -> m (Response LazyByteString)
+ Security.AccessTokenProvider.Internal.Types: [$sel:logAddNamespace:BackendLog] :: BackendLog m -> forall a. Text -> m a -> m a
+ Security.AccessTokenProvider.Internal.Types: [$sel:logMsg:BackendLog] :: BackendLog m -> Severity -> Text -> m ()
+ Security.AccessTokenProvider.Internal.Types: data Backend m
+ Security.AccessTokenProvider.Internal.Types: data BackendEnv m
+ Security.AccessTokenProvider.Internal.Types: data BackendFilesystem m
+ Security.AccessTokenProvider.Internal.Types: data BackendHttp m
+ Security.AccessTokenProvider.Internal.Types: data BackendLog m
+ Security.AccessTokenProvider.Internal.Types.Severity: Alert :: Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: Debug :: Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: Error :: Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: Info :: Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: Warning :: Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: data Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: instance GHC.Classes.Eq Security.AccessTokenProvider.Internal.Types.Severity.Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: instance GHC.Classes.Ord Security.AccessTokenProvider.Internal.Types.Severity.Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: instance GHC.Enum.Enum Security.AccessTokenProvider.Internal.Types.Severity.Severity
+ Security.AccessTokenProvider.Internal.Types.Severity: instance GHC.Show.Show Security.AccessTokenProvider.Internal.Types.Severity.Severity
- Security.AccessTokenProvider: new :: (KatipContext m, MonadUnliftIO m, MonadMask m, MonadEnvironment m, MonadFilesystem m, MonadHttp m) => AccessTokenName -> m (AccessTokenProvider m t)
+ Security.AccessTokenProvider: new :: (MonadUnliftIO m, MonadMask m) => AccessTokenName -> m (AccessTokenProvider m t)
- Security.AccessTokenProvider: newWithProviders :: (MonadThrow m, KatipContext m) => NonEmpty (AtpProbe m) -> AccessTokenName -> m (AccessTokenProvider m t)
+ Security.AccessTokenProvider: newWithProviders :: MonadThrow m => Backend m -> NonEmpty (AtpProbe m) -> AccessTokenName -> m (AccessTokenProvider m t)
- Security.AccessTokenProvider.Internal: namespace :: Namespace
+ Security.AccessTokenProvider.Internal: namespace :: Text
- Security.AccessTokenProvider.Internal: new :: (KatipContext m, MonadUnliftIO m, MonadMask m, MonadEnvironment m, MonadFilesystem m, MonadHttp m) => AccessTokenName -> m (AccessTokenProvider m t)
+ Security.AccessTokenProvider.Internal: new :: (MonadUnliftIO m, MonadMask m) => AccessTokenName -> m (AccessTokenProvider m t)
- Security.AccessTokenProvider.Internal: newWithProviders :: (MonadThrow m, KatipContext m) => NonEmpty (AtpProbe m) -> AccessTokenName -> m (AccessTokenProvider m t)
+ Security.AccessTokenProvider.Internal: newWithProviders :: MonadThrow m => Backend m -> NonEmpty (AtpProbe m) -> AccessTokenName -> m (AccessTokenProvider m t)
- Security.AccessTokenProvider.Internal.Types: AtpConfFile :: Maybe (Map Text FilePath) -> Maybe FilePath -> AtpConfFile
+ Security.AccessTokenProvider.Internal.Types: AtpConfFile :: Maybe (Map Text FilePath) -> AtpConfFile
- Security.AccessTokenProvider.Internal.Types: AtpConfFixed :: Maybe (Map Text Text) -> Maybe Text -> AtpConfFixed
+ Security.AccessTokenProvider.Internal.Types: AtpConfFixed :: Maybe (Map Text Text) -> AtpConfFixed
- Security.AccessTokenProvider.Internal.Types: AtpConfRopcg :: FilePath -> FilePath -> FilePath -> Double -> Request -> Manager -> Map Text AtpRopcgTokenDef -> Maybe AtpRopcgTokenDef -> AtpConfRopcg
+ Security.AccessTokenProvider.Internal.Types: AtpConfRopcg :: FilePath -> FilePath -> FilePath -> Double -> Request -> Manager -> Map Text AtpRopcgTokenDef -> AtpConfRopcg
- Security.AccessTokenProvider.Internal.Types: AtpPreconfRopcg :: Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe Double -> Text -> Map Text AtpRopcgTokenDef -> Maybe AtpRopcgTokenDef -> AtpPreconfRopcg
+ Security.AccessTokenProvider.Internal.Types: AtpPreconfRopcg :: Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe Double -> Text -> Map Text AtpRopcgTokenDef -> AtpPreconfRopcg
- Security.AccessTokenProvider.Internal.Types: AtpProbe :: (forall t. AccessTokenName -> m (Maybe (AccessTokenProvider m t))) -> AtpProbe m
+ Security.AccessTokenProvider.Internal.Types: AtpProbe :: (forall t. Backend m -> AccessTokenName -> m (Maybe (AccessTokenProvider m t))) -> AtpProbe m
- Security.AccessTokenProvider.Internal.Util: parseEndpoint :: (KatipContext m, MonadIO m, MonadCatch m) => Text -> Text -> m Request
+ Security.AccessTokenProvider.Internal.Util: parseEndpoint :: (MonadIO m, MonadCatch m) => Text -> m Request

Files

README.md view
@@ -1,35 +1,36 @@-# Access Token Provider+# Access Token Provider [![Hackage version](https://img.shields.io/hackage/v/access-token-provider.svg?label=Hackage)](https://hackage.haskell.org/package/access-token-provider) [![Stackage version](https://www.stackage.org/package/access-token-provider/badge/lts?label=Stackage)](https://www.stackage.org/package/access-token-provider) [![Build Status](https://travis-ci.org/mtesseract/access-token-provider.svg?branch=master)](https://travis-ci.org/mtesseract/access-token-provider) -This package provides a convenient retrieval mechanism of access-tokens. Access Token Provider supporting multiple provider backends,-including OAuth2 Resource Owner Password Credentials Grant, file-based-token access (e.g. for Kubernetes) and fetching tokens from the-environment (e.g. for local testing). The package is configurable via-environment variables. It uses Katip for logging.+This package provides a convenient retrieval mechanism for access+tokens. Multiple provider backends, including OAuth2 Resource Owner+Password Credentials Grant, file-based token access (e.g. for+Kubernetes) and fetching tokens from the environment (e.g. for local+testing) are supported; custom provider backends can easily be added.  ## Examples  ```haskell import qualified Security.AccessTokenProvider as ATP -retrieveSomeToken :: KatipContextT IO ()+retrieveSomeToken :: IO () retrieveSomeToken = do   tokenProvider <- ATP.new (AccessTokenName "token-name")   token <- ATP.retrieveAccessToken tokenProvider-  liftIO $ print token+  print token ```  ## Configuration -Configuration is done by setting the environment variable `ATP_CONF`.+Configuration is done by setting certain environment variables,+depending on the provider.  ### OAuth2 based token retrieval -For OAuth2 (Resource Owner Password Credentials Grant) provider, use:+The OAuth2 (Resource Owner Password Credentials Grant) provider+expects the `ATP_CONF_ROPCG` environment variable to contain a JSON+object as follows:  ```json {-  "provider": "ropcg",   "credentials_directory": "/optional/credentials/directory",   "auth_endpoint": "<OAuth2 authentication endpoint>",   "tokens": {"token-name": {"scopes": ["first-scope", "second-scope"]}}@@ -43,9 +44,11 @@  ### File based token retrieval (e.g. for Kubernetes) +The file based provider expects the `ATP_CONF_FILE` environment+variable to contain a JSON object as follows:+ ```json {-  "provider": "file",   "tokens": {"token-name": "/some/file/name"} } ```@@ -55,12 +58,11 @@  ### Environment based token retrieval (e.g. for testing) +The file based provider expects the `ATP_CONF_FIXED` environment+variable to contain a JSON object as follows:+ ```json {-  "provider": "fixed",   "tokens": {"token-name": "some-fixed-token"} } ```--As a short cut, you can simply save a token directly in the-environment variable `TOKEN`.
access-token-provider.cabal view
@@ -1,5 +1,5 @@ name:                access-token-provider-version:             0.1.0.0+version:             0.1.1.0 synopsis:            Provides Access Token for Services description:         Access Token Provider supporting multiple provider backends,                      including OAuth2 Resource Owner Password Credentials Grant,@@ -23,10 +23,11 @@   exposed-modules:     Security.AccessTokenProvider                      , Security.AccessTokenProvider.Internal                      , Security.AccessTokenProvider.Internal.Types+                     , Security.AccessTokenProvider.Internal.Types.Severity                      , Security.AccessTokenProvider.Internal.Lenses                      , Security.AccessTokenProvider.Internal.Util-                     , Security.AccessTokenProvider.Internal.Providers.Common                      , Security.AccessTokenProvider.Internal.Providers.Fixed+                     , Security.AccessTokenProvider.Internal.Providers.SimpleFixed                      , Security.AccessTokenProvider.Internal.Providers.File                      , Security.AccessTokenProvider.Internal.Providers.OAuth2.Ropcg   build-depends:       base >= 4.7 && < 5@@ -52,6 +53,7 @@                      , mtl                      , transformers                      , random+                     , say   default-language:    Haskell2010  
src/Security/AccessTokenProvider.hs view
@@ -1,19 +1,21 @@ module Security.AccessTokenProvider   ( new   , newWithProviders-  , providerProbeFile-  , providerProbeFixed-  , providerProbeRopcg-  , MonadHttp(..)-  , MonadFilesystem(..)-  , MonadEnvironment(..)+  , newWithBackend+  , probeProviderFile+  , probeProviderFixed+  , probeProviderSimpleFixed+  , probeProviderRopcg+  , defaultProviders   , AccessTokenName(..)   , AccessTokenProvider(..)   , AccessToken(..)+  , AtpProbe(..)   ) where  import           Security.AccessTokenProvider.Internal import           Security.AccessTokenProvider.Internal.Providers.File import           Security.AccessTokenProvider.Internal.Providers.Fixed import           Security.AccessTokenProvider.Internal.Providers.OAuth2.Ropcg+import           Security.AccessTokenProvider.Internal.Providers.SimpleFixed import           Security.AccessTokenProvider.Internal.Types
src/Security/AccessTokenProvider/Internal.hs view
@@ -3,53 +3,151 @@ {-# 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           Katip+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 -namespace :: Namespace+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, KatipContext m)-  => NonEmpty (AtpProbe m)-  -> AccessTokenName+  :: 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 providers tokenName = katipAddNamespace namespace $-  probeProviders (NonEmpty.toList providers)+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 tokenName+          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-  :: ( KatipContext m-     , MonadUnliftIO m-     , MonadMask m-     , MonadEnvironment m-     , MonadFilesystem m-     , MonadHttp m)-  => AccessTokenName -> m (AccessTokenProvider m t)-new = newWithProviders providers+  :: (MonadUnliftIO m, MonadMask m)+  => AccessTokenName -- ^ Name of the access token to create a+                     -- provider for.+  -> m (AccessTokenProvider m t)+new = newWithProviders backendIO defaultProviders -  where providers =-          AtpProbe providerProbeFixed-          :| [ AtpProbe providerProbeFile-             , AtpProbe providerProbeRopcg ]+-- | 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
− src/Security/AccessTokenProvider/Internal/Providers/Common.hs
@@ -1,67 +0,0 @@-{-# LANGUAGE OverloadedStrings   #-}-{-# LANGUAGE QuasiQuotes         #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Security.AccessTokenProvider.Internal.Providers.Common-  ( tryNewProvider-  , tryEnvDeserialization-  ) where--import           Control.Exception.Safe-import           Control.Lens-import           Control.Monad.IO.Class-import           Control.Monad.Trans.Class-import           Control.Monad.Trans.Maybe-import           Data.Aeson-import           Data.Aeson.Lens-import           Data.Format-import           Data.List.NonEmpty                          (NonEmpty)-import qualified Data.List.NonEmpty                          as NonEmpty-import           Data.Text                                   (Text)-import qualified Data.Text.Encoding                          as Text-import           Katip--import           Security.AccessTokenProvider.Internal.Types-import           Security.AccessTokenProvider.Internal.Util--tryNewProvider-  :: (MonadIO m, MonadThrow m, KatipContext m)-  => AccessTokenName-  -> m (Maybe envConf)-  -> (envConf -> m conf)-  -> (AccessTokenName -> conf -> m (Maybe (AccessTokenProvider m t)))-  -> m (Maybe (AccessTokenProvider m t))-tryNewProvider tokenName makeEnvConf makeConf providerBuilder = do-  maybeEnvConf <- makeEnvConf-  case maybeEnvConf of-    Just envConf -> do-      conf <- makeConf envConf-      providerBuilder tokenName conf-    Nothing ->-      pure Nothing--atpConfVarName :: Text-atpConfVarName = "ATP_CONF"--tryEnvDeserialization-  :: ( MonadThrow m-     , MonadEnvironment m-     , FromJSON a-     , KatipContext m )-  => NonEmpty Text-  -> m (Maybe a)-tryEnvDeserialization providers = do-  maybeConf <- runMaybeT $ do-    envVal <- MaybeT $ environmentLookup atpConfVarName-    jsonVal :: Value <- lift $ throwDecode (Text.encodeUtf8 envVal)-    provider <- MaybeT . pure $ jsonVal ^? key "provider" . _String-    logFM DebugS (ls [fmt|ATP_CONF requests AccessTokenProvider '${provider}'|])-    if provider `elem` providers-      then do logFM InfoS (ls [fmt|Using AccessTokenProvider '${NonEmpty.head providers}'|])-              pure jsonVal-      else MaybeT (pure Nothing)-  case maybeConf of-    Just conf ->-      Just <$> throwDecodeValue conf-    Nothing ->-      pure Nothing
src/Security/AccessTokenProvider/Internal/Providers/File.hs view
@@ -2,92 +2,89 @@ {-# LANGUAGE LambdaCase            #-} {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE QuasiQuotes           #-}+{-# LANGUAGE RecordWildCards       #-} {-# LANGUAGE ScopedTypeVariables   #-}  module Security.AccessTokenProvider.Internal.Providers.File-  ( providerProbeFile+  ( probeProviderFile   ) where -import           Control.Applicative import           Control.Exception.Safe import           Control.Lens import           Control.Monad.IO.Unlift import           Data.Format-import           Data.List.NonEmpty                                     (NonEmpty (..))-import qualified Data.Map                                               as Map+import qualified Data.Map                                             as Map import           Data.Maybe-import qualified Data.Text                                              as Text-import qualified Data.Text.Encoding                                     as Text-import           Katip+import qualified Data.Text.Encoding                                   as Text import           UnliftIO.STM -import qualified Security.AccessTokenProvider.Internal.Lenses           as L-import           Security.AccessTokenProvider.Internal.Providers.Common+import qualified Security.AccessTokenProvider.Internal.Lenses         as L import           Security.AccessTokenProvider.Internal.Types+import qualified Security.AccessTokenProvider.Internal.Types.Severity as Severity+import           Security.AccessTokenProvider.Internal.Util -providerProbeFile-  :: ( KatipContext m-     , MonadFilesystem m-     , MonadCatch m-     , MonadEnvironment m-     , MonadUnliftIO m )-  => AccessTokenName -> m (Maybe (AccessTokenProvider m t))-providerProbeFile tokenName = katipAddNamespace "probe-file" $-  tryNewProvider tokenName makeConf pure createFilePathTokenProvider+-- | Access Token Provider prober for file based access token+-- retrieval.+probeProviderFile :: (MonadUnliftIO m, MonadCatch m) => AtpProbe m+probeProviderFile = AtpProbe probeProvider -  where makeConf = do-          maybeTokenFile <- fmap Text.unpack <$> environmentLookup  "TOKEN_FILE"-          case maybeTokenFile of-            Just tokenFile ->-              pure . Just $ AtpConfFile { _tokens = Just Map.empty-                                        , _token  = Just tokenFile }-            Nothing ->-              tryEnvDeserialization ("file" :| [])+probeProvider+  :: (MonadCatch m, MonadUnliftIO m)+  => Backend m+  -> AccessTokenName+  -> m (Maybe (AccessTokenProvider m t))+probeProvider backend tokenName = do+  let BackendLog { .. } = backendLog backend+      BackendEnv { .. } = backendEnv backend+  logAddNamespace "probe-file" $ do+    envLookup "ATP_CONF_FILE" >>= \ case+      Just confS -> do+        logMsg Severity.Info [fmt|Trying access token provider 'file'|]+        throwDecode (Text.encodeUtf8 confS) >>= tryCreateProvider backend tokenName+      Nothing ->+        pure Nothing -createFilePathTokenProvider-  :: ( KatipContext m-     , MonadFilesystem m-     , MonadUnliftIO m-     , MonadCatch m )-  => AccessTokenName+tryCreateProvider+  :: (MonadUnliftIO m, MonadCatch m)+  => Backend m+  -> AccessTokenName   -> AtpConfFile   -> m (Maybe (AccessTokenProvider m t))-createFilePathTokenProvider (AccessTokenName tokenName) conf = do-  let tokenFileMap = fromMaybe Map.empty (conf ^. L.tokens)-      maybeTokenFile = Map.lookup tokenName tokenFileMap <|> (conf^.L.token)--  case maybeTokenFile of+tryCreateProvider backend (AccessTokenName tokenName) conf = do+  let BackendLog { .. } = backendLog backend+      tokenFileMap = fromMaybe Map.empty (conf ^. L.tokens)+  case Map.lookup tokenName tokenFileMap of     Just filename -> do-      logFM InfoS (ls [fmt|AccessTokenProvider starting|])+      logMsg Severity.Info [fmt|AccessTokenProvider starting|]       provider <- newProvider filename       pure (Just provider)     Nothing ->       pure Nothing    where newProvider filename = do-          readAction <- newReadAction filename+          readAction <- newReadAction backend filename           pure AccessTokenProvider { retrieveAccessToken = readAction                                    , releaseProvider     = pure () }  newReadAction-  :: ( MonadFilesystem m-     , MonadUnliftIO m-     , MonadCatch m-     , KatipContext m )-  => FilePath+  :: (MonadUnliftIO m, MonadCatch m)+  => Backend m+  -> FilePath   -> m (m (AccessToken t))-newReadAction filename = do+newReadAction backend filename = do+  let fsBackend = backendFilesystem backend+      BackendLog { .. } = backendLog backend   cache <- atomically $ newTVar (Left (toException AccessTokenProviderTokenMissing))   pure $-    tryAny (fileRead filename) >>= \ case+    tryAny (fileRead fsBackend filename) >>= \ case       Right bytes -> do         liftIO . atomically $ writeTVar cache (Right bytes)         pure (AccessToken (Text.decodeUtf8 bytes))       Left exn -> do-        logFM ErrorS (ls [fmt|Failed to read token file '${filename}': $exn|])+        logMsg Severity.Error [fmt|Failed to read token file '${filename}': $exn|]         liftIO (atomically (readTVar cache)) >>= \ case           Right bytes -> do-            logFM WarningS (ls [fmt|Using cached token from '${filename}'|])+            logMsg Severity.Warning [fmt|Using cached token from '${filename}'|]             pure (AccessToken (Text.decodeUtf8 bytes))           Left _exn ->             throwM exn -- Return newer exception.
src/Security/AccessTokenProvider/Internal/Providers/Fixed.hs view
@@ -1,51 +1,60 @@ {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE LambdaCase            #-} {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE QuasiQuotes           #-}+{-# LANGUAGE RecordWildCards       #-} {-# LANGUAGE ScopedTypeVariables   #-}  module Security.AccessTokenProvider.Internal.Providers.Fixed-  ( providerProbeFixed+  ( probeProviderFixed   ) where -import           Control.Applicative import           Control.Exception.Safe import           Control.Lens import           Control.Monad.IO.Class import           Data.Format-import           Data.List.NonEmpty                                     (NonEmpty (..))-import qualified Data.Map                                               as Map+import qualified Data.Map                                             as Map import           Data.Maybe-import           Katip+import qualified Data.Text.Encoding                                   as Text -import qualified Security.AccessTokenProvider.Internal.Lenses           as L-import           Security.AccessTokenProvider.Internal.Providers.Common+import qualified Security.AccessTokenProvider.Internal.Lenses         as L import           Security.AccessTokenProvider.Internal.Types+import qualified Security.AccessTokenProvider.Internal.Types.Severity as Severity+import           Security.AccessTokenProvider.Internal.Util -providerProbeFixed-  :: (KatipContext m, MonadIO m, MonadThrow m, MonadEnvironment m)-  => AccessTokenName-  -> m (Maybe (AccessTokenProvider m t))-providerProbeFixed tokenName = katipAddNamespace "probe-fixed" $-  tryNewProvider tokenName makeEnvConf pure createEnvTokenProvider+-- | Access Token Provider prober for environment based access token+-- retrieval.+probeProviderFixed :: (MonadIO m, MonadCatch m) => AtpProbe m+probeProviderFixed = AtpProbe probeProvider -  where makeEnvConf = do-          maybeToken <- environmentLookup  "TOKEN"-          case maybeToken of-            Just token -> pure . Just $ AtpConfFixed { _tokens = Just Map.empty-                                                     , _token = Just token }-            Nothing -> tryEnvDeserialization ("fixed" :| [])+probeProvider+  :: (MonadIO m, MonadThrow m)+  => Backend m+  -> AccessTokenName+  -> m (Maybe (AccessTokenProvider m t))+probeProvider backend tokenName = do+  let BackendLog { .. } = backendLog backend+      BackendEnv { .. } = backendEnv backend+  logAddNamespace "probe-fixed" $ do+    envLookup "ATP_CONF_FIXED" >>= \ case+      Just confS -> do+        logMsg Severity.Info [fmt|Trying access token provider 'fixed'|]+        throwDecode (Text.encodeUtf8 confS) >>= tryCreateProvider backend tokenName+      Nothing ->+        pure Nothing -createEnvTokenProvider-  :: (KatipContext m, MonadEnvironment m)-  => AccessTokenName+tryCreateProvider+  :: Monad m+  => Backend m+  -> AccessTokenName   -> AtpConfFixed   -> m (Maybe (AccessTokenProvider m t))-createEnvTokenProvider (AccessTokenName tokenName) conf =-  let tokensMap  = fromMaybe Map.empty (conf^.L.tokens)-      maybeToken = (conf^.L.token) <|> Map.lookup tokenName tokensMap-  in case maybeToken of+tryCreateProvider backend (AccessTokenName tokenName) conf =+  let BackendLog { .. } = backendLog backend+      tokensMap  = fromMaybe Map.empty (conf^.L.tokens)+  in case Map.lookup tokenName tokensMap of        Just token -> do-         logFM InfoS (ls [fmt|AccessTokenProvider started|])+         logMsg Severity.Info [fmt|AccessTokenProvider started|]          pure . Just $ AccessTokenProvider            { retrieveAccessToken = pure (AccessToken token)            , releaseProvider = pure ()
src/Security/AccessTokenProvider/Internal/Providers/OAuth2/Ropcg.hs view
@@ -2,60 +2,64 @@ {-# LANGUAGE LambdaCase            #-} {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE QuasiQuotes           #-}+{-# LANGUAGE RecordWildCards       #-} {-# LANGUAGE ScopedTypeVariables   #-}  module Security.AccessTokenProvider.Internal.Providers.OAuth2.Ropcg-  ( providerProbeRopcg+  ( probeProviderRopcg   ) where -import           Control.Applicative import           Control.Exception.Safe import           Control.Lens import           Control.Monad import           Control.Monad.IO.Class import           Control.Monad.IO.Unlift import           Data.Aeson-import qualified Data.ByteString                                        as ByteString-import qualified Data.ByteString.Base64                                 as B64+import qualified Data.ByteString                                      as ByteString+import qualified Data.ByteString.Base64                               as B64 import           Data.Format-import           Data.List.NonEmpty                                     (NonEmpty (..))-import qualified Data.Map                                               as Map+import qualified Data.Map                                             as Map import           Data.Maybe import           Data.Monoid-import qualified Data.Text                                              as Text-import qualified Data.Text.Encoding                                     as Text-import           Katip+import qualified Data.Text                                            as Text+import qualified Data.Text.Encoding                                   as Text import           Network.HTTP.Client import           Network.HTTP.Client.TLS import           Network.HTTP.Types-import qualified System.Environment                                     as Env+import qualified System.Environment                                   as Env import           System.FilePath import           System.Random import           UnliftIO.Async import           UnliftIO.Concurrent import           UnliftIO.STM -import qualified Security.AccessTokenProvider.Internal.Lenses           as L-import           Security.AccessTokenProvider.Internal.Providers.Common+import qualified Security.AccessTokenProvider.Internal.Lenses         as L import           Security.AccessTokenProvider.Internal.Types+import qualified Security.AccessTokenProvider.Internal.Types.Severity as Severity import           Security.AccessTokenProvider.Internal.Util -providerProbeRopcg-  :: ( MonadMask m-     , MonadUnliftIO m-     , KatipContext m-     , MonadEnvironment m-     , MonadHttp m-     , MonadFilesystem m )-  => AccessTokenName-  -> m (Maybe (AccessTokenProvider m t))-providerProbeRopcg tokenName =-  tryNewProvider tokenName mkConf createRopcgConf-    createTokenProviderResourceOwnerPasswordCredentials+-- | Access Token Provider prober for access token retrieval via+-- OAuth2 Resource-Owner-Password-Credentails-Grant.+probeProviderRopcg :: (MonadMask m, MonadUnliftIO m) => AtpProbe m+probeProviderRopcg = AtpProbe probeProvider -  where mkConf =-          tryEnvDeserialization-          ("resource-owner-password-credentials-grant" :| ["ropcg"])+probeProvider+  :: (MonadMask m, MonadUnliftIO m)+  => Backend m+  -> AccessTokenName+  -> m (Maybe (AccessTokenProvider m t))+probeProvider backend tokenName = do+  let BackendLog { .. } = backendLog backend+      BackendEnv { .. } = backendEnv backend+  logAddNamespace "probe-ropcg" $ do+    envLookup "ATP_CONF_ROPCG" >>= \ case+      Just confS -> do+        logMsg Severity.Info [fmt|Trying access token provider 'ropcg'|]+        throwDecode (Text.encodeUtf8 confS)+          >>= createRopcgConf+          >>= tryCreateProvider backend tokenName+      Nothing ->+        pure Nothing  -- | Derive an authorization header from provided client credentials. makeBasicAuthorizationHeader@@ -69,29 +73,33 @@   in ("Authorization", "Basic " <> b64Token)  retrieveJson-  :: (MonadFilesystem m, KatipContext m, FromJSON a, MonadCatch m)-  => FilePath+  :: (FromJSON a, MonadCatch m)+  => Backend m+  -> FilePath   -> m a-retrieveJson filename = do-  content <- fileRead filename+retrieveJson backend filename = do+  let fsBackend = backendFilesystem backend+      BackendLog { .. } = backendLog backend+  content <- fileRead fsBackend filename   case eitherDecodeStrict content of     Right a      -> return a     Left  errMsgStr -> do       let errMsg = Text.pack errMsgStr-      logFM ErrorS (ls [fmt|JSON deserialization error: $errMsg|])+      logMsg Severity.Error [fmt|JSON deserialization error: $errMsg|]       throwM . AccessTokenProviderDeserialization $         [fmt|Failed to deserialize '${filename}': $errMsg|]  -- | Retrieve credentials from credentials directory. retrieveCredentials-  :: (MonadFilesystem m, KatipContext m, MonadCatch m)-  => AtpConfRopcg+  :: MonadCatch m+  => Backend m+  -> AtpConfRopcg   -> m Credentials-retrieveCredentials conf = do+retrieveCredentials backend conf = do   let baseDir = conf^.L.credentialsDirectory-  userCred   <- retrieveJson+  userCred   <- retrieveJson backend                 (prefixIfRelative baseDir (conf^.L.resourceOwnerPasswordFile))-  clientCred <- retrieveJson+  clientCred <- retrieveJson backend                 (prefixIfRelative baseDir (conf^.L.clientPasswordFile))   return Credentials { _user   = userCred                      , _client = clientCred }@@ -101,18 +109,6 @@           then filename           else baseDir </> filename -maskHttpRequest-  :: Request-  -> Request-maskHttpRequest req =-  req { requestHeaders = maskHttpHeaders headers }-  where headers = requestHeaders req--        maskHttpHeaders = map maskHttpHeader--        maskHttpHeader ("Authorization", _) = ("Authorization", "XXXXXXXXXXXXXXXX")-        maskHttpHeader hdr = hdr- -- | Environment variable expected to contain the path to the mint -- credentials. envCredentialsDirectory :: String@@ -130,11 +126,11 @@       liftIO (fromMaybe "." <$> Env.lookupEnv envCredentialsDirectory)  createRopcgConf-  :: (KatipContext m, MonadIO m, MonadCatch m)+  :: (MonadIO m, MonadCatch m)   => AtpPreconfRopcg   -> m AtpConfRopcg createRopcgConf envConf = do-  authEndpoint         <- parseEndpoint "authentication" (envConf^.L.authEndpoint)+  authEndpoint         <- parseEndpoint (envConf^.L.authEndpoint)   credentialsDirectory <- retrieveCredentialsDir envConf   let clientPasswordFile        = fromMaybe defaultClientPasswordFile                                   (envConf^.L.clientPasswordFile)@@ -151,27 +147,23 @@     , _authEndpoint              = authEndpoint     , _manager                   = manager     , _tokens                    = envConf^.L.tokens-    , _token                     = envConf^.L.token     }    where defaultResourceOwnerPasswordFile = "user.json"-        defaultClientPasswordFile = "client.json"+        defaultClientPasswordFile        = "client.json"  -- | Main refreshing function. tryRefreshToken-  :: ( MonadCatch m-     , KatipContext m-     , MonadHttp m-     , MonadFilesystem m )-  => AtpConfRopcg+  :: MonadCatch m+  => Backend m+  -> AtpConfRopcg   -> AccessTokenName   -> AtpRopcgTokenDef   -> m AtpRopcgResponse-tryRefreshToken conf tokenName tokenDef =-  katipAddContext (sl "tokenName" tokenName) $-  katipAddNamespace "refreshActionOne" $ do-  credentials <- retrieveCredentials conf-  let manager        = conf^.L.manager+tryRefreshToken backend conf tokenName tokenDef =+  logAddNamespace "refreshActionOne" $ do+  credentials <- retrieveCredentials backend conf+  let httpBackend    = backendHttp backend       bodyParameters =         [ ("grant_type", "password")         , ("username",   Text.encodeUtf8 (credentials^.L.user.L.applicationUsername))@@ -181,25 +173,26 @@       httpRequest    = (conf^.L.authEndpoint) { method = "POST"                                               , requestHeaders = [authorization] }                        & urlEncodedBody bodyParameters-      maskedRequest  = Text.pack . show $ maskHttpRequest httpRequest-  logFM DebugS (ls [fmt|HTTP Request for token refreshing: $maskedRequest|])-  response <- httpRequestExecute httpRequest manager+  logMsg Severity.Debug [fmt|HTTP Request for token refreshing: ${tshow httpRequest}|]+  response <- httpRequestExecute httpBackend httpRequest   let status = responseStatus response       body   = responseBody response   when (status /= ok200) $ do-    logFM ErrorS (ls [fmt|Failed to refresh token: ${tshow response}|])+    logMsg Severity.Error [fmt|Failed to refresh token: ${tshow response}|]     throwM $ decodeOAuth2Error status body   case eitherDecode body :: Either String AtpRopcgResponse of     Right tokenResponse -> do-      logFM DebugS (ls [fmt|Successfully refreshed token '${tokenName}'|])+      logMsg Severity.Debug [fmt|Successfully refreshed token '${tokenName}'|]       pure tokenResponse     Left errMsgS -> do       let errMsg = Text.pack errMsgS-      logFM ErrorS (ls [fmt|Deserialization of token response failed: $errMsg|])+      logMsg Severity.Error [fmt|Deserialization of token response failed: $errMsg|]       throwM $ AccessTokenProviderDeserialization errMsg    where packScopes = ByteString.intercalate " " . map Text.encodeUtf8 +        BackendLog { .. } = backendLog backend+         decodeOAuth2Error status body =           case decode body of             Just problem ->@@ -209,17 +202,16 @@                 [fmt|Deserialization of OAuth2 error object failed; response status: ${tshow status}'|]  tokenRefreshLoop-  :: ( MonadCatch m-     , KatipContext m-     , MonadHttp m-     , MonadFilesystem m )-  => AtpConfRopcg+  :: forall m t+   . (MonadCatch m, MonadIO m)+  => Backend m+  -> AtpConfRopcg   -> AccessTokenName   -> AtpRopcgTokenDef   -> TMVar (Either SomeException (AccessToken t))   -> m ()-tokenRefreshLoop conf tokenName tokenDef cache = forever $ do-  eitherTokenResponse <- tryAny (tryRefreshToken conf tokenName tokenDef)+tokenRefreshLoop backend conf tokenName tokenDef cache = forever $ do+  eitherTokenResponse <- tryAny (tryRefreshToken backend conf tokenName tokenDef)   atomically $ do     let eitherToken = AccessToken . view L.accessToken <$> eitherTokenResponse     isEmptyTMVar cache >>= \ case@@ -231,8 +223,7 @@    where -- Returns duration in seconds.         computeDurationToWait-          :: KatipContext m-          => Either SomeException AtpRopcgResponse+          :: Either SomeException AtpRopcgResponse           -> m Double         computeDurationToWait eitherTokenResponse =           case eitherTokenResponse of@@ -243,9 +234,11 @@                   Nothing ->                     pure defaultRefreshInterval               Left exn -> do-                logFM ErrorS (ls [fmt|Failed to refresh token '${tokenName}': $exn|])+                logMsg Severity.Error [fmt|Failed to refresh token '${tokenName}': $exn|]                 liftIO $ randomRIO (1, 10) -- Some jitter: wait 1 - 10 seconds. +        BackendLog { .. } = backendLog backend+ -- | In seconds. defaultRefreshInterval :: Double defaultRefreshInterval = 60@@ -255,45 +248,39 @@ defaultRefreshTimeFactor :: Double defaultRefreshTimeFactor = 0.8 -createTokenProviderResourceOwnerPasswordCredentials-  :: ( MonadUnliftIO m-     , MonadMask m-     , KatipContext m-     , MonadHttp m-     , MonadFilesystem m )-  => AccessTokenName+tryCreateProvider+  :: (MonadUnliftIO m, MonadMask m)+  => Backend m+  -> AccessTokenName   -> AtpConfRopcg   -> m (Maybe (AccessTokenProvider m t))-createTokenProviderResourceOwnerPasswordCredentials tokenName conf = do+tryCreateProvider backend tokenName conf = do   let (AccessTokenName tokenNameText) = tokenName+      BackendLog { .. } = backendLog backend       maybeTokenDef = Map.lookup tokenNameText (conf^.L.tokens)-                      <|> (conf^.L.token)-   case maybeTokenDef of     Just tokenDef -> do-      logFM InfoS (ls [fmt|AccessTokenProvider starting|])+      logMsg Severity.Info [fmt|AccessTokenProvider starting|]       provider <- newProvider tokenDef       pure (Just provider)     Nothing ->       pure Nothing    where newProvider tokenDef = do-          (retrieveAction, releaseAction) <- newRetrieveAction conf tokenName tokenDef+          (retrieveAction, releaseAction) <- newRetrieveAction+            backend conf tokenName tokenDef           pure AccessTokenProvider { retrieveAccessToken = retrieveAction                                    , releaseProvider     = releaseAction } newRetrieveAction-  :: ( KatipContext m-     , MonadUnliftIO m-     , MonadCatch m-     , MonadHttp m-     , MonadFilesystem m )-  => AtpConfRopcg+  :: (MonadUnliftIO m, MonadCatch m)+  => Backend m+  -> AtpConfRopcg   -> AccessTokenName   -> AtpRopcgTokenDef   -> m (m (AccessToken t), m ())-newRetrieveAction conf tokenName tokenDef = do+newRetrieveAction backend conf tokenName tokenDef = do   cache <- atomically newEmptyTMVar-  asyncHandle <- async $ tokenRefreshLoop conf tokenName tokenDef cache+  asyncHandle <- async $ tokenRefreshLoop backend conf tokenName tokenDef cache   link asyncHandle   pure $ do     let retrieveAction = atomically (readTMVar cache) >>= \ case
+ src/Security/AccessTokenProvider/Internal/Providers/SimpleFixed.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE LambdaCase            #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE QuasiQuotes           #-}+{-# LANGUAGE RecordWildCards       #-}+{-# LANGUAGE ScopedTypeVariables   #-}++module Security.AccessTokenProvider.Internal.Providers.SimpleFixed+  ( probeProviderSimpleFixed+  ) where++import           Control.Exception.Safe+import           Control.Monad.IO.Class+import           Data.Format++import           Security.AccessTokenProvider.Internal.Types+import qualified Security.AccessTokenProvider.Internal.Types.Severity as Severity++-- | Access Token Provider prober for access token retrieval from the+-- @TOKEN@ environment retrieval.+probeProviderSimpleFixed :: (MonadIO m, MonadCatch m) => AtpProbe m+probeProviderSimpleFixed = AtpProbe probeProvider++probeProvider+  :: (MonadIO m, MonadThrow m)+  => Backend m+  -> AccessTokenName+  -> m (Maybe (AccessTokenProvider m t))+probeProvider backend tokenName = do+  let BackendLog { .. } = backendLog backend+  let BackendEnv { .. } = backendEnv backend+  logAddNamespace "probe-simple-fixed" $ do+    fmap AccessToken <$> envLookup "TOKEN" >>= \ case+      Just accessToken -> do+        logMsg Severity.Info [fmt|Trying access token provider 'simple-fixed'|]+        tryCreateProvider backend tokenName accessToken+      Nothing ->+        pure Nothing++tryCreateProvider+  :: Monad m+  => Backend m+  -> AccessTokenName+  -> AccessToken t+  -> m (Maybe (AccessTokenProvider m t))+tryCreateProvider backend _accessTokenName accessToken = do+  let BackendLog { .. } = backendLog backend+  logMsg Severity.Info [fmt|AccessTokenProvider started|]+  pure . Just $ AccessTokenProvider+    { retrieveAccessToken = pure accessToken+    , releaseProvider     = pure ()+    }
src/Security/AccessTokenProvider/Internal/Types.hs view
@@ -11,29 +11,23 @@  module Security.AccessTokenProvider.Internal.Types where -import           Control.Arrow import           Control.Exception-import           Control.Monad.IO.Class-import           Control.Monad.Reader-import           Control.Monad.Trans.Maybe-import           Control.Monad.Trans.State-import           Data.Aeson+import           Data.Aeson                                           hiding+                                                                       (Error) import           Data.Aeson.Casing import           Data.Aeson.TH-import           Data.ByteString           (ByteString)-import qualified Data.ByteString           as ByteString-import qualified Data.ByteString.Lazy      as ByteString.Lazy+import           Data.ByteString                                      (ByteString)+import qualified Data.ByteString.Lazy                                 as ByteString.Lazy import           Data.Format-import           Data.Map.Strict           (Map)+import           Data.Map.Strict                                      (Map) import           Data.String-import           Data.Text                 (Text)-import qualified Data.Text                 as Text+import           Data.Text                                            (Text) import           Data.Typeable import           GHC.Generics-import           Katip import           Network.HTTP.Client-import qualified System.Environment        as Env +import           Security.AccessTokenProvider.Internal.Types.Severity+ type LazyByteString = ByteString.Lazy.ByteString  data AccessTokenProvider (m :: * -> * ) t =@@ -54,14 +48,12 @@  data AtpConfFixed =   AtpConfFixed { _tokens :: Maybe (Map Text Text)-               , _token  :: Maybe Text                } deriving (Eq, Show, Generic)  $(deriveJSON (aesonDrop 1 snakeCase) ''AtpConfFixed)  data AtpConfFile =   AtpConfFile { _tokens :: Maybe (Map Text FilePath)-              , _token  :: Maybe FilePath               } deriving (Eq, Show, Generic)  $(deriveJSON (aesonDrop 1 snakeCase) ''AtpConfFile)@@ -80,7 +72,6 @@   , _refreshTimeFactor         :: Maybe Double   , _authEndpoint              :: Text   , _tokens                    :: Map Text AtpRopcgTokenDef-  , _token                     :: Maybe AtpRopcgTokenDef   } deriving (Eq, Show, Generic)  $(deriveJSON (aesonDrop 1 snakeCase) ''AtpPreconfRopcg)@@ -94,7 +85,6 @@   , _authEndpoint              :: Request   , _manager                   :: Manager   , _tokens                    :: Map Text AtpRopcgTokenDef-  , _token                     :: Maybe AtpRopcgTokenDef   } deriving (Generic)  -- | Type modelling the content of the credentials stored in a@@ -155,55 +145,28 @@ $(deriveJSON (aesonDrop 1 snakeCase) ''AtpRopcgResponse)  newtype AtpProbe m =-  AtpProbe (forall t. AccessTokenName -> m (Maybe (AccessTokenProvider m t)) )--class Monad m => MonadFilesystem m where-  fileRead :: FilePath -> m ByteString-  default fileRead :: (m ~ t n, MonadTrans t, MonadFilesystem n) => FilePath -> m ByteString-  fileRead = lift . fileRead--instance MonadFilesystem IO where-  fileRead = ByteString.readFile--instance MonadFilesystem m => MonadFilesystem (ReaderT r m)-instance MonadFilesystem m => MonadFilesystem (MaybeT m)-instance MonadFilesystem m => MonadFilesystem (KatipContextT m)-instance MonadFilesystem m => MonadFilesystem (KatipT m)-instance MonadFilesystem m => MonadFilesystem (StateT s m)--class Monad m => MonadEnvironment m where-  environmentLookup :: Text -> m (Maybe Text)-  default environmentLookup :: (m ~ t n, MonadTrans t, MonadEnvironment n)-                            => Text-                            -> m (Maybe Text)-  environmentLookup = lift . environmentLookup+  AtpProbe (forall t. Backend m -> AccessTokenName -> m (Maybe (AccessTokenProvider m t)) ) -instance MonadEnvironment IO where-  environmentLookup =-    Text.unpack-    >>> Env.lookupEnv-    >>> liftIO-    >>> fmap (fmap Text.pack)+data BackendHttp m = BackendHttp+  { httpRequestExecute :: Request -> m (Response LazyByteString)+  } -instance MonadEnvironment m => MonadEnvironment (ReaderT r m)-instance MonadEnvironment m => MonadEnvironment (MaybeT m)-instance MonadEnvironment m => MonadEnvironment (KatipContextT m)-instance MonadEnvironment m => MonadEnvironment (KatipT m)-instance MonadEnvironment m => MonadEnvironment (StateT s m)+data BackendEnv m = BackendEnv+  { envLookup :: Text -> m (Maybe Text)+  } -class Monad m => MonadHttp m where-  httpRequestExecute :: Request -> Manager -> m (Response  LazyByteString)-  default httpRequestExecute :: (m ~ t n, MonadTrans t, MonadHttp n)-                             => Request-                             -> Manager-                             -> m (Response  LazyByteString)-  httpRequestExecute req manager = lift $ httpRequestExecute req manager+data BackendFilesystem m = BackendFilesystem+  { fileRead :: FilePath -> m ByteString+  } -instance MonadHttp IO where-  httpRequestExecute = httpLbs+data BackendLog m = BackendLog+  { logAddNamespace :: forall a. Text -> m a -> m a+  , logMsg          :: Severity -> Text -> m ()+  } -instance MonadHttp m => MonadHttp (ReaderT r m)-instance MonadHttp m => MonadHttp (MaybeT m)-instance MonadHttp m => MonadHttp (KatipContextT m)-instance MonadHttp m => MonadHttp (KatipT m)-instance MonadHttp m => MonadHttp (StateT s m)+data Backend m = Backend+  { backendHttp       :: BackendHttp m+  , backendEnv        :: BackendEnv m+  , backendFilesystem :: BackendFilesystem m+  , backendLog        :: BackendLog m+  }
+ src/Security/AccessTokenProvider/Internal/Types/Severity.hs view
@@ -0,0 +1,9 @@+module Security.AccessTokenProvider.Internal.Types.Severity where++data Severity+  = Debug+  | Info+  | Warning+  | Error+  | Alert+  deriving (Eq, Ord, Enum, Show)
src/Security/AccessTokenProvider/Internal/Util.hs view
@@ -13,10 +13,8 @@ import           Data.Aeson import           Data.ByteString                             (ByteString) import qualified Data.ByteString.Lazy                        as ByteString.Lazy-import           Data.Format import           Data.Text                                   (Text) import qualified Data.Text                                   as Text-import           Katip import           Network.HTTP.Client  import           Security.AccessTokenProvider.Internal.Types@@ -38,13 +36,11 @@       throwM $ AccessTokenProviderDeserialization (Text.pack errMsg)  parseEndpoint-  :: (KatipContext m, MonadIO m, MonadCatch m)+  :: (MonadIO m, MonadCatch m)   => Text-  -> Text   -> m Request-parseEndpoint label endpoint =+parseEndpoint endpoint =   parseRequest (Text.unpack endpoint) `catchAny` \ exn -> do-    logFM ErrorS (ls [fmt|Failed to parse $label endpoint '${endpoint}': $exn|])     throwM exn  tshow
tests/Security/AccessTokenProvider/Internal/Providers/Test.hs view
@@ -11,18 +11,17 @@ 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 qualified Data.Text                                  as Text+import           Data.Semigroup import qualified Data.Text.Encoding                         as Text import           Data.UUID                                  (UUID)-import           Katip 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           UnliftIO.Concurrent  import           Security.AccessTokenProvider import qualified Security.AccessTokenProvider               as ATP@@ -32,100 +31,86 @@ securityAccessTokenProviderInternalProvidersTest :: [TestTree] securityAccessTokenProviderInternalProvidersTest =   [ testGroup "Security.AccessTokenProvider.Internal.Providers"-    [ testCase "Fixed Provider reads from TOKEN"-        fixedProviderReadsFromToken-    , testCase "Fixed Provider reads from ATP_CONF"+    [ testCase "SimpleFixed Provider reads from TOKEN"+        simpleFixedProviderReadsFromToken+    , testCase "Fixed Provider reads from ATP_CONF_FIXED"         fixedProviderReadsFromConf-    , testCase "Fixed Provider reads from ATP_CONF and lookup fails"+    , testCase "Fixed Provider reads from ATP_CONF_FIXED and lookup fails"         fixedProviderReadsFromConfLookupFails-    , testCase "File Provider reads from TOKEN_FILE"-        fileProviderReadsFromTokenFile-    , testCase "File Provider reads from ATP_CONF"+    , testCase "File Provider reads from ATP_CONF_FILE"         fileProviderReadsFromConf-    , testCase "File Provider reads from ATP_CONF and lookup fails"+    , testCase "File Provider reads from ATP_CONF_FILE and lookup fails"         fileProviderReadsFromConfLookupFails-    , testCase "Ropcg Provider reads from ATP_CONF"+    , testCase "Ropcg Provider reads from ATP_CONF_ROPCG"         ropcgProviderReadsFromConf     ]   ] -fixedProviderReadsFromToken :: Assertion-fixedProviderReadsFromToken = do+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 <- new (AccessTokenName "some-random-token-name")+    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|{"provider": "fixed", "tokens": {"label1": "$token"}}|]+  let conf = [fmt|{"tokens": {"label1": "$token"}}|]       testState = TestState                   { _testStateFilesystem = Map.empty-                  , _testStateEnvironment = Map.fromList [ ("ATP_CONF", conf) ]+                  , _testStateEnvironment = Map.fromList [ ("ATP_CONF_FIXED", conf) ]                   , _testStateHttpResponse = Nothing                   , _testStateHttpRequests = []+                  , _testStateLog = []                   }   evalTestStack testState $ do-    tokenProvider <- new (AccessTokenName "label1")+    tokenProvider <- newWithBackend mockBackend (AccessTokenName "label1")     (AccessToken token') <- retrieveAccessToken tokenProvider     liftIO $ token @=? token'  fixedProviderReadsFromConfLookupFails :: Assertion fixedProviderReadsFromConfLookupFails = do   token <- tshow <$> (randomIO :: IO UUID)-  let conf = [fmt|{"provider": "fixed", "tokens": {"label1": "$token"}}|]+  let conf = [fmt|{"tokens": {"label1": "$token"}}|]       testState = TestState                   { _testStateFilesystem = Map.empty-                  , _testStateEnvironment = Map.fromList [ ("ATP_CONF", conf) ]+                  , _testStateEnvironment = Map.fromList [ ("ATP_CONF_FIXED", conf) ]                   , _testStateHttpResponse = Nothing                   , _testStateHttpRequests = []+                  , _testStateLog = []                   }   evalTestStack testState $ do-    Left _ <- tryAny $ new (AccessTokenName "label2")+    Left _ <- tryAny $ newWithBackend mockBackend (AccessTokenName "label2")     pure () -fileProviderReadsFromTokenFile :: Assertion-fileProviderReadsFromTokenFile = do-  tokenText <- tshow <$> (randomIO :: IO UUID)-  let tokenBytes = Text.encodeUtf8 tokenText-      filename = "/a/b/c"-      testState = TestState-                  { _testStateFilesystem =-                      Map.fromList [ (filename, tokenBytes) ]-                  , _testStateEnvironment =-                      Map.fromList [ ("TOKEN_FILE", Text.pack filename) ]-                  , _testStateHttpResponse = Nothing-                  , _testStateHttpRequests = []-                  }-  evalTestStack testState $ do-    tokenProvider <- new (AccessTokenName "some-random-token-name")-    (AccessToken token') <- retrieveAccessToken tokenProvider-    liftIO $ tokenText @=? token'- fileProviderReadsFromConf :: Assertion fileProviderReadsFromConf = do   tokenText <- tshow <$> (randomIO :: IO UUID)   let tokenBytes = Text.encodeUtf8 tokenText       filename = "/a/b/c"-      conf = [fmt|{"provider": "file", "tokens": {"label1": "$filename"}}|]+      conf = [fmt|{"tokens": {"label1": "$filename"}}|]       testState = TestState                   { _testStateFilesystem =                       Map.fromList [ (filename, tokenBytes) ]                   , _testStateEnvironment =-                      Map.fromList [ ("ATP_CONF", conf) ]+                      Map.fromList [ ("ATP_CONF_FILE", conf) ]                   , _testStateHttpResponse = Nothing                   , _testStateHttpRequests = []+                  , _testStateLog = []                   }   evalTestStack testState $ do-    tokenProvider <- new (AccessTokenName "label1")+    tokenProvider <- newWithBackend mockBackend (AccessTokenName "label1")     (AccessToken token') <- retrieveAccessToken tokenProvider     liftIO $ tokenText @=? token' @@ -134,17 +119,18 @@   tokenText <- tshow <$> (randomIO :: IO UUID)   let tokenBytes = Text.encodeUtf8 tokenText       filename = "/a/b/c"-      conf = [fmt|{"provider": "file", "tokens": {"label1": "$filename"}}|]+      conf = [fmt|{"tokens": {"label1": "$filename"}}|]       testState = TestState                   { _testStateFilesystem =                       Map.fromList [ (filename, tokenBytes) ]                   , _testStateEnvironment =-                      Map.fromList [ ("ATP_CONF", conf) ]+                      Map.fromList [ ("ATP_CONF_FILE", conf) ]                   , _testStateHttpResponse = Nothing                   , _testStateHttpRequests = []+                  , _testStateLog = []                   }   evalTestStack testState $ do-    Left _ <- tryAny $ new (AccessTokenName "label2")+    Left _ <- tryAny $ newWithBackend mockBackend (AccessTokenName "label2")     pure ()  ropcgProviderReadsFromConf :: Assertion@@ -155,7 +141,7 @@                     "auth_endpoint": "https://localhost",                     "tokens": {"label1": {"scopes": ["foo"]}}                   }|]-      responseBody = ByteString.Lazy.fromStrict . Text.encodeUtf8 $+      rspBody = ByteString.Lazy.fromStrict . Text.encodeUtf8 $         [fmt|{"scope":        "foo",               "expires_in":   60,               "token_type":   "test",@@ -164,7 +150,7 @@       response = Response { responseStatus    = ok200                           , responseVersion   = http20                           , responseHeaders   = []-                          , responseBody      = responseBody+                          , responseBody      = rspBody                           , responseCookieJar = CJ []                           , responseClose'    = ResponseClose (pure ())                           }@@ -180,20 +166,21 @@                     [ ("/credentials/user.json",   userCredentials)                     , ("/credentials/client.json", clientCredentials)                     ]-                  , _testStateEnvironment  = Map.fromList [ ("ATP_CONF", conf) ]+                  , _testStateEnvironment  = Map.fromList [ ("ATP_CONF_ROPCG", conf) ]                   , _testStateHttpResponse = Just response                   , _testStateHttpRequests = []+                  , _testStateLog = []                   }   (_, testState') <- runTestStack testState $ do-    tokenProvider <- new (AccessTokenName "label1")+    tokenProvider <- newWithBackend mockBackend (AccessTokenName "label1")     (AccessToken token) <- retrieveAccessToken tokenProvider     liftIO $ tokenText @=? token     pure ()   1 @=? length (testState'^.testStateHttpRequests)   pure () -retrieveSomeToken :: KatipContextT IO ()+retrieveSomeToken :: IO () retrieveSomeToken = do   tokenProvider <- ATP.new (AccessTokenName "token-name")   token <- ATP.retrieveAccessToken tokenProvider-  liftIO $ print token+  print token
tests/Test.hs view
@@ -14,53 +14,38 @@  import           Control.Arrow import           Control.Lens-import           Control.Monad.Catch                         hiding (bracket)+import           Control.Monad.Catch                                  hiding+                                                                       (bracket) import           Control.Monad.IO.Class import           Control.Monad.IO.Unlift import           Control.Monad.Reader import           Control.Monad.State-import           Data.ByteString                             (ByteString)-import qualified Data.ByteString.Lazy                        as ByteString.Lazy+import           Data.ByteString                                      (ByteString)+import qualified Data.ByteString.Lazy                                 as ByteString.Lazy import           Data.IORef-import           Data.Map.Strict                             (Map)-import qualified Data.Map.Strict                             as Map-import           Data.Text                                   (Text)-import           Katip-import           Katip.Monadic+import           Data.Map.Strict                                      (Map)+import qualified Data.Map.Strict                                      as Map+import           Data.Text                                            (Text) import           Network.HTTP.Client-import           System.IO-import           UnliftIO.Exception  import           Security.AccessTokenProvider.Internal.Types+import           Security.AccessTokenProvider.Internal.Types.Severity  runTestStack :: TestState -> TestStack a -> IO (a, TestState) runTestStack testState m = do   s <- newIORef testState   a <- m & (_runTestStack-            >>> runKatip             >>> flip runReaderT s)   (a,) <$> readIORef s -runKatip :: MonadUnliftIO m => KatipContextT m a -> m a-runKatip m = do-  handleScribe <- liftIO $ mkHandleScribe ColorIfTerminal stdout WarningS V2-  let makeLogEnv = do-        logEnv <- initLogEnv "test-suite" "test"-        registerScribe "stdout" handleScribe defaultScribeSettings logEnv-  bracket (liftIO makeLogEnv) (liftIO . closeScribes) $ \ logEnv -> do-    let initialContext = ()-    let initialNamespace = "main"-    runKatipContextT logEnv initialContext initialNamespace m- evalTestStack :: TestState -> TestStack a -> IO a evalTestStack testState m = do   s <- newIORef testState   m & (_runTestStack-       >>> runKatip        >>> flip runReaderT s)  newtype TestStack a = TestStack-  { _runTestStack :: KatipContextT (ReaderT (IORef TestState) IO) a+  { _runTestStack :: ReaderT (IORef TestState) IO a   } deriving ( Functor              , Applicative              , Monad@@ -69,8 +54,6 @@              , MonadMask              , MonadReader (IORef TestState)              , MonadIO-             , Katip-             , KatipContext              )  instance MonadUnliftIO TestStack where@@ -83,6 +66,7 @@             , _testStateEnvironment  :: Map Text Text             , _testStateHttpRequests :: [Request]             , _testStateHttpResponse :: Maybe (Response ByteString.Lazy.ByteString)+            , _testStateLog          :: [(Severity, Text)]             }  makeFieldsNoPrefix ''TestState@@ -95,23 +79,46 @@     envRef <- ask     liftIO $ writeIORef envRef s -instance MonadEnvironment TestStack where-  environmentLookup name =-    Map.lookup name <$> gets (view testStateEnvironment)+mockBackend :: Backend TestStack+mockBackend =+  Backend { backendHttp       = mockBackendHttp+          , backendEnv        = mockBackendEnv+          , backendFilesystem = mockBackendFilesystem+          , backendLog        = mockBackendLog+          } -instance MonadFilesystem TestStack where-  fileRead filename = do-    fs <- gets (view testStateFilesystem)-    case Map.lookup filename fs of-      Just bytes -> pure bytes-      _          -> error "FIXME: file not found"+mockBackendHttp :: BackendHttp TestStack+mockBackendHttp =+  BackendHttp { httpRequestExecute = \ request -> do+                  testStateHttpRequests %= (request :)+                  maybeResponse <- gets (view testStateHttpResponse)+                  case maybeResponse of+                    Just response ->+                      pure response+                    Nothing ->+                      error "FIXME"+              } -instance MonadHttp TestStack where-  httpRequestExecute request _manager = do-    testStateHttpRequests %= (request :)-    maybeResponse <- gets (view testStateHttpResponse)-    case maybeResponse of-      Just response ->-        pure response-      Nothing ->-        error "FIXME"+mockBackendEnv :: BackendEnv TestStack+mockBackendEnv =+  BackendEnv { envLookup = \ name ->+                 Map.lookup name <$> gets (view testStateEnvironment)+              }++mockBackendFilesystem :: BackendFilesystem TestStack+mockBackendFilesystem =+  BackendFilesystem { fileRead = \ filename -> do+                        fs <- gets (view testStateFilesystem)+                        case Map.lookup filename fs of+                          Just bytes ->+                            pure bytes+                          Nothing ->+                            error "FIXME: file not found"+                    }++mockBackendLog :: BackendLog TestStack+mockBackendLog =+  BackendLog { logAddNamespace = \ _namespace -> id+             , logMsg = \ severity msg ->+                 modify (testStateLog %~ ((severity, msg) :))+             }