This commit is contained in:
pat brisbin 2015-04-13 16:36:29 +00:00
commit 1aa8774e70
4 changed files with 110 additions and 104 deletions

View File

@ -10,8 +10,10 @@
-- * See Yesod.Auth.OAuth2.GitHub for example usage. -- * See Yesod.Auth.OAuth2.GitHub for example usage.
-- --
module Yesod.Auth.OAuth2 module Yesod.Auth.OAuth2
( authOAuth2 ( OAuth2Plugin(..)
, oauth2Url , oauth2Url
, oauth2Plugin
, authOAuth2
, YesodOAuth2Exception(..) , YesodOAuth2Exception(..)
, module Network.OAuth.OAuth2 , module Network.OAuth.OAuth2
) where ) where
@ -43,9 +45,45 @@ data YesodOAuth2Exception = InvalidProfileResponse Text BL.ByteString
instance Exception YesodOAuth2Exception instance Exception YesodOAuth2Exception
data OAuth2Plugin a = OAuth2Plugin
{ oapName :: Text
, oapAuthEndpoint :: Text
, oapTokenEndpoint :: Text
, oapFetchProfile :: Manager -> AccessToken -> IO (Either BL.ByteString a)
, oapToCredsIdent :: a -> Text
, oapToCredsExtra :: a -> [(Text, Text)]
}
oauth2Url :: Text -> AuthRoute oauth2Url :: Text -> AuthRoute
oauth2Url name = PluginR name ["forward"] oauth2Url name = PluginR name ["forward"]
oauth2Plugin :: YesodAuth m
=> OAuth2Plugin a
-> Text -- ^ Client ID
-> Text -- ^ Client Secret
-> AuthPlugin m
oauth2Plugin oap clientId clientSecret =
authOAuth2 (oapName oap) oapOauth $ \manager token -> do
result <- oapFetchProfile oap manager token
case result of
Left err -> throwIO $ InvalidProfileResponse (oapName oap) err
Right user -> return Creds
{ credsIdent = oapToCredsIdent oap user
, credsPlugin = oapName oap
, credsExtra = oapToCredsExtra oap user
}
where
oapOauth :: OAuth2
oapOauth = OAuth2
{ oauthClientId = encodeUtf8 clientId
, oauthClientSecret = encodeUtf8 clientSecret
, oauthOAuthorizeEndpoint = encodeUtf8 $ oapAuthEndpoint oap
, oauthAccessTokenEndpoint = encodeUtf8 $ oapTokenEndpoint oap
, oauthCallback = Nothing
}
authOAuth2 :: YesodAuth m authOAuth2 :: YesodAuth m
=> Text -- ^ Service name => Text -- ^ Service name
-> OAuth2 -- ^ Service details -> OAuth2 -- ^ Service details
@ -89,7 +127,7 @@ authOAuth2 name oauth getCreds = AuthPlugin name dispatch login
Left _ -> permissionDenied "Unable to retreive OAuth2 token" Left _ -> permissionDenied "Unable to retreive OAuth2 token"
Right token -> do Right token -> do
creds <- liftIO $ getCreds (authHttpManager master) token creds <- liftIO $ getCreds (authHttpManager master) token
lift $ setCredsRedirect creds lift $ setCredsRedirect $ addAccessToken token creds
_ -> _ ->
permissionDenied "Invalid OAuth2 state token" permissionDenied "Invalid OAuth2 state token"
@ -104,5 +142,10 @@ authOAuth2 name oauth getCreds = AuthPlugin name dispatch login
<a href=@{tm $ oauth2Url name}>Login via #{name} <a href=@{tm $ oauth2Url name}>Login via #{name}
|] |]
addAccessToken token creds = creds
{ credsExtra =
("access_token", bsToText $ accessToken token) : credsExtra creds
}
bsToText :: ByteString -> Text bsToText :: ByteString -> Text
bsToText = decodeUtf8With lenientDecode bsToText = decodeUtf8With lenientDecode

View File

