planb-token-introspection 0.1.2.0 → 0.1.3.0
raw patch · 6 files changed
+73/−52 lines, 6 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Network.PlanB.Introspection: data Conf m
- Network.PlanB.Introspection: data IntrospectionException
- Network.PlanB.Introspection: httpRequestExecuteIO :: MonadIO m => Maybe Manager -> Request -> m (Response LazyByteString)
- Network.PlanB.Introspection: introspectToken :: TokenIntrospector m -> ByteString -> m TokenInfo
- Network.PlanB.Introspection: newWithBackend :: (MonadThrow m, MonadIO m) => Backend m -> Text -> m (TokenIntrospector m)
- Network.PlanB.Introspection.Internal: data IntrospectionException
- Network.PlanB.Introspection.Internal: httpRequestExecuteIO :: MonadIO m => Maybe Manager -> Request -> m (Response LazyByteString)
- Network.PlanB.Introspection.Internal: introspectToken :: TokenIntrospector m -> ByteString -> m TokenInfo
- Network.PlanB.Introspection.Internal.Types: data IntrospectionException
- Network.PlanB.Introspection.Internal.Types: instance GHC.Exception.Exception Network.PlanB.Introspection.Internal.Types.IntrospectionException
- Network.PlanB.Introspection.Internal.Types: instance GHC.Show.Show Network.PlanB.Introspection.Internal.Types.IntrospectionException
+ Network.PlanB.Introspection: TokenIntrospector :: (ByteString -> m TokenInfo) -> TokenIntrospector m
+ Network.PlanB.Introspection: [introspectToken] :: TokenIntrospector m -> ByteString -> m TokenInfo
+ Network.PlanB.Introspection: data IntrospectionError
+ Network.PlanB.Introspection: data TokenIntrospector m
+ Network.PlanB.Introspection.Internal: Backend :: BackendHttp m -> BackendEnv m -> Backend m
+ Network.PlanB.Introspection.Internal: BackendEnv :: (Text -> m (Maybe Text)) -> BackendEnv m
+ Network.PlanB.Introspection.Internal: BackendHttp :: (Request -> m (Response LazyByteString)) -> BackendHttp m
+ Network.PlanB.Introspection.Internal: TokenIntrospector :: (ByteString -> m TokenInfo) -> TokenIntrospector m
+ Network.PlanB.Introspection.Internal: [backendEnv] :: Backend m -> BackendEnv m
+ Network.PlanB.Introspection.Internal: [backendHttp] :: Backend m -> BackendHttp m
+ Network.PlanB.Introspection.Internal: [envLookup] :: BackendEnv m -> Text -> m (Maybe Text)
+ Network.PlanB.Introspection.Internal: [httpRequestExecute] :: BackendHttp m -> Request -> m (Response LazyByteString)
+ Network.PlanB.Introspection.Internal: [introspectToken] :: TokenIntrospector m -> ByteString -> m TokenInfo
+ Network.PlanB.Introspection.Internal: backendIO :: MonadIO m => Maybe Manager -> Backend m
+ Network.PlanB.Introspection.Internal: data Backend m
+ Network.PlanB.Introspection.Internal: data BackendEnv m
+ Network.PlanB.Introspection.Internal: data BackendHttp m
+ Network.PlanB.Introspection.Internal: data IntrospectionError
+ Network.PlanB.Introspection.Internal: data TokenIntrospector m
+ Network.PlanB.Introspection.Internal.Types: data IntrospectionError
+ Network.PlanB.Introspection.Internal.Types: instance GHC.Classes.Eq Network.PlanB.Introspection.Internal.Types.IntrospectionError
+ Network.PlanB.Introspection.Internal.Types: instance GHC.Exception.Exception Network.PlanB.Introspection.Internal.Types.IntrospectionError
+ Network.PlanB.Introspection.Internal.Types: instance GHC.Generics.Generic Network.PlanB.Introspection.Internal.Types.IntrospectionError
+ Network.PlanB.Introspection.Internal.Types: instance GHC.Show.Show Network.PlanB.Introspection.Internal.Types.IntrospectionError
- Network.PlanB.Introspection: DeserializationFailure :: Text -> ByteString -> IntrospectionException
+ Network.PlanB.Introspection: DeserializationFailure :: Text -> ByteString -> IntrospectionError
- Network.PlanB.Introspection: InvalidRequest :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection: InvalidRequest :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection: InvalidToken :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection: InvalidToken :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection: NoEndpoint :: IntrospectionException
+ Network.PlanB.Introspection: NoEndpoint :: IntrospectionError
- Network.PlanB.Introspection: Other :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection: Other :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection.Internal: DeserializationFailure :: Text -> ByteString -> IntrospectionException
+ Network.PlanB.Introspection.Internal: DeserializationFailure :: Text -> ByteString -> IntrospectionError
- Network.PlanB.Introspection.Internal: InvalidRequest :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection.Internal: InvalidRequest :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection.Internal: InvalidToken :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection.Internal: InvalidToken :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection.Internal: NoEndpoint :: IntrospectionException
+ Network.PlanB.Introspection.Internal: NoEndpoint :: IntrospectionError
- Network.PlanB.Introspection.Internal: Other :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection.Internal: Other :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection.Internal.Types: DeserializationFailure :: Text -> ByteString -> IntrospectionException
+ Network.PlanB.Introspection.Internal.Types: DeserializationFailure :: Text -> ByteString -> IntrospectionError
- Network.PlanB.Introspection.Internal.Types: InvalidRequest :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection.Internal.Types: InvalidRequest :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection.Internal.Types: InvalidToken :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection.Internal.Types: InvalidToken :: ErrorResponse -> IntrospectionError
- Network.PlanB.Introspection.Internal.Types: NoEndpoint :: IntrospectionException
+ Network.PlanB.Introspection.Internal.Types: NoEndpoint :: IntrospectionError
- Network.PlanB.Introspection.Internal.Types: Other :: ErrorResponse -> IntrospectionException
+ Network.PlanB.Introspection.Internal.Types: Other :: ErrorResponse -> IntrospectionError
Files
- planb-token-introspection.cabal +1/−1
- src/Network/PlanB/Introspection.hs +4/−6
- src/Network/PlanB/Introspection/Internal.hs +13/−9
- src/Network/PlanB/Introspection/Internal/Types.hs +16/−11
- tests/Network/PlanB/Introspection/Internal/Test.hs +24/−2
- tests/Network/PlanB/Introspection/Test.hs +15/−23
planb-token-introspection.cabal view
@@ -1,5 +1,5 @@ name: planb-token-introspection-version: 0.1.2.0+version: 0.1.3.0 synopsis: Token Introspection for PlanB description: This package provides token introspection functionality for the PlanB token provider.
src/Network/PlanB/Introspection.hs view
@@ -1,14 +1,12 @@ module Network.PlanB.Introspection ( TokenInfo(..)- , Conf- , IntrospectionException(..)+ , IntrospectionError(..) , ErrorResponse(..)+ , TokenIntrospector(..) , new , newWithManager , newFromEnv- , newWithBackend- , httpRequestExecuteIO- , introspectToken- ) where+ )+where import Network.PlanB.Introspection.Internal
src/Network/PlanB/Introspection/Internal.hs view
@@ -5,14 +5,17 @@ module Network.PlanB.Introspection.Internal ( TokenInfo(..) , Conf- , IntrospectionException(..)+ , IntrospectionError(..) , ErrorResponse(..)+ , TokenIntrospector(..)+ , Backend(..)+ , BackendEnv(..)+ , BackendHttp(..) , new , newWithManager , newFromEnv , newWithBackend- , httpRequestExecuteIO- , introspectToken+ , backendIO ) where import Control.Arrow@@ -33,22 +36,23 @@ import Network.PlanB.Introspection.Internal.Types --- | Create a new PlanB introspector using the provided endpoint.+-- | Create a new PlanB token introspector using the provided endpoint. Uses a global default HTTP manager. new :: (MonadThrow m, MonadIO m) => Text -> m (TokenIntrospector m) new = newWithBackend (backendIO Nothing) --- | Create a new PlanB introspector using the provided endpoint and--- manager.+-- | Create a new PlanB toke introspector using the provided endpoint and+-- HTTP manager. newWithManager :: (MonadThrow m, MonadIO m) => Manager -> Text -> m (TokenIntrospector m) newWithManager manager = newWithBackend (backendIO (Just manager)) +-- | Produces the default IO backend. backendIO :: MonadIO m- => Maybe Manager+ => Maybe Manager -- ^ Use global default HTTP manager if 'Nothing'. -> Backend m backendIO maybeManager = Backend { backendHttp = httpBackendIO maybeManager@@ -85,7 +89,7 @@ Nothing -> throwM NoEndpoint newWithBackend backend endpoint --- | Create a new PlanB introspector using the provided backend and endpoint.+-- | Create a new PlanB token introspector using the provided backend and endpoint. newWithBackend :: (MonadThrow m, MonadIO m) => Backend m@@ -143,7 +147,7 @@ BackendHttp { .. } = backend & backendHttp bodyToPlanBException- :: ByteString -> IntrospectionException+ :: ByteString -> IntrospectionError bodyToPlanBException bytes = case eitherDecodeStrict bytes of Right err @ ErrorResponse { .. } ->
src/Network/PlanB/Introspection/Internal/Types.hs view
@@ -8,10 +8,10 @@ import Control.Exception.Safe import Data.Aeson.Casing import Data.Aeson.TH-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.Set-import Data.Text (Text)+import Data.Text ( Text ) import GHC.Generics import Network.HTTP.Client @@ -20,9 +20,10 @@ -- | A 'TokenIntrospector' can be used for introspecting tokens. data TokenIntrospector m = TokenIntrospector- { introspectToken :: ByteString -> m TokenInfo -- ^ Introspect the provided token+ { introspectToken :: ByteString -> m TokenInfo -- ^ Introspect the provided token. } +-- | Information returned on a successful token introspection. data TokenInfo = TokenInfo { tokenInfoExpiresIn :: Int , tokenInfoScope :: Set Text@@ -40,6 +41,7 @@ { envLookup :: Text -> m (Maybe Text) } +-- | Type for backends for the PlanB token introspector. data Backend m = Backend { backendHttp :: BackendHttp m , backendEnv :: BackendEnv m@@ -49,6 +51,7 @@ { confIntrospectionRequest :: Request , confBackend :: Backend m } +-- | Contains the error response data returned from a PlanB server in case of an introspection error. data ErrorResponse = ErrorResponse { errorResponseError :: Text , errorResponseErrorDescription :: Maybe Text@@ -56,11 +59,13 @@ $(deriveJSON (aesonDrop (length ("errorResponse" :: String)) snakeCase) ''ErrorResponse) -data IntrospectionException = DeserializationFailure Text ByteString- | InvalidRequest ErrorResponse- | InvalidToken ErrorResponse- | Other ErrorResponse- | NoEndpoint- deriving (Typeable, Show)+-- | This type models the error scenarios specific to a token introspection attempt. These can be used+-- as exceptions and are in fact thrown by a PlanB token introspector.+data IntrospectionError = DeserializationFailure Text ByteString+ | InvalidRequest ErrorResponse+ | InvalidToken ErrorResponse+ | Other ErrorResponse+ | NoEndpoint+ deriving (Typeable, Show, Eq, Generic) -instance Exception IntrospectionException+instance Exception IntrospectionError
tests/Network/PlanB/Introspection/Internal/Test.hs view
@@ -29,9 +29,32 @@ testIntrospectToken , testCase "Deserialization failure is converted to exception" testDeserializationFailure+ , testCase "InvalidToken exception is thrown"+ testInvalidToken ] ] +testInvalidToken :: Assertion+testInvalidToken = do+ let rspBody = ByteString.Lazy.fromStrict . Text.encodeUtf8 $+ [fmt|{"error":"invalid_token","error_description":"Access Token not valid"}|]+ response = Response { responseStatus = status401+ , responseVersion = http20+ , responseHeaders = []+ , responseBody = rspBody+ , responseCookieJar = CJ []+ , responseClose' = ResponseClose (pure ())+ }+ testState = TestState+ { _testStateHttpResponse = Just response+ , _testStateHttpRequests = []+ , _testStateEnvironment = Map.empty+ }+ Left (PlanB.InvalidToken _) <- try $ runTestStack testState $ do+ introspector <- makeTestIntrospector+ void $ introspectToken introspector "some-token"+ pure ()+ testDeserializationFailure :: Assertion testDeserializationFailure = do let rspBody = ByteString.Lazy.fromStrict . Text.encodeUtf8 $ "{something broken"@@ -47,10 +70,9 @@ , _testStateHttpRequests = [] , _testStateEnvironment = Map.empty }- tokenName = "some-token-name" Left (PlanB.DeserializationFailure _ _) <- try $ runTestStack testState $ do introspector <- makeTestIntrospector- void $ introspectToken introspector tokenName+ void $ introspectToken introspector "some-token" pure () testIntrospectToken :: Assertion
tests/Network/PlanB/Introspection/Test.hs view
@@ -13,26 +13,24 @@ 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 qualified Data.ByteString.Lazy as ByteString.Lazy+import qualified Data.ByteString.Lazy as ByteString.Lazy import Data.IORef-import Data.Map (Map)-import qualified Data.Map as Map-import Data.Text (Text)+import Data.Map ( Map )+import qualified Data.Map as Map+import Data.Text ( Text ) import Network.HTTP.Client- import Network.PlanB.Introspection.Internal-import Network.PlanB.Introspection.Internal.Types runTestStack :: TestState -> TestStack a -> IO (a, TestState) runTestStack testState m = do s <- newIORef testState a <- m & (_runTestStack >>> flip runReaderT s)- (a,) <$> readIORef s+ (a, ) <$> readIORef s evalTestStack :: TestState -> TestStack a -> IO a evalTestStack testState m = do@@ -72,23 +70,20 @@ envRef <- ask liftIO $ writeIORef envRef s -mockHttpRequestExecute :: Request -> TestStack (Response LazyByteString)+mockHttpRequestExecute+ :: Request -> TestStack (Response ByteString.Lazy.ByteString) mockHttpRequestExecute request = do testStateHttpRequests %= (request :) maybeResponse <- gets (view testStateHttpResponse) case maybeResponse of- Just response ->- pure response- Nothing ->- error "FIXME"+ Just response -> pure response+ Nothing -> error "FIXME" mockHttpBackend :: BackendHttp TestStack-mockHttpBackend =- BackendHttp { httpRequestExecute = mockHttpRequestExecute }+mockHttpBackend = BackendHttp {httpRequestExecute = mockHttpRequestExecute} mockEnvBackend :: BackendEnv TestStack-mockEnvBackend =- BackendEnv { envLookup = mockEnvLookup }+mockEnvBackend = BackendEnv {envLookup = mockEnvLookup} mockEnvLookup :: Text -> TestStack (Maybe Text) mockEnvLookup name = do@@ -96,11 +91,8 @@ pure $ Map.lookup name environment mockBackend :: Backend TestStack-mockBackend = Backend- { backendHttp = mockHttpBackend- , backendEnv = mockEnvBackend- }+mockBackend =+ Backend {backendHttp = mockHttpBackend, backendEnv = mockEnvBackend} makeTestIntrospector :: TestStack (TokenIntrospector TestStack)-makeTestIntrospector =- newWithBackend mockBackend "https://localhost"+makeTestIntrospector = newWithBackend mockBackend "https://localhost"