packages feed

openid-connect-0.1.1: example/Discover.hs

{-|

Copyright:

  This file is part of the package openid-connect.  It is subject to
  the license terms in the LICENSE file found in the top-level
  directory of this distribution and at:

    https://code.devalot.com/open/openid-connect

  No part of this package, including this file, may be copied,
  modified, propagated, or distributed except according to the terms
  contained in the LICENSE file.

License: BSD-2-Clause

-}
module Discover
  ( getProvider
  , getCredentials
  ) where

--------------------------------------------------------------------------------
-- Imports:
import Control.Lens ((^.))
import Crypto.JOSE (JWK, JWKSet(..), asPublicKey)
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Lazy.Char8 as Char8
import Data.List.NonEmpty (NonEmpty(..))
import Data.Text (Text)
import Network.HTTP.Client (Manager)
import OpenID.Connect.Authentication
import OpenID.Connect.Client.DynamicRegistration as R
import OpenID.Connect.Client.Provider
import OpenID.Connect.Provider.Key
import Options
import Util

--------------------------------------------------------------------------------
-- | Fetch the provider's discovery document and signing keys.
getProvider :: Options -> Manager -> IO Provider
getProvider opts mgr =
  discoveryAndKeys (https mgr) (optionsProviderUri opts) >>= \case
    Left err     -> fail (show err)
    Right (x, _) -> pure (x { providerDiscovery =
                              updateDisco (providerDiscovery x) })

  where
    updateDisco :: Discovery -> Discovery
    updateDisco d =
      case optionsForceAuthMode opts of
        ForceSecretPost ->
          d {tokenEndpointAuthMethodsSupported =
             Just (ClientSecretPost :| [])}
        ForceSecretJWT ->
          d {tokenEndpointAuthMethodsSupported =
             Just (ClientSecretJwt :| [])}
        ForcePrivateJWT ->
          d {tokenEndpointAuthMethodsSupported =
             Just (PrivateKeyJwt :| [])}
        NoForcedAuth ->
          d

--------------------------------------------------------------------------------
-- | Construct client credentials.
getCredentials
  :: Options
  -> Manager
  -> Provider
  -> IO Credentials
getCredentials opts mgr provider =
  case optionsClientDetails opts of
    Direct cid csec -> pure (build csec cid)
    Register email -> register email

  where
    register :: Text -> IO Credentials
    register email = do
      key <- newSigningJWK
      let metadata = clientMetadata (regReq email key) BasicRegistration
      putStrLn "Performing dynamic client registration:"
      Char8.putStrLn (Aeson.encode metadata)

      registerClient (https mgr) (providerDiscovery provider) metadata >>= \case
        Left e -> fail (show e)
        Right x -> do
          let res = clientSecretsFromResponse x
          putStrLn "Registration successful:" >> print res
          pure (build (R.clientId res) (toClientSecret key (R.clientSecret res)))

    regReq :: Text -> JWK -> Registration
    regReq email key =
      let reg = defaultRegistration (optionsClientUri opts)
      in  reg { R.contacts = Just (email :| [])
              , R.tokenEndpointAuthMethod = defaultAuthMethod
              , R.jwks = JWKSet . pure <$> (key ^. asPublicKey)
              }

    defaultAuthMethod :: ClientAuthentication
    defaultAuthMethod = case optionsForceAuthMode opts of
      ForceSecretPost -> ClientSecretPost
      ForceSecretJWT  -> ClientSecretJwt
      ForcePrivateJWT -> PrivateKeyJwt
      NoForcedAuth    -> ClientSecretBasic

    toClientSecret :: JWK -> Maybe Text -> ClientSecret
    toClientSecret key = \case
      Nothing ->
        case optionsForceAuthMode opts of
          ForceSecretPost -> AssignedSecretText "should fail"
          ForceSecretJWT  -> AssignedAssertionText "should fail"
          ForcePrivateJWT -> AssertionPrivateKey key
          NoForcedAuth    -> AssignedSecretText "should fail"
      Just sec ->
        case optionsForceAuthMode opts of
          ForceSecretPost -> AssignedSecretText sec
          ForceSecretJWT  -> AssignedAssertionText sec
          ForcePrivateJWT -> AssertionPrivateKey key
          NoForcedAuth    -> AssignedSecretText sec

    build :: Text -> ClientSecret -> Credentials
    build cid csec =
      Credentials
        { assignedClientId  = cid
        , clientSecret      = csec
        , clientRedirectUri = optionsClientUri opts
        }