{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- | -- -- OAuth2 plugin for http://login.salesforce.com -- -- * Authenticates against Salesforce -- * Uses Salesforce user id as credentials identifier -- * Returns given_name, family_name, email and avatar_url as extras -- module Yesod.Auth.OAuth2.Salesforce ( oauth2Salesforce , oauth2SalesforceScoped , oauth2SalesforceSandbox , oauth2SalesforceSandboxScoped , module Yesod.Auth.OAuth2 ) where #if __GLASGOW_HASKELL__ < 710 import Control.Applicative ((<$>), (<*>)) #endif import Control.Exception.Lifted import Control.Monad (mzero) import Data.Aeson import Data.Monoid ((<>)) import Data.Text (Text) import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Network.HTTP.Conduit (Manager) import Yesod.Auth import Yesod.Auth.OAuth2 import qualified Data.Text as T oauth2Salesforce :: YesodAuth m => Text -- ^ Client ID -> Text -- ^ Client Secret -> AuthPlugin m oauth2Salesforce = oauth2SalesforceScoped ["openid", "email", "api"] svcName :: Text svcName = "salesforce" oauth2SalesforceScoped :: YesodAuth m => [Text] -- ^ List of scopes to request -> Text -- ^ Client ID -> Text -- ^ Client Secret -> AuthPlugin m oauth2SalesforceScoped scopes clientId clientSecret = authOAuth2 svcName oauth fetchSalesforceUser where oauth = OAuth2 { oauthClientId = encodeUtf8 clientId , oauthClientSecret = encodeUtf8 clientSecret , oauthOAuthorizeEndpoint = encodeUtf8 $ "https://login.salesforce.com/services/oauth2/authorize?scope=" <> T.intercalate " " scopes , oauthAccessTokenEndpoint = "https://login.salesforce.com/services/oauth2/token" , oauthCallback = Nothing } fetchSalesforceUser :: Manager -> AccessToken -> IO (Creds m) fetchSalesforceUser manager token = do result <- authGetJSON manager token "https://login.salesforce.com/services/oauth2/userinfo" case result of Right user -> return $ toCreds svcName user token Left err -> throwIO $ InvalidProfileResponse svcName err svcNameSb :: Text svcNameSb = "salesforce-sandbox" oauth2SalesforceSandbox :: YesodAuth m => Text -- ^ Client ID -> Text -- ^ Client Secret -> AuthPlugin m oauth2SalesforceSandbox = oauth2SalesforceSandboxScoped ["openid", "email"] oauth2SalesforceSandboxScoped :: YesodAuth m => [Text] -- ^ List of scopes to request -> Text -- ^ Client ID -> Text -- ^ Client Secret -> AuthPlugin m oauth2SalesforceSandboxScoped scopes clientId clientSecret = authOAuth2 svcNameSb oauth fetchSalesforceSandboxUser where oauth = OAuth2 { oauthClientId = encodeUtf8 clientId , oauthClientSecret = encodeUtf8 clientSecret , oauthOAuthorizeEndpoint = encodeUtf8 $ "https://test.salesforce.com/services/oauth2/authorize?scope=" <> T.intercalate " " scopes , oauthAccessTokenEndpoint = "https://test.salesforce.com/services/oauth2/token" , oauthCallback = Nothing } fetchSalesforceSandboxUser :: Manager -> AccessToken -> IO (Creds m) fetchSalesforceSandboxUser manager token = do result <- authGetJSON manager token "https://test.salesforce.com/services/oauth2/userinfo" case result of Right user -> return $ toCreds svcNameSb user token Left err -> throwIO $ InvalidProfileResponse svcNameSb err data User = User { userId :: Text , userOrg :: Text , userNickname :: Text , userName :: Text , userGivenName :: Text , userFamilyName :: Text , userTimeZone :: Text , userEmail :: Text , userPicture :: Text , userPhone :: Maybe Text , userRestUrl :: Text } instance FromJSON User where parseJSON (Object o) = do userId <- o .: "user_id" userOrg <- o .: "organization_id" userNickname <- o .: "nickname" userName <- o .: "name" userGivenName <- o .: "given_name" userFamilyName <- o .: "family_name" userTimeZone <- o .: "zoneinfo" userEmail <- o .: "email" userPicture <- o .: "picture" userPhone <- o .:? "phone_number" urls <- o .: "urls" userRestUrl <- urls .: "rest" return User{..} parseJSON _ = mzero toCreds :: Text -> User -> AccessToken -> Creds m toCreds name user token = Creds { credsPlugin = name , credsIdent = userId user , credsExtra = [ ("email", userEmail user) , ("org", userOrg user) , ("nickname", userName user) , ("name", userName user) , ("given_name", userGivenName user) , ("family_name", userFamilyName user) , ("time_zone", userTimeZone user) , ("avatar_url", userPicture user) , ("rest_url", userRestUrl user) , ("access_token", decodeUtf8 $ accessToken token) ] ++ maybeExtra "refresh_token" (decodeUtf8 <$> refreshToken token) ++ maybeExtra "expires_in" ((T.pack . show) <$> expiresIn token) ++ maybeExtra "phone_number" (userPhone user) }