7
7
8
8
import Control.Applicative
9
9
import Control.Concurrent.STM
10
+ import Control.Exception.Safe (Exception , throwM )
10
11
import Data.Either.Combinators
11
12
import Data.Function ((&) )
12
13
import Data.Map (Map )
@@ -19,18 +20,19 @@ import Kubernetes.Client.KubeConfig
19
20
import Kubernetes.OpenAPI.Core
20
21
import Network.HTTP.Client
21
22
import Network.HTTP.Client.TLS
22
- import Network.OAuth.OAuth2 as OAuth hiding ( error )
23
+ import Network.OAuth.OAuth2 as OAuth
23
24
import Network.TLS as TLS
24
25
import URI.ByteString
25
26
import Web.JWT as JWT
26
27
import Web.OIDC.Client.Discovery as OIDC
27
28
28
- import qualified Data.ByteString as BS
29
- import qualified Data.ByteString.Base64 as B64
30
- import qualified Data.Map as Map
31
- import qualified Data.Text as Text
32
- import qualified Data.Text.Encoding as Text
33
- import qualified Lens.Micro as L
29
+ import qualified Data.ByteString as BS
30
+ import qualified Data.ByteString.Base64 as B64
31
+ import qualified Data.Map as Map
32
+ import qualified Data.Text as Text
33
+ import qualified Data.Text.Encoding as Text
34
+ import qualified Lens.Micro as L
35
+ import qualified Network.OAuth.OAuth2.TokenRequest as OAuth2TokenRequest
34
36
35
37
data OIDCAuth = OIDCAuth { issuerURL :: Text
36
38
, clientID :: Text
@@ -50,6 +52,16 @@ instance AuthMethod OIDCAuth where
50
52
$ setHeader req [(" Authorization" , " Bearer " <> (Text. encodeUtf8 token))]
51
53
& L. set rAuthTypesL []
52
54
55
+ data OIDCGetTokenException = OIDCOAuthException (OAuth2Error OAuth2TokenRequest. Errors )
56
+ | OIDCURIException URIParseError
57
+ | OIDCGetTokenException String
58
+ deriving Show
59
+ instance Exception OIDCGetTokenException
60
+
61
+ data OIDCAuthParsingException = OIDCAuthParsingException String
62
+ deriving Show
63
+ instance Exception OIDCAuthParsingException
64
+
53
65
-- TODO: Consider a token expired few seconds before actual expiry to account for time skew
54
66
getToken :: OIDCAuth -> IO Text
55
67
getToken o@ (OIDCAuth {.. }) = do
@@ -64,26 +76,27 @@ getToken o@(OIDCAuth{..}) = do
64
76
isValidToken = fromMaybe False (fmap (now < ) maybeExp)
65
77
if not isValidToken
66
78
then fetchToken mgr o
67
- else return $ fromMaybe ( error " impossible" ) idToken
79
+ else maybe (throwM $ OIDCGetTokenException " impossible" ) pure idToken
68
80
69
81
fetchToken :: Manager -> OIDCAuth -> IO Text
70
82
fetchToken mgr o@ (OIDCAuth {.. }) = do
71
83
maybeToken <- readTVarIO refreshTokenTVar
72
84
case maybeToken of
73
- Nothing -> error " cannot refresh id-token without a refresh token"
85
+ Nothing -> throwM $ OIDCGetTokenException " cannot refresh id-token without a refresh token"
74
86
Just token -> do
75
87
tokenEndpoint <- fetchTokenEndpoint mgr o
76
- tokenURI <- exceptEither $ parseURI strictURIParserOptions (Text. encodeUtf8 tokenEndpoint)
88
+ tokenURI <- parseURI strictURIParserOptions (Text. encodeUtf8 tokenEndpoint)
89
+ & either (throwM . OIDCURIException ) pure
77
90
let oauth = OAuth2 { oauthClientId = clientID
78
91
, oauthClientSecret = clientSecret
79
92
, oauthAccessTokenEndpoint = tokenURI
80
93
, oauthOAuthorizeEndpoint = tokenURI
81
94
, oauthCallback = Nothing
82
95
}
83
96
oauthToken <- refreshAccessToken mgr oauth (RefreshToken token)
84
- >>= exceptEither
97
+ >>= either (throwM . OIDCOAuthException ) pure
85
98
case OAuth. idToken oauthToken of
86
- Nothing -> error " token response did not contain an id_token, either the scope \" openid\" wasn't requested upon login, or the provider doesn't support id_tokens as part of the refresh response."
99
+ Nothing -> throwM $ OIDCGetTokenException " token response did not contain an id_token, either the scope \" openid\" wasn't requested upon login, or the provider doesn't support id_tokens as part of the refresh response."
87
100
Just (IdToken t) -> do
88
101
_ <- atomically $ writeTVar idTokenTVar (Just t)
89
102
return t
@@ -94,10 +107,6 @@ fetchTokenEndpoint mgr OIDCAuth{..} = do
94
107
& (fmap configuration)
95
108
& (fmap tokenEndpoint)
96
109
97
- exceptEither :: Show b => Either b a -> IO a
98
- exceptEither (Right a) = pure a
99
- exceptEither (Left t) = error (show t)
100
-
101
110
{-
102
111
Detects if auth-provier name is oidc, if it is configures the 'KubernetesClientConfig' with OIDCAuth 'AuthMethod'.
103
112
Does not use cache, consider using 'cachedOIDCAuth'.
@@ -106,7 +115,7 @@ oidcAuth :: DetectAuth
106
115
oidcAuth AuthInfo {authProvider = Just (AuthProviderConfig " oidc" (Just cfg))} (tls, kubecfg)
107
116
= Just
108
117
$ parseOIDCAuthInfo cfg
109
- >>= either error (\ oidc -> pure (tls, addAuthMethod kubecfg oidc))
118
+ >>= either (throwM . OIDCAuthParsingException ) (\ oidc -> pure (tls, addAuthMethod kubecfg oidc))
110
119
oidcAuth _ _ = Nothing
111
120
112
121
-- TODO: Consider doing this whole function atomically, as two threads may miss the cache simultaneously
@@ -118,10 +127,11 @@ cachedOIDCAuth :: OIDCCache -> DetectAuth
118
127
cachedOIDCAuth cache AuthInfo {authProvider = Just (AuthProviderConfig " oidc" (Just cfg))} (tls, kubecfg) = Just $ do
119
128
m <- readTVarIO cache
120
129
o <- case findInCache m cfg of
121
- Left e -> error e
130
+ Left e -> throwM $ OIDCAuthParsingException e
122
131
Right (Just o) -> return o
123
132
Right Nothing -> do
124
- o@ (OIDCAuth {.. }) <- either error pure =<< parseOIDCAuthInfo cfg
133
+ o@ (OIDCAuth {.. }) <- parseOIDCAuthInfo cfg
134
+ >>= either (throwM . OIDCAuthParsingException ) pure
125
135
let newCache = Map. insert (issuerURL, clientID) o m
126
136
_ <- atomically $ swapTVar cache newCache
127
137
return o
0 commit comments