OAuth.hs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. {-# LANGUAGE DeriveDataTypeable, OverloadedStrings, QuasiQuotes #-}
  2. {-# LANGUAGE FlexibleContexts #-}
  3. module Yesod.Auth.OAuth
  4. ( authOAuth
  5. , oauthUrl
  6. , authTwitter
  7. , authTwitterUsingUserId
  8. , twitterUrl
  9. , authTumblr
  10. , tumblrUrl
  11. , module Web.Authenticate.OAuth
  12. ) where
  13. import Control.Applicative ((<$>), (<*>))
  14. import Control.Arrow ((***))
  15. import Control.Exception.Lifted
  16. import Control.Monad.IO.Class
  17. import Data.ByteString (ByteString)
  18. import Data.Maybe
  19. import Data.Text (Text)
  20. import qualified Data.Text as T
  21. import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
  22. import Data.Text.Encoding.Error (lenientDecode)
  23. import Data.Typeable
  24. import Web.Authenticate.OAuth
  25. import Yesod.Auth
  26. import Yesod.Form
  27. import Yesod.Core
  28. data YesodOAuthException = CredentialError String Credential
  29. | SessionError String
  30. deriving (Show, Typeable)
  31. instance Exception YesodOAuthException
  32. oauthUrl :: Text -> AuthRoute
  33. oauthUrl name = PluginR name ["forward"]
  34. authOAuth :: YesodAuth m
  35. => OAuth -- ^ 'OAuth' data-type for signing.
  36. -> (Credential -> IO (Creds m)) -- ^ How to extract ident.
  37. -> AuthPlugin m
  38. authOAuth oauth mkCreds = AuthPlugin name dispatch login
  39. where
  40. name = T.pack $ oauthServerName oauth
  41. url = PluginR name []
  42. lookupTokenSecret = bsToText . fromMaybe "" . lookup "oauth_token_secret" . unCredential
  43. oauthSessionName = "__oauth_token_secret"
  44. dispatch "GET" ["forward"] = do
  45. render <- lift getUrlRender
  46. tm <- getRouteToParent
  47. let oauth' = oauth { oauthCallback = Just $ encodeUtf8 $ render $ tm url }
  48. master <- lift getYesod
  49. tok <- lift $ getTemporaryCredential oauth' (authHttpManager master)
  50. setSession oauthSessionName $ lookupTokenSecret tok
  51. redirect $ authorizeUrl oauth' tok
  52. dispatch "GET" [] = lift $ do
  53. Just tokSec <- lookupSession oauthSessionName
  54. deleteSession oauthSessionName
  55. reqTok <-
  56. if oauthVersion oauth == OAuth10
  57. then do
  58. oaTok <- runInputGet $ ireq textField "oauth_token"
  59. return $ Credential [ ("oauth_token", encodeUtf8 oaTok)
  60. , ("oauth_token_secret", encodeUtf8 tokSec)
  61. ]
  62. else do
  63. (verifier, oaTok) <-
  64. runInputGet $ (,) <$> ireq textField "oauth_verifier"
  65. <*> ireq textField "oauth_token"
  66. return $ Credential [ ("oauth_verifier", encodeUtf8 verifier)
  67. , ("oauth_token", encodeUtf8 oaTok)
  68. , ("oauth_token_secret", encodeUtf8 tokSec)
  69. ]
  70. master <- getYesod
  71. accTok <- getAccessToken oauth reqTok (authHttpManager master)
  72. creds <- liftIO $ mkCreds accTok
  73. setCredsRedirect creds
  74. dispatch _ _ = notFound
  75. login tm = do
  76. render <- getUrlRender
  77. let oaUrl = render $ tm $ oauthUrl name
  78. [whamlet| <a href=#{oaUrl}>Login via #{name} |]
  79. mkExtractCreds :: YesodAuth m => Text -> String -> Credential -> IO (Creds m)
  80. mkExtractCreds name idName (Credential dic) = do
  81. let mcrId = decodeUtf8With lenientDecode <$> lookup (encodeUtf8 $ T.pack idName) dic
  82. case mcrId of
  83. Just crId -> return $ Creds name crId $ map (bsToText *** bsToText) dic
  84. Nothing -> throwIO $ CredentialError ("key not found: " ++ idName) (Credential dic)
  85. authTwitter' :: YesodAuth m
  86. => ByteString -- ^ Consumer Key
  87. -> ByteString -- ^ Consumer Secret
  88. -> String
  89. -> AuthPlugin m
  90. authTwitter' key secret idName = authOAuth
  91. (newOAuth { oauthServerName = "twitter"
  92. , oauthRequestUri = "https://api.twitter.com/oauth/request_token"
  93. , oauthAccessTokenUri = "https://api.twitter.com/oauth/access_token"
  94. , oauthAuthorizeUri = "https://api.twitter.com/oauth/authorize"
  95. , oauthSignatureMethod = HMACSHA1
  96. , oauthConsumerKey = key
  97. , oauthConsumerSecret = secret
  98. , oauthVersion = OAuth10a
  99. })
  100. (mkExtractCreds "twitter" idName)
  101. -- | This plugin uses Twitter's /screen_name/ as ID, which shouldn't be used for authentication because it is mutable.
  102. authTwitter :: YesodAuth m
  103. => ByteString -- ^ Consumer Key
  104. -> ByteString -- ^ Consumer Secret
  105. -> AuthPlugin m
  106. authTwitter key secret = authTwitter' key secret "screen_name"
  107. {-# DEPRECATED authTwitter "Use authTwitterUsingUserID instead" #-}
  108. -- | Twitter plugin which uses Twitter's /user_id/ as ID.
  109. --
  110. -- For more information, see: https://github.com/yesodweb/yesod/pull/1168
  111. --
  112. -- @since 1.4.1
  113. authTwitterUsingUserId :: YesodAuth m
  114. => ByteString -- ^ Consumer Key
  115. -> ByteString -- ^ Consumer Secret
  116. -> AuthPlugin m
  117. authTwitterUsingUserId key secret = authTwitter' key secret "user_id"
  118. twitterUrl :: AuthRoute
  119. twitterUrl = oauthUrl "twitter"
  120. authTumblr :: YesodAuth m
  121. => ByteString -- ^ Consumer Key
  122. -> ByteString -- ^ Consumer Secret
  123. -> AuthPlugin m
  124. authTumblr key secret = authOAuth
  125. (newOAuth { oauthServerName = "tumblr"
  126. , oauthRequestUri = "http://www.tumblr.com/oauth/request_token"
  127. , oauthAccessTokenUri = "http://www.tumblr.com/oauth/access_token"
  128. , oauthAuthorizeUri = "http://www.tumblr.com/oauth/authorize"
  129. , oauthSignatureMethod = HMACSHA1
  130. , oauthConsumerKey = key
  131. , oauthConsumerSecret = secret
  132. , oauthVersion = OAuth10a
  133. })
  134. (mkExtractCreds "tumblr" "name")
  135. tumblrUrl :: AuthRoute
  136. tumblrUrl = oauthUrl "tumblr"
  137. bsToText :: ByteString -> Text
  138. bsToText = decodeUtf8With lenientDecode