oidc-client 0.3.0.0 → 0.3.0.1
raw patch · 6 files changed
+80/−20 lines, 6 filesdep +base64-bytestringdep −base32-bytestringdep ~aesondep ~jose-jwtdep ~network-uriPVP ok
version bump matches the API change (PVP)
Dependencies added: base64-bytestring
Dependencies removed: base32-bytestring
Dependency ranges changed: aeson, jose-jwt, network-uri
API changes (from Hackage documentation)
Files
- examples/scotty/Main.hs +1/−1
- oidc-client.cabal +20/−3
- src/Web/OIDC/Client/CodeFlow.hs +9/−8
- src/Web/OIDC/Client/Internal.hs +14/−7
- test/Spec.hs +4/−1
- test/Spec/Client/Internal.hs +32/−0
examples/scotty/Main.hs view
@@ -7,7 +7,7 @@ import Crypto.Random.AESCtr (makeSystem) import Crypto.Random.API (CPRG, cprgGenBytes) import Data.ByteString (ByteString)-import Data.ByteString.Base32 (encode)+import Data.ByteString.Base64.URL (encode) import qualified Data.ByteString.Char8 as B import Data.IORef (IORef, newIORef, atomicModifyIORef', readIORef) import Data.Map (Map)
oidc-client.cabal view
@@ -1,5 +1,5 @@ name: oidc-client-version: 0.3.0.0+version: 0.3.0.1 synopsis: OpenID Connect 1.0 library for RP homepage: https://github.com/krdlab/haskell-oidc-client stability: experimental@@ -60,12 +60,24 @@ test-suite oidc-client-spec type: exitcode-stdio-1.0- hs-source-dirs: test+ hs-source-dirs: test, src default-language: Haskell2010 ghc-options: -Wall main-is: Spec.hs other-modules: Spec.Client+ , Spec.Client.Internal++ -- NOTE: The following modules are needed for using Web.OIDC.Client.Internal.+ , Web.OIDC.Client+ , Web.OIDC.Client.CodeFlow+ , Web.OIDC.Client.Discovery+ , Web.OIDC.Client.Discovery.Issuers+ , Web.OIDC.Client.Discovery.Provider+ , Web.OIDC.Client.Settings+ , Web.OIDC.Client.Internal+ , Web.OIDC.Client.Tokens+ , Web.OIDC.Client.Types build-depends: base , hspec@@ -75,6 +87,11 @@ , http-types , http-client , http-client-tls+ , aeson+ , jose-jwt+ , exceptions+ , time+ , network-uri executable scotty-example main-is: Main.hs@@ -95,7 +112,7 @@ , blaze-html , cprng-aes , crypto-random- , base32-bytestring+ , base64-bytestring , http-types , http-client , tls >=1.3.2
src/Web/OIDC/Client/CodeFlow.hs view
@@ -16,10 +16,11 @@ import Control.Monad (unless) import Control.Monad.Catch (MonadThrow, throwM, MonadCatch, catch)-import Data.Aeson (decode)+import Data.Aeson (eitherDecode) import qualified Data.ByteString.Char8 as B import Data.List (nub)-import Data.Text (Text, unpack)+import Data.Monoid ((<>))+import Data.Text (Text, pack, unpack) import Data.Text.Encoding (decodeUtf8) import Data.Time.Clock.POSIX (getPOSIXTime) import Jose.Jwt (Jwt)@@ -70,9 +71,9 @@ requestTokens :: OIDC -> Code -> Manager -> IO Tokens requestTokens oidc code manager = do json <- getTokensJson `catch` I.rethrow- case decode json of- Just ts -> validate oidc ts- Nothing -> error "failed to decode tokens json" -- TODO+ case eitherDecode json of+ Right ts -> validate oidc ts+ Left err -> error $ "failed to decode tokens json: " ++ err -- TODO: Exception where getTokensJson = do req <- parseUrl endpoint@@ -128,15 +129,15 @@ validateClaims issuer' clientId' now claims' = do iss' <- getIss claims' unless (iss' == issuer')- $ throwM $ ValidationException "issuer"+ $ throwM $ ValidationException $ "issuer from token \"" <> iss' <> "\" is different than expected issuer \"" <> issuer' <> "\"" aud' <- getAud claims' unless (clientId' `elem` aud')- $ throwM $ ValidationException "audience"+ $ throwM $ ValidationException $ "our client \"" <> clientId' <> "\" isn't contained in the token's audience " <> (pack . show) aud' exp' <- getExp claims' unless (now < exp')- $ throwM $ ValidationException "expire"+ $ throwM $ ValidationException "received token has expired" where getIss c = get Jwt.jwtIss c "'iss' claim was not found" getAud c = get Jwt.jwtAud c "'aud' claim was not found"
src/Web/OIDC/Client/Internal.hs view
@@ -6,13 +6,15 @@ -} module Web.OIDC.Client.Internal where +import Control.Applicative ((<|>)) import Control.Monad (mzero) import Control.Monad.Catch (MonadThrow, throwM, MonadCatch) import Data.Aeson (FromJSON, parseJSON, Value(..), (.:), (.:?)) import Data.Maybe (fromJust) import Data.Text (Text, unpack)+import Data.Text.Read (decimal) import Jose.Jwt (Jwt, JwtClaims(..))-import Network.HTTP.Client (HttpException, parseUrl, Request)+import Network.HTTP.Client (HttpException, parseRequest, Request) import Prelude hiding (exp) import Web.OIDC.Client.Tokens (IdTokenClaims(..)) import Web.OIDC.Client.Types (OpenIdException(InternalHttpException))@@ -28,13 +30,18 @@ instance FromJSON TokensResponse where parseJSON (Object o) = TokensResponse- <$> o .: "access_token"- <*> o .: "token_type"- <*> o .: "id_token"- <*> o .:? "expires_in"- <*> o .:? "refresh_token"+ <$> o .: "access_token"+ <*> o .: "token_type"+ <*> o .: "id_token"+ <*> (o .:? "expires_in" <|> (>>= textToInt) <$> (o .:? "expires_in"))+ <*> o .:? "refresh_token" parseJSON _ = mzero +textToInt :: Text -> Maybe Integer+textToInt t = case decimal t of+ Right (i, _) -> Just i+ Left _ -> Nothing+ rethrow :: (MonadCatch m) => HttpException -> m a rethrow = throwM . InternalHttpException @@ -48,4 +55,4 @@ } parseUrl :: MonadThrow m => Text -> m Request-parseUrl = Network.HTTP.Client.parseUrl . unpack+parseUrl = Network.HTTP.Client.parseRequest . unpack
test/Spec.hs view
@@ -1,5 +1,8 @@ import Test.Hspec (hspec) import qualified Spec.Client as Client+import qualified Spec.Client.Internal as Internal main :: IO ()-main = hspec Client.tests+main = hspec $ do+ Client.tests+ Internal.tests
+ test/Spec/Client/Internal.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}+module Spec.Client.Internal where++import Data.Aeson (decode)+import Jose.Jwt (Jwt(..))+import Test.Hspec (Spec, describe, it, shouldBe)+import qualified Web.OIDC.Client.Internal as I++tests :: Spec+tests =+ describe "Internal: Decode TokensResponse JSON data" $ do+ it "should be successful decoding a JSON data which has full fields" $ do+ let json = "{\"access_token\":\"access token\",\"token_type\":\"token type\",\"id_token\":\"dummy jwt\",\"expires_in\":123,\"refresh_token\":\"refresh token\"}"+ decode json `shouldBe` Just (I.TokensResponse "access token" "token type" (Jwt "dummy jwt") (Just 123) (Just "refresh token"))++ it "should be successful decoding a JSON data without 'refresh_token' field" $ do+ let json = "{\"access_token\":\"access token\",\"token_type\":\"token type\",\"id_token\":\"dummy jwt\",\"expires_in\":123}"+ decode json `shouldBe` Just (I.TokensResponse "access token" "token type" (Jwt "dummy jwt") (Just 123) Nothing)++ it "should be successful decoding a JSON data without 'expires_in' and 'refresh_token' fields" $ do+ let json = "{\"access_token\":\"access token\",\"token_type\":\"token type\",\"id_token\":\"dummy jwt\"}"+ decode json `shouldBe` Just (I.TokensResponse "access token" "token type" (Jwt "dummy jwt") Nothing Nothing)++ it "should be failure decoding a JSON data from the lack of a required field" $ do+ let json = "{\"access_token\":\"access token\",\"token_type\":\"token type\"}"+ decode json `shouldBe` (Nothing :: Maybe I.TokensResponse)++ -- NOTE: The 'expires_in' field is an integer, is not included a fractional part. But, I received the report https://github.com/krdlab/haskell-oidc-client/pull/15.+ -- see also: https://tools.ietf.org/html/rfc6749#appendix-A.14+ it "should be successful decoding a JSON data with 'expires_in' field which has a fractional part" $ do+ let json = "{\"access_token\":\"access token\",\"token_type\":\"token type\",\"id_token\":\"dummy jwt\",\"expires_in\":123.45}"+ decode json `shouldBe` Just (I.TokensResponse "access token" "token type" (Jwt "dummy jwt") (Just 123) Nothing)