@ -18,13 +18,10 @@ module Yesod.Auth.OAuth2.Github
import Control.Applicative ((<$>), (<*>), pure) import Control.Applicative ((<$>), (<*>), pure)
#endif #endif
import Control.Exception.Lifted
import Control.Monad (mzero) import Control.Monad (mzero)
import Data.Aeson import Data.Aeson
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Network.HTTP.Conduit (Manager)
import Yesod.Auth import Yesod.Auth
import Yesod.Auth.OAuth2 import Yesod.Auth.OAuth2
@ -56,50 +53,46 @@ instance FromJSON GithubUserEmail where
parseJSON _ = mzero parseJSON _ = mzero
data GithubProfile = GithubProfile GithubUser (Maybe GithubUserEmail)
githubProfileIdent :: GithubProfile -> Text
githubProfileIdent (GithubProfile user _) = T.pack $ show $ githubUserId user
oauth2Github :: YesodAuth m oauth2Github :: YesodAuth m
=> Text -- ^ Client ID => Text -- ^ Client ID
-> Text -- ^ Client Secret -> Text -- ^ Client Secret
-> AuthPlugin m -> AuthPlugin m
oauth2Github clientId clientSecret = oauth2GithubScoped clientId clientSecret ["user:email"] oauth2Github = oauth2GithubScoped ["user:email"]
oauth2GithubScoped :: YesodAuth m oauth2GithubScoped :: YesodAuth m
=> Text -- ^ Client ID => [Text] -- ^ Scopes to request
-> Text -- ^ Client ID
-> Text -- ^ Client Secret -> Text -- ^ Client Secret
-> [Text] -- ^ List of scopes to request
-> AuthPlugin m -> AuthPlugin m
oauth2GithubScoped clientId clientSecret scopes = authOAuth2 "github" oauth fetchGithubProfile oauth2GithubScoped scopes = oauth2Plugin OAuth2Plugin
where { oapName = "github"
oauth = OAuth2 , oapAuthEndpoint = "https://github.com/login/oauth/authorize?scope=" <> T.intercalate "," scopes
{ oauthClientId = encodeUtf8 clientId , oapTokenEndpoint = "https://github.com/login/oauth/access_token"
, oauthClientSecret = encodeUtf8 clientSecret , oapFetchProfile = fetchProfile
, oauthOAuthorizeEndpoint = encodeUtf8 $ "https://github.com/login/oauth/authorize?scope=" <> T.intercalate "," scopes , oapToCredsIdent = githubProfileIdent
, oauthAccessTokenEndpoint = "https://github.com/login/oauth/access_token" , oapToCredsExtra = toCredsExtra
, oauthCallback = Nothing
} }
fetchGithubProfile :: Manager -> AccessToken -> IO (Creds m) where
fetchGithubProfile manager token = do fetchProfile manager token = do
userResult <- authGetJSON manager token "https://api.github.com/user" user <- authGetJSON manager token "https://api.github.com/user"
mailResult <- authGetJSON manager token "https://api.github.com/user/emails" emails <- authGetJSON manager token "https://api.github.com/user/emails"
case (userResult, mailResult) of return $ case emails of
(Right _, Right []) -> throwIO $ InvalidProfileResponse "github" "no mail address for user" Right (email:_) -> GithubProfile <$> user <*> pure (Just email)
(Right user, Right mails) -> return $ toCreds user mails token _ -> Left "user has no email"
(Left err, _) -> throwIO $ InvalidProfileResponse "github" err
(_, Left err) -> throwIO $ InvalidProfileResponse "github" err
toCreds :: GithubUser -> [GithubUserEmail] -> AccessToken -> Creds m toCredsExtra (GithubProfile user email) =
toCreds user userMail token = Creds [ ("login", githubUserLogin user)
{ credsPlugin = "github"
, credsIdent = T.pack $ show $ githubUserId user
, credsExtra =
[ ("email", githubUserEmail $ head userMail)
, ("login", githubUserLogin user)
, ("avatar_url", githubUserAvatarUrl user) , ("avatar_url", githubUserAvatarUrl user)
, ("access_token", decodeUtf8 $ accessToken token) ]
] ++ maybeName (githubUserName user) ++ maybeToTuple "name" (githubUserName user)
} ++ maybeToTuple "email" (githubUserEmail <$> email)
where maybeToTuple _ Nothing = []
maybeName Nothing = [] maybeToTuple k (Just x) = [(k, x)]
maybeName (Just name) = [("name", name)]

View File

