module OpenID.Connect.Client.Provider
(
ProviderDiscoveryURI
, discovery
, keysFromDiscovery
, Provider(..)
, discoveryAndKeys
, DiscoveryError(..)
, Discovery(..)
, URI(..)
, uriToText
) where
import Control.Exception (Exception)
import Control.Monad.Except (ExceptT(..), runExceptT)
import Crypto.JOSE.JWK (JWKSet)
import Data.Bifunctor (first)
import Data.Functor ((<&>))
import Data.Text (Text)
import Data.Time.Clock (UTCTime)
import qualified Network.URI as Network
import OpenID.Connect.Client.HTTP
import OpenID.Connect.Discovery
import OpenID.Connect.JSON
data DiscoveryError
= DiscoveryFailedError ErrorResponse
| InvalidUriError Text
deriving (Int -> DiscoveryError -> ShowS
[DiscoveryError] -> ShowS
DiscoveryError -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DiscoveryError] -> ShowS
$cshowList :: [DiscoveryError] -> ShowS
show :: DiscoveryError -> String
$cshow :: DiscoveryError -> String
showsPrec :: Int -> DiscoveryError -> ShowS
$cshowsPrec :: Int -> DiscoveryError -> ShowS
Show, Show DiscoveryError
Typeable DiscoveryError
SomeException -> Maybe DiscoveryError
DiscoveryError -> String
DiscoveryError -> SomeException
forall e.
Typeable e
-> Show e
-> (e -> SomeException)
-> (SomeException -> Maybe e)
-> (e -> String)
-> Exception e
displayException :: DiscoveryError -> String
$cdisplayException :: DiscoveryError -> String
fromException :: SomeException -> Maybe DiscoveryError
$cfromException :: SomeException -> Maybe DiscoveryError
toException :: DiscoveryError -> SomeException
$ctoException :: DiscoveryError -> SomeException
Exception)
data Provider = Provider
{ Provider -> Discovery
providerDiscovery :: Discovery
, Provider -> JWKSet
providerKeys :: JWKSet
}
discovery
:: Applicative f
=> HTTPS f
-> ProviderDiscoveryURI
-> f (Either DiscoveryError (Discovery, Maybe UTCTime))
discovery :: forall (f :: * -> *).
Applicative f =>
HTTPS f
-> ProviderDiscoveryURI
-> f (Either DiscoveryError (Discovery, Maybe UTCTime))
discovery HTTPS f
https ProviderDiscoveryURI
uri =
case Either Text ProviderDiscoveryURI -> Maybe Request
requestFromURI (forall a b. b -> Either a b
Right (ProviderDiscoveryURI -> ProviderDiscoveryURI
setPath ProviderDiscoveryURI
uri)) of
Maybe Request
Nothing -> forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a b. a -> Either a b
Left (Text -> DiscoveryError
InvalidUriError (ProviderDiscoveryURI -> Text
uriToText ProviderDiscoveryURI
uri)))
Just Request
req -> HTTPS f
https Request
req forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> forall a.
FromJSON a =>
Response ByteString -> Either ErrorResponse (a, Maybe UTCTime)
parseResponse forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ErrorResponse -> DiscoveryError
DiscoveryFailedError
where
setPath :: Network.URI -> Network.URI
setPath :: ProviderDiscoveryURI -> ProviderDiscoveryURI
setPath u :: ProviderDiscoveryURI
u@Network.URI{String
uriPath :: ProviderDiscoveryURI -> String
uriPath :: String
uriPath} =
if forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
uriPath Bool -> Bool -> Bool
|| String
uriPath forall a. Eq a => a -> a -> Bool
== String
"/"
then ProviderDiscoveryURI
u {uriPath :: String
Network.uriPath = String
"/.well-known/openid-configuration"}
else ProviderDiscoveryURI
u
keysFromDiscovery
:: Applicative f
=> HTTPS f
-> Discovery
-> f (Either DiscoveryError (JWKSet, Maybe UTCTime))
keysFromDiscovery :: forall (f :: * -> *).
Applicative f =>
HTTPS f
-> Discovery -> f (Either DiscoveryError (JWKSet, Maybe UTCTime))
keysFromDiscovery HTTPS f
https Discovery{URI
jwksUri :: Discovery -> URI
jwksUri :: URI
jwksUri} =
case Either Text ProviderDiscoveryURI -> Maybe Request
requestFromURI (forall a b. b -> Either a b
Right (URI -> ProviderDiscoveryURI
getURI URI
jwksUri)) of
Maybe Request
Nothing -> forall (f :: * -> *) a. Applicative f => a -> f a
pure (forall a b. a -> Either a b
Left (Text -> DiscoveryError
InvalidUriError (ProviderDiscoveryURI -> Text
uriToText (URI -> ProviderDiscoveryURI
getURI URI
jwksUri))))
Just Request
req -> HTTPS f
https Request
req forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> forall a.
FromJSON a =>
Response ByteString -> Either ErrorResponse (a, Maybe UTCTime)
parseResponse forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ErrorResponse -> DiscoveryError
DiscoveryFailedError
discoveryAndKeys
:: Monad m
=> HTTPS m
-> ProviderDiscoveryURI
-> m (Either DiscoveryError (Provider, Maybe UTCTime))
discoveryAndKeys :: forall (m :: * -> *).
Monad m =>
HTTPS m
-> ProviderDiscoveryURI
-> m (Either DiscoveryError (Provider, Maybe UTCTime))
discoveryAndKeys HTTPS m
https ProviderDiscoveryURI
uri = forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT forall a b. (a -> b) -> a -> b
$ do
(Discovery
d, Maybe UTCTime
t1) <- forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT (forall (f :: * -> *).
Applicative f =>
HTTPS f
-> ProviderDiscoveryURI
-> f (Either DiscoveryError (Discovery, Maybe UTCTime))
discovery HTTPS m
https ProviderDiscoveryURI
uri )
(JWKSet
k, Maybe UTCTime
t2) <- forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT (forall (f :: * -> *).
Applicative f =>
HTTPS f
-> Discovery -> f (Either DiscoveryError (JWKSet, Maybe UTCTime))
keysFromDiscovery HTTPS m
https Discovery
d)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Discovery -> JWKSet -> Provider
Provider Discovery
d JWKSet
k, forall a. Ord a => a -> a -> a
min forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe UTCTime
t1 forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Maybe UTCTime
t2)