packages feed

hoauth2 1.10.1 → 1.10.2

raw patch · 19 files changed

+201/−29 lines, 19 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq Network.OAuth.OAuth2.Internal.AccessToken
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq Network.OAuth.OAuth2.Internal.IdToken
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq Network.OAuth.OAuth2.Internal.OAuth2Token
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq Network.OAuth.OAuth2.Internal.RefreshToken

Files

example/App.hs view
@@ -9,7 +9,7 @@  import           Control.Monad import           Control.Monad.Error.Class-import           Control.Monad.IO.Class        (liftIO)+import           Control.Monad.IO.Class        (MonadIO, liftIO) import           Data.Bifunctor import           Data.Maybe import           Data.Text.Lazy                (Text)@@ -52,6 +52,7 @@     get "/" $ indexH cache     get "/oauth2/callback" $ callbackH cache     get "/logout" $ logoutH cache+    get "/refresh" $ refreshH cache  debug :: Bool debug = True@@ -69,13 +70,45 @@ globalErrorHandler :: Text -> ActionM () globalErrorHandler t = status status401 >> html t -logoutH :: CacheStore -> ActionM ()-logoutH c = do+readIdpParam :: ActionM (Either Text IDPApp)+readIdpParam = do   pas <- params   let idpP = paramValue "idp" pas   when (null idpP) redirectToHomeM-  let eitherIdpApp = parseIDP (head idpP)+  return $ parseIDP (head idpP)++refreshH :: CacheStore -> ActionM ()+refreshH c = do+  eitherIdpApp <- readIdpParam   case eitherIdpApp of+    Right (IDPApp idp) -> do+      maybeIdpData <- lookIdp c idp+      when (isNothing maybeIdpData) (errorM "refreshH: cannot find idp data from cache")+      let idpData = fromJust maybeIdpData+      re <- liftIO $ doRefreshToken idp idpData+      case re of+        Right newToken -> liftIO (print newToken) >> redirectToHomeM -- TODO: update access token in the store+        Left e         -> errorM (TL.pack e)+    Left e       -> errorM ("logout: unknown IDP " `TL.append` e)++doRefreshToken :: HasTokenRefreshReq a =>+                  a -> IDPData -> IO (Either String OAuth2Token)+doRefreshToken idp idpData = do+  mgr <- newManager tlsManagerSettings+  case oauth2Token idpData of+    Nothing -> return $ Left "no token found for idp"+    Just at ->+      case refreshToken at of+        Nothing -> return $ Left "no refresh token presents"+        Just rt -> do+          re <- tokenRefreshReq idp mgr rt+          return (first show re)++logoutH :: CacheStore -> ActionM ()+logoutH c = do+  eitherIdpApp <- readIdpParam+  -- let eitherIdpApp = parseIDP (head idpP)+  case eitherIdpApp of     Right (IDPApp idp) -> liftIO (removeKey c (idpLabel idp)) >> redirectToHomeM     Left e       -> errorM ("logout: unknown IDP " `TL.append` e) @@ -106,28 +139,43 @@   when (isNothing maybeIdpData) (errorM "fetchTokenAndUser: cannot find idp data from cache")    let idpData = fromJust maybeIdpData-  result <- liftIO $ tryFetchUser idp code-+  result <- liftIO $ fetchTokenAndUser' c code idp idpData   case result of-    Right luser -> updateIdp c idpData luser >> redirectToHomeM-    Left err    -> errorM ("fetchTokenAndUser: " `TL.append` err)--  where lookIdp c1 idp1 = liftIO $ lookupKey c1 (idpLabel idp1)-        updateIdp c1 oldIdpData luser = liftIO $ insertIDPData c1 (oldIdpData {loginUser = Just luser })+    Right _  -> redirectToHomeM+    Left err -> errorM err --- TODO: may use Exception monad to capture error in this IO monad----tryFetchUser :: (HasTokenReq a, HasUserReq a, HasLabel a)-  => a-  -> TL.Text           -- ^ code-  -> IO (Either Text LoginUser)-tryFetchUser idp code = do+fetchTokenAndUser' :: (HasTokenReq a, HasUserReq a) =>+                      CacheStore -> Text -> a -> IDPData -> IO (Either Text ())+fetchTokenAndUser' c code idp idpData = do   mgr <- newManager tlsManagerSettings   token <- tokenReq idp mgr (ExchangeToken $ TL.toStrict code)   when debug (print token)-  case token of-    Right at -> fetchUser idp mgr (accessToken at)++  result <- case token of+    Right at -> tryFetchUser mgr at idp     Left e   -> return (Left $ TL.pack $ "tryFetchUser: cannot fetch asses token. error detail: " ++ show e)++  case result of+    Right (luser, at) -> updateIdp c idpData luser at >> return (Right ())+    Left err    -> return $ Left ("fetchTokenAndUser: " `TL.append` err)++  where updateIdp c1 oldIdpData luser token =+          insertIDPData c1 (oldIdpData {loginUser = Just luser, oauth2Token = Just token })++lookIdp :: (MonadIO m, HasLabel a) =>+           CacheStore -> a -> m (Maybe IDPData)+lookIdp c1 idp1 = liftIO $ lookupKey c1 (idpLabel idp1)++-- TODO: may use Exception monad to capture error in this IO monad+--+tryFetchUser :: HasUserReq a =>+                Manager+             -> OAuth2Token -> a -> IO (Either Text (LoginUser, OAuth2Token))+tryFetchUser mgr at idp = do+  re <- fetchUser idp mgr (accessToken at)+  return $ case re of+    Right user' -> Right (user', at)+    Left e      -> Left e  -- * Fetch UserInfo --
example/IDP.hs view
@@ -14,6 +14,7 @@ import qualified IDP.Okta            as IOkta import qualified IDP.StackExchange   as IStackExchange import qualified IDP.Weibo           as IWeibo+import qualified IDP.ZOHO            as IZOHO import           Session import           Types @@ -30,6 +31,7 @@        , IDPApp IOkta.Okta        , IDPApp IStackExchange.StackExchange        , IDPApp IWeibo.Weibo+       , IDPApp IZOHO.ZOHO        ]  initIdps :: CacheStore -> IO ()@@ -42,4 +44,4 @@ parseIDP s = maybe (Left s) Right (Map.lookup s idpsMap)  mkIDPData :: IDPApp -> IDPData-mkIDPData (IDPApp idp) = IDPData (authUri idp) Nothing (idpLabel idp)+mkIDPData (IDPApp idp) = IDPData (authUri idp) Nothing Nothing (idpLabel idp)
example/IDP/AzureAD.hs view
@@ -23,6 +23,9 @@  instance HasLabel AzureAD +instance HasTokenRefreshReq AzureAD where+  tokenRefreshReq _ mgr = refreshAccessToken mgr azureADKey+ instance HasTokenReq AzureAD where   tokenReq _ mgr = fetchAccessToken mgr azureADKey 
example/IDP/Douban.hs view
@@ -23,6 +23,9 @@  instance HasLabel Douban +instance HasTokenRefreshReq Douban where+  tokenRefreshReq _ mgr = refreshAccessToken mgr doubanKey+ instance HasTokenReq Douban where   tokenReq _ mgr = fetchAccessToken2 mgr doubanKey 
example/IDP/Dropbox.hs view
@@ -27,6 +27,9 @@ instance HasTokenReq Dropbox where   tokenReq _ mgr = fetchAccessToken mgr dropboxKey +instance HasTokenRefreshReq Dropbox where+  tokenRefreshReq _ mgr = refreshAccessToken mgr dropboxKey+ instance HasUserReq Dropbox where   userReq _ mgr at = do     re <- authPostBS3 mgr at userInfoUri
example/IDP/Facebook.hs view
@@ -26,6 +26,9 @@ instance HasTokenReq Facebook where   tokenReq _ mgr = fetchAccessToken2 mgr facebookKey +instance HasTokenRefreshReq Facebook where+  tokenRefreshReq _ mgr = refreshAccessToken mgr facebookKey+ instance HasUserReq Facebook where   userReq _ mgr at = do     re <- authGetJSON mgr at userInfoUri
example/IDP/Fitbit.hs view
@@ -28,6 +28,9 @@ instance HasTokenReq Fitbit where   tokenReq _ mgr = fetchAccessToken mgr fitbitKey +instance HasTokenRefreshReq Fitbit where+  tokenRefreshReq _ mgr = refreshAccessToken mgr fitbitKey+ instance HasUserReq Fitbit where   userReq _ mgr at = do     re <- authGetJSON mgr at userInfoUri
example/IDP/Github.hs view
@@ -26,6 +26,9 @@ instance HasTokenReq Github where   tokenReq _ mgr = fetchAccessToken mgr githubKey +instance HasTokenRefreshReq Github where+  tokenRefreshReq _ mgr = refreshAccessToken mgr githubKey+ instance HasUserReq Github where   userReq _ mgr at = do     re <- authGetJSON mgr at userInfoUri
example/IDP/Google.hs view
@@ -26,6 +26,9 @@ instance HasTokenReq Google where   tokenReq _ mgr = fetchAccessToken mgr googleKey +instance HasTokenRefreshReq Google where+  tokenRefreshReq _ mgr = refreshAccessToken mgr googleKey+ instance HasUserReq Google where   userReq _ mgr at = do     re <- authGetJSON mgr at userInfoUri
example/IDP/Okta.hs view
@@ -26,6 +26,9 @@ instance HasTokenReq Okta where   tokenReq _ mgr = fetchAccessToken mgr oktaKey +instance HasTokenRefreshReq Okta where+  tokenRefreshReq _ mgr = refreshAccessToken mgr oktaKey+ instance HasUserReq Okta where   userReq _ mgr at = do     re <- authGetJSON mgr at userInfoUri@@ -33,8 +36,8 @@  instance HasAuthUri Okta where   authUri _ = createCodeUri oktaKey [ ("state", "Okta.test-state-123")-                                        , ("scope", "openid profile")-                                        ]+                                    , ("scope", "openid profile offline_access")+                                    ]  data OktaUser = OktaUser { name              :: Text                          , preferredUsername :: Text
example/IDP/StackExchange.hs view
@@ -34,6 +34,9 @@ instance HasTokenReq StackExchange where   tokenReq _ mgr = fetchAccessToken2 mgr stackexchangeKey +instance HasTokenRefreshReq StackExchange where+  tokenRefreshReq _ mgr = refreshAccessToken mgr stackexchangeKey+ instance HasUserReq StackExchange where   userReq _ mgr token = do     re <- authGetBS2 mgr token
example/IDP/Weibo.hs view
@@ -25,6 +25,9 @@  instance HasLabel Weibo +instance HasTokenRefreshReq Weibo where+  tokenRefreshReq _ mgr = refreshAccessToken mgr weiboKey+ instance HasTokenReq Weibo where   tokenReq _ mgr = fetchAccessToken mgr weiboKey 
+ example/IDP/ZOHO.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}++module IDP.ZOHO where+import           Data.Aeson+import           Data.Bifunctor+import           Data.Hashable+import           Data.Text.Lazy       (Text)+import           GHC.Generics+import           Keys+import           Network.OAuth.OAuth2+import           Types+import           URI.ByteString+import           URI.ByteString.QQ+import           Utils++data ZOHO = ZOHO deriving (Show, Generic)++instance Hashable ZOHO++instance IDP ZOHO++instance HasLabel ZOHO++instance HasTokenReq ZOHO where+  tokenReq _ mgr = fetchAccessToken2 mgr zohoKey++instance HasTokenRefreshReq ZOHO where+  tokenRefreshReq _ mgr = refreshAccessToken2 mgr zohoKey++instance HasUserReq ZOHO where+  userReq _ mgr at = do+    re <- authGetJSON mgr at userInfoUri+    return (second toLoginUser re)++instance HasAuthUri ZOHO where+  authUri _ = createCodeUri zohoKey [ ("state", "ZOHO.test-state-123")+                                    , ("scope", "ZohoCRM.users.READ")+                                    , ("access_type", "offline")+                                    , ("prompt", "consent")+                                    ]++data ZOHOUser = ZOHOUser { email    :: Text+                         , fullName :: Text+                         } deriving (Show, Generic)++newtype ZOHOUserResp = ZOHOUserResp { users :: [ZOHOUser] }+  deriving (Show, Generic)++instance FromJSON ZOHOUserResp where+    parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = camelTo2 '_' }++instance FromJSON ZOHOUser where+    parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = camelTo2 '_' }++userInfoUri :: URI+userInfoUri = [uri|https://www.zohoapis.com/crm/v2/users|]+  -- `oauth/user/info` url does not work and find answer from+  -- https://help.zoho.com/portal/community/topic/oauth2-api-better-document-oauth-user-info++toLoginUser :: ZOHOUserResp -> LoginUser+toLoginUser resp =+  let us = users resp+  in+    case us of+      []    -> LoginUser { loginUserName = "no user found" }+      (a:_) -> LoginUser { loginUserName = fullName a }+
example/Keys.hs.sample view
@@ -92,3 +92,11 @@                     , oauthOAuthorizeEndpoint = [uri|https://login.windows.net/common/oauth2/authorize|]                     , oauthAccessTokenEndpoint = [uri|https://login.windows.net/common/oauth2/token|]                     }++zohoKey :: OAuth2+zohoKey = OAuth2 { oauthClientId = "xxx"+                    , oauthClientSecret = "xxx"+                    , oauthCallback = Just [uri|http://localhost:9988/oauth2/callback|]+                    , oauthOAuthorizeEndpoint = [uri|https://accounts.zoho.com/oauth/v2/auth|]+                    , oauthAccessTokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]+                    }
example/README.md view
@@ -13,3 +13,7 @@  * WIP: Linkedin   - <https://developer.linkedin.com>++* NOTES+  - classes in Types.hs takes a (`IDP`) as first parameter but it is actually not used. bad pattern. how to fix it??+  - refactor: `App.hs` is messy!
example/Types.hs view
@@ -42,13 +42,21 @@ class (IDP a) => HasTokenReq a where   tokenReq :: a -> Manager -> ExchangeToken -> IO (OAuth2Result TR.Errors OAuth2Token) +class (IDP a) => HasTokenRefreshReq a where+  tokenRefreshReq :: a -> Manager -> RefreshToken -> IO (OAuth2Result TR.Errors OAuth2Token)+ class (IDP a) => HasUserReq a where   userReq :: a -> Manager -> AccessToken -> IO (Either BSL.ByteString LoginUser)  -- Heterogenous collections -- https://wiki.haskell.org/Heterogenous_collections ---data IDPApp = forall a. (IDP a, HasTokenReq a, HasUserReq a, HasLabel a, HasAuthUri a) => IDPApp a+data IDPApp = forall a. (IDP a,+                         HasTokenRefreshReq a,+                         HasTokenReq a,+                         HasUserReq a,+                         HasLabel a,+                         HasAuthUri a) => IDPApp a  -- dummy oauth2 request error --@@ -66,6 +74,7 @@ data IDPData =   IDPData { codeFlowUri     :: Text           , loginUser       :: Maybe LoginUser+          , oauth2Token     :: Maybe OAuth2Token           , idpDisplayLabel :: IDPLabel           } 
example/templates/index.mustache view
@@ -17,6 +17,7 @@             <div class="result">Hello, {{name}}</div>             {{/user}}             <a href="/logout?idp={{name}}">Logout</a>+            <a href="/refresh?idp={{name}}">Refresh</a>             {{/isLogin}}          </section>
hoauth2.cabal view
@@ -1,7 +1,7 @@ Cabal-version: 2.4 Name:                hoauth2 -- http://wiki.haskell.org/Package_versioning_policy-Version:             1.10.1+Version:             1.10.2  Synopsis:            Haskell OAuth2 authentication client @@ -110,6 +110,7 @@                        IDP.StackExchange                        IDP.Weibo                        IDP.Linkedin+                       IDP.ZOHO                        Keys                        Session                        Types
src/Network/OAuth/OAuth2/Internal.hs view
@@ -43,9 +43,9 @@     , oauthCallback            :: Maybe URI     } deriving (Show, Eq) -newtype AccessToken = AccessToken { atoken :: Text } deriving (Binary, Show, FromJSON, ToJSON)-newtype RefreshToken = RefreshToken { rtoken :: Text } deriving (Binary, Show, FromJSON, ToJSON)-newtype IdToken = IdToken { idtoken :: Text } deriving (Binary, Show, FromJSON, ToJSON)+newtype AccessToken = AccessToken { atoken :: Text } deriving (Binary, Eq, Show, FromJSON, ToJSON)+newtype RefreshToken = RefreshToken { rtoken :: Text } deriving (Binary, Eq, Show, FromJSON, ToJSON)+newtype IdToken = IdToken { idtoken :: Text } deriving (Binary, Eq, Show, FromJSON, ToJSON) newtype ExchangeToken = ExchangeToken { extoken :: Text } deriving (Show, FromJSON, ToJSON)  @@ -59,7 +59,7 @@     , expiresIn    :: Maybe Int     , tokenType    :: Maybe Text     , idToken      :: Maybe IdToken-    } deriving (Show, Generic)+    } deriving (Eq, Show, Generic)  instance Binary OAuth2Token