@ -10,21 +10,17 @@ module Yesod.Auth.OAuth2.Spotify
) where ) where
#if __GLASGOW_HASKELL__ < 710 #if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>), (<*>), pure) import Control.Applicative ((<$>), (<*>))
#endif #endif
import Control.Exception.Lifted
import Control.Monad (mzero) import Control.Monad (mzero)
import Data.Aeson import Data.Aeson
import Data.ByteString (ByteString)
import Data.Maybe import Data.Maybe
import Data.Monoid ((<>))
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Network.HTTP.Conduit(Manager)
import Yesod.Auth import Yesod.Auth
import Yesod.Auth.OAuth2 import Yesod.Auth.OAuth2
import qualified Data.ByteString as B
import qualified Data.Text as T import qualified Data.Text as T
data SpotifyUserImage = SpotifyUserImage data SpotifyUserImage = SpotifyUserImage
@ -66,33 +62,22 @@ instance FromJSON SpotifyUser where
parseJSON _ = mzero parseJSON _ = mzero
oauth2Spotify :: YesodAuth m oauth2Spotify :: YesodAuth m
=> Text -- ^ Client ID => [Text] -- ^ Scopes
-> Text -- ^ Client ID
-> Text -- ^ Client Secret -> Text -- ^ Client Secret
-> [ByteString] -- ^ Scopes
-> AuthPlugin m -> AuthPlugin m
oauth2Spotify clientId clientSecret scope = authOAuth2 "spotify" oauth2Spotify scopes = oauth2Plugin OAuth2Plugin
OAuth2 { oapName = "spotify"
{ oauthClientId = encodeUtf8 clientId , oapAuthEndpoint = "https://accounts.spotify.com/authorize?scope=" <> T.intercalate "%20" scopes
, oauthClientSecret = encodeUtf8 clientSecret , oapTokenEndpoint = "https://accounts.spotify.com/api/token"
, oauthOAuthorizeEndpoint = B.append "https://accounts.spotify.com/authorize?scope=" (B.intercalate "%20" scope) , oapFetchProfile = \manager token ->
, oauthAccessTokenEndpoint = "https://accounts.spotify.com/api/token" authGetJSON manager token "https://api.spotify.com/v1/me"
, oauthCallback = Nothing , oapToCredsIdent = spotifyUserId
, oapToCredsExtra = toCredsExtra
} }
fetchSpotifyProfile
fetchSpotifyProfile :: Manager -> AccessToken -> IO (Creds m) toCredsExtra :: SpotifyUser -> [(Text, Text)]
fetchSpotifyProfile manager token = do toCredsExtra user = mapMaybe getExtra extrasTemplate
result <- authGetJSON manager token "https://api.spotify.com/v1/me"
case result of
Right user -> return $ toCreds user
Left err -> throwIO $ InvalidProfileResponse "spotify" err
toCreds :: SpotifyUser -> Creds m
toCreds user = Creds
{ credsPlugin = "spotify"
, credsIdent = spotifyUserId user
, credsExtra = mapMaybe getExtra extrasTemplate
}
where where
userImage :: Maybe SpotifyUserImage userImage :: Maybe SpotifyUserImage

View File

@ -14,17 +14,14 @@ module Yesod.Auth.OAuth2.Upcase
) where ) where
#if __GLASGOW_HASKELL__ < 710 #if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>), (<*>), pure) import Control.Applicative ((<$>), (<*>))
#endif #endif
import Control.Exception.Lifted
import Control.Monad (mzero) import Control.Monad (mzero)
import Data.Aeson import Data.Aeson
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Yesod.Auth import Yesod.Auth
import Yesod.Auth.OAuth2 import Yesod.Auth.OAuth2
import Network.HTTP.Conduit(Manager)
import qualified Data.Text as T import qualified Data.Text as T
data UpcaseUser = UpcaseUser data UpcaseUser = UpcaseUser
@ -55,31 +52,19 @@ oauth2Upcase :: YesodAuth m
=> Text -- ^ Client ID => Text -- ^ Client ID
-> Text -- ^ Client Secret -> Text -- ^ Client Secret
-> AuthPlugin m -> AuthPlugin m
oauth2Upcase clientId clientSecret = authOAuth2 "upcase" oauth2Upcase = oauth2Plugin OAuth2Plugin
OAuth2 { oapName = "upcase"
{ oauthClientId = encodeUtf8 clientId , oapAuthEndpoint = "http://upcase.com/oauth/authorize"
, oauthClientSecret = encodeUtf8 clientSecret , oapTokenEndpoint = "http://upcase.com/oauth/token"
, oauthOAuthorizeEndpoint = "http://upcase.com/oauth/authorize" , oapFetchProfile = \manager token ->
, oauthAccessTokenEndpoint = "http://upcase.com/oauth/token" authGetJSON manager token "http://upcase.com/api/v1/me.json"
, oauthCallback = Nothing , oapToCredsIdent = T.pack . show . upcaseUserId
, oapToCredsExtra = toCredsExtra
} }
fetchUpcaseProfile
fetchUpcaseProfile :: Manager -> AccessToken -> IO (Creds m) toCredsExtra :: UpcaseUser -> [(Text, Text)]
fetchUpcaseProfile manager token = do toCredsExtra user =
result <- authGetJSON manager token "http://upcase.com/api/v1/me.json"
case result of
Right (UpcaseResponse user) -> return $ toCreds user
Left err -> throwIO $ InvalidProfileResponse "upcase" err
toCreds :: UpcaseUser -> Creds m
toCreds user = Creds
{ credsPlugin = "upcase"
, credsIdent = T.pack $ show $ upcaseUserId user
, credsExtra =
[ ("first_name", upcaseUserFirstName user) [ ("first_name", upcaseUserFirstName user)
, ("last_name" , upcaseUserLastName user) , ("last_name" , upcaseUserLastName user)
, ("email" , upcaseUserEmail user) , ("email" , upcaseUserEmail user)
] ]
}