It all compiles

This commit is contained in:
Michael Snoyman 2019-03-12 13:14:27 +02:00
parent cd76b34497
commit eccbe4acbe
No known key found for this signature in database
GPG Key ID: A048E8C057E86876
10 changed files with 132 additions and 891 deletions

View File

@ -4,6 +4,7 @@
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Yesod.Auth.OAuth module Yesod.Auth.OAuth
( authOAuth ( authOAuth
, oauthUrl , oauthUrl
@ -14,14 +15,8 @@ module Yesod.Auth.OAuth
, tumblrUrl , tumblrUrl
, module Web.Authenticate.OAuth , module Web.Authenticate.OAuth
) where ) where
import Control.Applicative as A ((<$>), (<*>))
import Control.Arrow ((***)) import Control.Arrow ((***))
import UnliftIO.Exception import RIO
import Control.Monad.IO.Class
import UnliftIO (MonadUnliftIO)
import Data.ByteString (ByteString)
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With, encodeUtf8) import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
import Data.Text.Encoding.Error (lenientDecode) import Data.Text.Encoding.Error (lenientDecode)
@ -53,14 +48,9 @@ authOAuth oauth mkCreds = AuthPlugin name dispatch login
oauthSessionName = "__oauth_token_secret" oauthSessionName = "__oauth_token_secret"
dispatch dispatch
:: ( MonadHandler m :: Text
, master ~ HandlerSite m
, Auth ~ SubHandlerSite m
, MonadUnliftIO m
)
=> Text
-> [Text] -> [Text]
-> m TypedContent -> SubHandlerFor Auth master TypedContent
dispatch "GET" ["forward"] = do dispatch "GET" ["forward"] = do
render <- getUrlRender render <- getUrlRender
tm <- getRouteToParent tm <- getRouteToParent
@ -83,8 +73,8 @@ authOAuth oauth mkCreds = AuthPlugin name dispatch login
] ]
else do else do
(verifier, oaTok) <- (verifier, oaTok) <-
runInputGet $ (,) A.<$> ireq textField "oauth_verifier" runInputGet $ (,) <$> ireq textField "oauth_verifier"
A.<*> ireq textField "oauth_token" <*> ireq textField "oauth_token"
return $ Credential [ ("oauth_verifier", encodeUtf8 verifier) return $ Credential [ ("oauth_verifier", encodeUtf8 verifier)
, ("oauth_token", encodeUtf8 oaTok) , ("oauth_token", encodeUtf8 oaTok)
, ("oauth_token_secret", encodeUtf8 tokSec) , ("oauth_token_secret", encodeUtf8 tokSec)

View File

@ -24,7 +24,7 @@ library
build-depends: authenticate-oauth >= 1.5 && < 1.7 build-depends: authenticate-oauth >= 1.5 && < 1.7
, bytestring >= 0.9.1.4 , bytestring >= 0.9.1.4
, text >= 0.7 , text >= 0.7
, unliftio , rio
, yesod-auth >= 1.6 && < 1.7 , yesod-auth >= 1.6 && < 1.7
, yesod-core >= 1.6 && < 1.7 , yesod-core >= 1.6 && < 1.7
, yesod-form >= 1.6 && < 1.7 , yesod-form >= 1.6 && < 1.7

View File

@ -6,6 +6,7 @@
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
@ -15,6 +16,7 @@ module Yesod.Auth
( -- * Subsite ( -- * Subsite
Auth Auth
, AuthRoute , AuthRoute
, AuthHandler
, Route (..) , Route (..)
, AuthPlugin (..) , AuthPlugin (..)
, getAuth , getAuth
@ -38,9 +40,6 @@ module Yesod.Auth
, requireAuth , requireAuth
-- * Exception -- * Exception
, AuthException (..) , AuthException (..)
-- * Helper
, MonadAuthHandler
, AuthHandler
-- * Internal -- * Internal
, credsKey , credsKey
, provideJsonMessage , provideJsonMessage
@ -48,9 +47,8 @@ module Yesod.Auth
, asHtml , asHtml
) where ) where
import Control.Monad (when) import RIO
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import UnliftIO (withRunInIO, MonadUnliftIO)
import Yesod.Auth.Routes import Yesod.Auth.Routes
import Data.Aeson hiding (json) import Data.Aeson hiding (json)
@ -76,10 +74,9 @@ import Network.HTTP.Types (Status, internalServerError500, unauthorized401)
import qualified Control.Monad.Trans.Writer as Writer import qualified Control.Monad.Trans.Writer as Writer
import Control.Monad (void) import Control.Monad (void)
type AuthRoute = Route Auth type AuthHandler site = SubHandlerFor Auth site
type MonadAuthHandler master m = (MonadHandler m, YesodAuth master, master ~ HandlerSite m, Auth ~ SubHandlerSite m, MonadUnliftIO m) type AuthRoute = Route Auth
type AuthHandler master a = forall m. MonadAuthHandler master m => m a
type Method = Text type Method = Text
type Piece = Text type Piece = Text
@ -94,7 +91,7 @@ data AuthenticationResult master
data AuthPlugin master = AuthPlugin data AuthPlugin master = AuthPlugin
{ apName :: Text { apName :: Text
, apDispatch :: Method -> [Piece] -> AuthHandler master TypedContent , apDispatch :: Method -> [Piece] -> SubHandlerFor Auth master TypedContent
, apLogin :: (Route Auth -> Route master) -> WidgetFor master () , apLogin :: (Route Auth -> Route master) -> WidgetFor master ()
} }
@ -112,7 +109,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
type AuthId master type AuthId master
-- | specify the layout. Uses defaultLayout by default -- | specify the layout. Uses defaultLayout by default
authLayout :: (MonadHandler m, HandlerSite m ~ master) => WidgetFor master () -> m Html authLayout :: (HasHandlerData env, HandlerSite env ~ master) => WidgetFor master () -> RIO env Html
authLayout = liftHandler . defaultLayout authLayout = liftHandler . defaultLayout
-- | Default destination on successful login, if no other -- | Default destination on successful login, if no other
@ -128,7 +125,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- Default implementation is in terms of @'getAuthId'@ -- Default implementation is in terms of @'getAuthId'@
-- --
-- @since: 1.4.4 -- @since: 1.4.4
authenticate :: (MonadHandler m, HandlerSite m ~ master) => Creds master -> m (AuthenticationResult master) authenticate :: (HasHandlerData env, HandlerSite env ~ master) => Creds master -> RIO env (AuthenticationResult master)
authenticate creds = do authenticate creds = do
muid <- getAuthId creds muid <- getAuthId creds
@ -138,7 +135,7 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- --
-- Default implementation is in terms of @'authenticate'@ -- Default implementation is in terms of @'authenticate'@
-- --
getAuthId :: (MonadHandler m, HandlerSite m ~ master) => Creds master -> m (Maybe (AuthId master)) getAuthId :: (HasHandlerData env, HandlerSite env ~ master) => Creds master -> RIO env (Maybe (AuthId master))
getAuthId creds = do getAuthId creds = do
auth <- authenticate creds auth <- authenticate creds
@ -168,7 +165,9 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- > lift $ redirect HomeR -- or any other Handler code you want -- > lift $ redirect HomeR -- or any other Handler code you want
-- > defaultLoginHandler -- > defaultLoginHandler
-- --
loginHandler :: AuthHandler master Html loginHandler
:: (HasHandlerData env, SubHandlerSite env ~ Auth, HandlerSite env ~ master)
=> RIO env Html
loginHandler = defaultLoginHandler loginHandler = defaultLoginHandler
-- | Used for i18n of messages provided by this package. -- | Used for i18n of messages provided by this package.
@ -194,16 +193,16 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- type. This allows backends to reuse persistent connections. If none of -- type. This allows backends to reuse persistent connections. If none of
-- the backends you're using use HTTP connections, you can safely return -- the backends you're using use HTTP connections, you can safely return
-- @error \"authHttpManager\"@ here. -- @error \"authHttpManager\"@ here.
authHttpManager :: (MonadHandler m, HandlerSite m ~ master) => m Manager authHttpManager :: (HasHandlerData env, HandlerSite env ~ master) => RIO env Manager
authHttpManager = liftIO getGlobalManager authHttpManager = liftIO getGlobalManager
-- | Called on a successful login. By default, calls -- | Called on a successful login. By default, calls
-- @addMessageI "success" NowLoggedIn@. -- @addMessageI "success" NowLoggedIn@.
onLogin :: (MonadHandler m, master ~ HandlerSite m) => m () onLogin :: (HasHandlerData env, master ~ HandlerSite env) => RIO env ()
onLogin = addMessageI "success" Msg.NowLoggedIn onLogin = addMessageI "success" Msg.NowLoggedIn
-- | Called on logout. By default, does nothing -- | Called on logout. By default, does nothing
onLogout :: (MonadHandler m, master ~ HandlerSite m) => m () onLogout :: (HasHandlerData env, master ~ HandlerSite env) => RIO env ()
onLogout = return () onLogout = return ()
-- | Retrieves user credentials, if user is authenticated. -- | Retrieves user credentials, if user is authenticated.
@ -215,16 +214,20 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- other than a browser. -- other than a browser.
-- --
-- @since 1.2.0 -- @since 1.2.0
maybeAuthId :: (MonadHandler m, master ~ HandlerSite m) => m (Maybe (AuthId master)) maybeAuthId :: (HasHandlerData env, master ~ HandlerSite env) => RIO env (Maybe (AuthId master))
default maybeAuthId default maybeAuthId
:: (MonadHandler m, master ~ HandlerSite m, YesodAuthPersist master, Typeable (AuthEntity master)) :: (HasHandlerData env, master ~ HandlerSite env, YesodAuthPersist master, Typeable (AuthEntity master))
=> m (Maybe (AuthId master)) => RIO env (Maybe (AuthId master))
maybeAuthId = defaultMaybeAuthId maybeAuthId = defaultMaybeAuthId
-- | Called on login error for HTTP requests. By default, calls -- | Called on login error for HTTP requests. By default, calls
-- @addMessage@ with "error" as status and redirects to @dest@. -- @addMessage@ with "error" as status and redirects to @dest@.
onErrorHtml :: (MonadHandler m, HandlerSite m ~ master) => Route master -> Text -> m Html onErrorHtml
:: (HasHandlerData env, HandlerSite env ~ master)
=> Route master
-> Text
-> RIO env Html
onErrorHtml dest msg = do onErrorHtml dest msg = do
addMessage "error" $ toHtml msg addMessage "error" $ toHtml msg
fmap asHtml $ redirect dest fmap asHtml $ redirect dest
@ -235,10 +238,10 @@ class (Yesod master, PathPiece (AuthId master), RenderMessage master FormMessage
-- The HTTP 'Request' is given in case it is useful to change behavior based on inspecting the request. -- The HTTP 'Request' is given in case it is useful to change behavior based on inspecting the request.
-- This is an experimental API that is not broadly used throughout the yesod-auth code base -- This is an experimental API that is not broadly used throughout the yesod-auth code base
runHttpRequest runHttpRequest
:: (MonadHandler m, HandlerSite m ~ master, MonadUnliftIO m) :: (HasHandlerData env, HandlerSite env ~ master)
=> Request => Request
-> (Response BodyReader -> m a) -> (Response BodyReader -> RIO env a)
-> m a -> RIO env a
runHttpRequest req inner = do runHttpRequest req inner = do
man <- authHttpManager man <- authHttpManager
withRunInIO $ \run -> withResponse req man $ run . inner withRunInIO $ \run -> withResponse req man $ run . inner
@ -261,8 +264,8 @@ credsKey = "_ID"
-- --
-- @since 1.1.2 -- @since 1.1.2
defaultMaybeAuthId defaultMaybeAuthId
:: (MonadHandler m, HandlerSite m ~ master, YesodAuthPersist master, Typeable (AuthEntity master)) :: (HasHandlerData env, HandlerSite env ~ master, YesodAuthPersist master, Typeable (AuthEntity master))
=> m (Maybe (AuthId master)) => RIO env (Maybe (AuthId master))
defaultMaybeAuthId = runMaybeT $ do defaultMaybeAuthId = runMaybeT $ do
s <- MaybeT $ lookupSession credsKey s <- MaybeT $ lookupSession credsKey
aid <- MaybeT $ return $ fromPathPiece s aid <- MaybeT $ return $ fromPathPiece s
@ -270,13 +273,13 @@ defaultMaybeAuthId = runMaybeT $ do
return aid return aid
cachedAuth cachedAuth
:: ( MonadHandler m :: ( HasHandlerData env
, YesodAuthPersist master , YesodAuthPersist master
, Typeable (AuthEntity master) , Typeable (AuthEntity master)
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> AuthId master => AuthId master
-> m (Maybe (AuthEntity master)) -> RIO env (Maybe (AuthEntity master))
cachedAuth cachedAuth
= fmap unCachedMaybeAuth = fmap unCachedMaybeAuth
. cached . cached
@ -290,7 +293,9 @@ cachedAuth
-- wraps the result in 'authLayout'. See 'loginHandler' for more details. -- wraps the result in 'authLayout'. See 'loginHandler' for more details.
-- --
-- @since 1.4.9 -- @since 1.4.9
defaultLoginHandler :: AuthHandler master Html defaultLoginHandler
:: (HasHandlerData env, SubHandlerSite env ~ Auth, YesodAuth (HandlerSite env))
=> RIO env Html
defaultLoginHandler = do defaultLoginHandler = do
tp <- getRouteToParent tp <- getRouteToParent
authLayout $ do authLayout $ do
@ -298,21 +303,21 @@ defaultLoginHandler = do
master <- getYesod master <- getYesod
mapM_ (flip apLogin tp) (authPlugins master) mapM_ (flip apLogin tp) (authPlugins master)
loginErrorMessageI loginErrorMessageI
:: Route Auth :: (HasHandlerData env, SubHandlerSite env ~ Auth, YesodAuth (HandlerSite env))
=> Route Auth
-> AuthMessage -> AuthMessage
-> AuthHandler master TypedContent -> RIO env TypedContent
loginErrorMessageI dest msg = do loginErrorMessageI dest msg = do
toParent <- getRouteToParent toParent <- getRouteToParent
loginErrorMessageMasterI (toParent dest) msg loginErrorMessageMasterI (toParent dest) msg
loginErrorMessageMasterI loginErrorMessageMasterI
:: (MonadHandler m, HandlerSite m ~ master, YesodAuth master) :: (HasHandlerData env, HandlerSite env ~ master, YesodAuth master)
=> Route master => Route master
-> AuthMessage -> AuthMessage
-> m TypedContent -> RIO env TypedContent
loginErrorMessageMasterI dest msg = do loginErrorMessageMasterI dest msg = do
mr <- getMessageRender mr <- getMessageRender
loginErrorMessage dest (mr msg) loginErrorMessage dest (mr msg)
@ -320,28 +325,28 @@ loginErrorMessageMasterI dest msg = do
-- | For HTML, set the message and redirect to the route. -- | For HTML, set the message and redirect to the route.
-- For JSON, send the message and a 401 status -- For JSON, send the message and a 401 status
loginErrorMessage loginErrorMessage
:: (MonadHandler m, YesodAuth (HandlerSite m)) :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Route (HandlerSite m) => Route (HandlerSite env)
-> Text -> Text
-> m TypedContent -> RIO env TypedContent
loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg) loginErrorMessage dest msg = messageJson401 msg (onErrorHtml dest msg)
messageJson401 messageJson401
:: MonadHandler m :: HasHandlerData env
=> Text => Text
-> m Html -> RIO env Html
-> m TypedContent -> RIO env TypedContent
messageJson401 = messageJsonStatus unauthorized401 messageJson401 = messageJsonStatus unauthorized401
messageJson500 :: MonadHandler m => Text -> m Html -> m TypedContent messageJson500 :: HasHandlerData env => Text -> RIO env Html -> RIO env TypedContent
messageJson500 = messageJsonStatus internalServerError500 messageJson500 = messageJsonStatus internalServerError500
messageJsonStatus messageJsonStatus
:: MonadHandler m :: HasHandlerData env
=> Status => Status
-> Text -> Text
-> m Html -> RIO env Html
-> m TypedContent -> RIO env TypedContent
messageJsonStatus status msg html = selectRep $ do messageJsonStatus status msg html = selectRep $ do
provideRep html provideRep html
provideRep $ do provideRep $ do
@ -354,9 +359,9 @@ provideJsonMessage msg = provideRep $ return $ object ["message" .= msg]
setCredsRedirect setCredsRedirect
:: (MonadHandler m, YesodAuth (HandlerSite m)) :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Creds (HandlerSite m) -- ^ new credentials => Creds (HandlerSite env) -- ^ new credentials
-> m TypedContent -> RIO env TypedContent
setCredsRedirect creds = do setCredsRedirect creds = do
y <- getYesod y <- getYesod
auth <- authenticate creds auth <- authenticate creds
@ -379,7 +384,7 @@ setCredsRedirect creds = do
Just ar -> loginErrorMessageMasterI ar msg Just ar -> loginErrorMessageMasterI ar msg
ServerError msg -> do ServerError msg -> do
$(logError) msg logError $ display msg
case authRoute y of case authRoute y of
Nothing -> do Nothing -> do
@ -395,10 +400,10 @@ setCredsRedirect creds = do
return $ renderAuthMessage master langs msg return $ renderAuthMessage master langs msg
-- | Sets user credentials for the session after checking them with authentication backends. -- | Sets user credentials for the session after checking them with authentication backends.
setCreds :: (MonadHandler m, YesodAuth (HandlerSite m)) setCreds :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Bool -- ^ if HTTP redirects should be done => Bool -- ^ if HTTP redirects should be done
-> Creds (HandlerSite m) -- ^ new credentials -> Creds (HandlerSite env) -- ^ new credentials
-> m () -> RIO env ()
setCreds doRedirects creds = setCreds doRedirects creds =
if doRedirects if doRedirects
then void $ setCredsRedirect creds then void $ setCredsRedirect creds
@ -409,10 +414,10 @@ setCreds doRedirects creds =
-- | same as defaultLayoutJson, but uses authLayout -- | same as defaultLayoutJson, but uses authLayout
authLayoutJson authLayoutJson
:: (ToJSON j, MonadAuthHandler master m) :: (ToJSON j, HasHandlerData env, YesodAuth (HandlerSite env))
=> WidgetFor master () -- ^ HTML => WidgetFor (HandlerSite env) () -- ^ HTML
-> m j -- ^ JSON -> RIO env j -- ^ JSON
-> m TypedContent -> RIO env TypedContent
authLayoutJson w json = selectRep $ do authLayoutJson w json = selectRep $ do
provideRep $ authLayout w provideRep $ authLayout w
provideRep $ fmap toJSON json provideRep $ fmap toJSON json
@ -420,9 +425,9 @@ authLayoutJson w json = selectRep $ do
-- | Clears current user credentials for the session. -- | Clears current user credentials for the session.
-- --
-- @since 1.1.7 -- @since 1.1.7
clearCreds :: (MonadHandler m, YesodAuth (HandlerSite m)) clearCreds :: (HasHandlerData env, YesodAuth (HandlerSite env))
=> Bool -- ^ if HTTP redirect to 'logoutDest' should be done => Bool -- ^ if HTTP redirect to 'logoutDest' should be done
-> m () -> RIO env ()
clearCreds doRedirects = do clearCreds doRedirects = do
y <- getYesod y <- getYesod
onLogout onLogout
@ -430,7 +435,7 @@ clearCreds doRedirects = do
when doRedirects $ do when doRedirects $ do
redirectUltDest $ logoutDest y redirectUltDest $ logoutDest y
getCheckR :: AuthHandler master TypedContent getCheckR :: (YesodAuth (HandlerSite env), HasHandlerData env) => RIO env TypedContent
getCheckR = do getCheckR = do
creds <- maybeAuthId creds <- maybeAuthId
authLayoutJson (do authLayoutJson (do
@ -451,23 +456,27 @@ $nothing
[ (T.pack "logged_in", Bool $ maybe False (const True) creds) [ (T.pack "logged_in", Bool $ maybe False (const True) creds)
] ]
setUltDestReferer' :: (MonadHandler m, YesodAuth (HandlerSite m)) => m () setUltDestReferer' :: (HasHandlerData env, YesodAuth (HandlerSite env)) => RIO env ()
setUltDestReferer' = do setUltDestReferer' = do
master <- getYesod master <- getYesod
when (redirectToReferer master) setUltDestReferer when (redirectToReferer master) setUltDestReferer
getLoginR :: AuthHandler master Html getLoginR :: (HasHandlerData env, YesodAuth (HandlerSite env), SubHandlerSite env ~ Auth) => RIO env Html
getLoginR = setUltDestReferer' >> loginHandler getLoginR = setUltDestReferer' >> loginHandler
getLogoutR :: AuthHandler master () getLogoutR :: (HasHandlerData env, YesodAuth (HandlerSite env), SubHandlerSite env ~ Auth) => RIO env ()
getLogoutR = do getLogoutR = do
tp <- getRouteToParent tp <- getRouteToParent
setUltDestReferer' >> redirectToPost (tp LogoutR) setUltDestReferer' >> redirectToPost (tp LogoutR)
postLogoutR :: AuthHandler master () postLogoutR :: (HasHandlerData env, YesodAuth (HandlerSite env)) => RIO env ()
postLogoutR = clearCreds True postLogoutR = clearCreds True
handlePluginR :: Text -> [Text] -> AuthHandler master TypedContent handlePluginR
:: YesodAuth site
=> Text
-> [Text]
-> SubHandlerFor Auth site TypedContent
handlePluginR plugin pieces = do handlePluginR plugin pieces = do
master <- getYesod master <- getYesod
env <- waiRequest env <- waiRequest
@ -486,9 +495,9 @@ maybeAuth :: ( YesodAuthPersist master
, Key val ~ AuthId master , Key val ~ AuthId master
, PersistEntity val , PersistEntity val
, Typeable val , Typeable val
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) => m (Maybe (Entity val)) ) => RIO env (Maybe (Entity val))
maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair
-- | Similar to 'maybeAuth', but doesnt assume that you are using a -- | Similar to 'maybeAuth', but doesnt assume that you are using a
@ -498,10 +507,10 @@ maybeAuth = fmap (fmap (uncurry Entity)) maybeAuthPair
maybeAuthPair maybeAuthPair
:: ( YesodAuthPersist master :: ( YesodAuthPersist master
, Typeable (AuthEntity master) , Typeable (AuthEntity master)
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> m (Maybe (AuthId master, AuthEntity master)) => RIO env (Maybe (AuthId master, AuthEntity master))
maybeAuthPair = runMaybeT $ do maybeAuthPair = runMaybeT $ do
aid <- MaybeT maybeAuthId aid <- MaybeT maybeAuthId
ae <- MaybeT $ cachedAuth aid ae <- MaybeT $ cachedAuth aid
@ -532,18 +541,21 @@ class (YesodAuth master, YesodPersist master) => YesodAuthPersist master where
type AuthEntity master :: * type AuthEntity master :: *
type AuthEntity master = KeyEntity (AuthId master) type AuthEntity master = KeyEntity (AuthId master)
getAuthEntity :: (MonadHandler m, HandlerSite m ~ master) getAuthEntity
=> AuthId master -> m (Maybe (AuthEntity master)) :: (HasHandlerData env, HandlerSite env ~ master)
=> AuthId master
-> RIO env (Maybe (AuthEntity master))
default getAuthEntity default getAuthEntity
:: ( YesodPersistBackend master ~ backend :: ( YesodPersistBackend master ~ backend
, PersistRecordBackend (AuthEntity master) backend , PersistRecordBackend (AuthEntity master) backend
, Key (AuthEntity master) ~ AuthId master , Key (AuthEntity master) ~ AuthId master
, PersistStore backend , PersistStore backend
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> AuthId master -> m (Maybe (AuthEntity master)) => AuthId master
-> RIO env (Maybe (AuthEntity master))
getAuthEntity = liftHandler . runDB . get getAuthEntity = liftHandler . runDB . get
@ -554,7 +566,7 @@ type instance KeyEntity (Key x) = x
-- authenticated or responds with error 401 if this is an API client (expecting JSON). -- authenticated or responds with error 401 if this is an API client (expecting JSON).
-- --
-- @since 1.1.0 -- @since 1.1.0
requireAuthId :: (MonadHandler m, YesodAuth (HandlerSite m)) => m (AuthId (HandlerSite m)) requireAuthId :: (HasHandlerData env, YesodAuth (HandlerSite env)) => RIO env (AuthId (HandlerSite env))
requireAuthId = maybeAuthId >>= maybe handleAuthLack return requireAuthId = maybeAuthId >>= maybe handleAuthLack return
-- | Similar to 'maybeAuth', but redirects to a login page if user is not -- | Similar to 'maybeAuth', but redirects to a login page if user is not
@ -566,9 +578,9 @@ requireAuth :: ( YesodAuthPersist master
, Key val ~ AuthId master , Key val ~ AuthId master
, PersistEntity val , PersistEntity val
, Typeable val , Typeable val
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) => m (Entity val) ) => RIO env (Entity val)
requireAuth = maybeAuth >>= maybe handleAuthLack return requireAuth = maybeAuth >>= maybe handleAuthLack return
-- | Similar to 'requireAuth', but not tied to Persistent's 'Entity' type. -- | Similar to 'requireAuth', but not tied to Persistent's 'Entity' type.
@ -578,18 +590,18 @@ requireAuth = maybeAuth >>= maybe handleAuthLack return
requireAuthPair requireAuthPair
:: ( YesodAuthPersist master :: ( YesodAuthPersist master
, Typeable (AuthEntity master) , Typeable (AuthEntity master)
, MonadHandler m , HasHandlerData env
, HandlerSite m ~ master , HandlerSite env ~ master
) )
=> m (AuthId master, AuthEntity master) => RIO env (AuthId master, AuthEntity master)
requireAuthPair = maybeAuthPair >>= maybe handleAuthLack return requireAuthPair = maybeAuthPair >>= maybe handleAuthLack return
handleAuthLack :: (YesodAuth (HandlerSite m), MonadHandler m) => m a handleAuthLack :: (YesodAuth (HandlerSite env), HasHandlerData env) => RIO env a
handleAuthLack = do handleAuthLack = do
aj <- acceptsJson aj <- acceptsJson
if aj then notAuthenticated else redirectLogin if aj then notAuthenticated else redirectLogin
redirectLogin :: (YesodAuth (HandlerSite m), MonadHandler m) => m a redirectLogin :: (YesodAuth (HandlerSite env), HasHandlerData env) => RIO env a
redirectLogin = do redirectLogin = do
y <- getYesod y <- getYesod
when (redirectToCurrent y) setUltDestCurrent when (redirectToCurrent y) setUltDestCurrent

View File

@ -1,170 +0,0 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RecordWildCards #-}
-- | NOTE: Mozilla Persona will be shut down by the end of 2016, therefore this
-- module is no longer recommended for use.
module Yesod.Auth.BrowserId
{-# DEPRECATED "Mozilla Persona will be shut down by the end of 2016" #-}
( authBrowserId
, createOnClick, createOnClickOverride
, def
, BrowserIdSettings
, bisAudience
, bisLazyLoad
, forwardUrl
) where
import Yesod.Auth
import Web.Authenticate.BrowserId
import Data.Text (Text)
import Yesod.Core
import qualified Data.Text as T
import Data.Maybe (fromMaybe)
import Control.Monad (when, unless)
import Text.Julius (rawJS)
import Network.URI (uriPath, parseURI)
import Data.FileEmbed (embedFile)
import Data.ByteString (ByteString)
import Data.Default
pid :: Text
pid = "browserid"
forwardUrl :: AuthRoute
forwardUrl = PluginR pid []
complete :: AuthRoute
complete = forwardUrl
-- | A settings type for various configuration options relevant to BrowserID.
--
-- See: <http://www.yesodweb.com/book/settings-types>
--
-- Since 1.2.0
data BrowserIdSettings = BrowserIdSettings
{ bisAudience :: Maybe Text
-- ^ BrowserID audience value. If @Nothing@, will be extracted based on the
-- approot.
--
-- Default: @Nothing@
--
-- Since 1.2.0
, bisLazyLoad :: Bool
-- ^ Use asynchronous Javascript loading for the BrowserID JS file.
--
-- Default: @True@.
--
-- Since 1.2.0
}
instance Default BrowserIdSettings where
def = BrowserIdSettings
{ bisAudience = Nothing
, bisLazyLoad = True
}
authBrowserId :: YesodAuth m => BrowserIdSettings -> AuthPlugin m
authBrowserId bis@BrowserIdSettings {..} = AuthPlugin
{ apName = pid
, apDispatch = \m ps ->
case (m, ps) of
("GET", [assertion]) -> do
audience <-
case bisAudience of
Just a -> return a
Nothing -> do
r <- getUrlRender
tm <- getRouteToParent
return $ T.takeWhile (/= '/') $ stripScheme $ r $ tm LoginR
manager <- authHttpManager
memail <- checkAssertion audience assertion manager
case memail of
Nothing -> do
$logErrorS "yesod-auth" "BrowserID assertion failure"
tm <- getRouteToParent
loginErrorMessage (tm LoginR) "BrowserID login error."
Just email -> setCredsRedirect Creds
{ credsPlugin = pid
, credsIdent = email
, credsExtra = []
}
("GET", ["static", "sign-in.png"]) -> sendResponse
( "image/png" :: ByteString
, toContent $(embedFile "persona_sign_in_blue.png")
)
(_, []) -> badMethod
_ -> notFound
, apLogin = \toMaster -> do
onclick <- createOnClick bis toMaster
autologin <- fmap (== Just "true") $ lookupGetParam "autologin"
when autologin $ toWidget [julius|#{rawJS onclick}();|]
toWidget [hamlet|
$newline never
<p>
<a href="javascript:#{onclick}()">
<img src=@{toMaster loginIcon}>
|]
}
where
loginIcon = PluginR pid ["static", "sign-in.png"]
stripScheme t = fromMaybe t $ T.stripPrefix "//" $ snd $ T.breakOn "//" t
-- | Generates a function to handle on-click events, and returns that function
-- name.
createOnClickOverride :: BrowserIdSettings
-> (Route Auth -> Route master)
-> Maybe (Route master)
-> WidgetFor master Text
createOnClickOverride BrowserIdSettings {..} toMaster mOnRegistration = do
unless bisLazyLoad $ addScriptRemote browserIdJs
onclick <- newIdent
render <- getUrlRender
let login = toJSON $ getPath $ render loginRoute -- (toMaster LoginR)
loginRoute = maybe (toMaster LoginR) id mOnRegistration
toWidget [julius|
function #{rawJS onclick}() {
if (navigator.id) {
navigator.id.watch({
onlogin: function (assertion) {
if (assertion) {
document.location = "@{toMaster complete}/" + assertion;
}
},
onlogout: function () {}
});
navigator.id.request({
returnTo: #{login} + "?autologin=true"
});
}
else {
alert("Loading, please try again");
}
}
|]
when bisLazyLoad $ toWidget [julius|
(function(){
var bid = document.createElement("script");
bid.async = true;
bid.src = #{toJSON browserIdJs};
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(bid, s);
})();
|]
autologin <- fmap (== Just "true") $ lookupGetParam "autologin"
when autologin $ toWidget [julius|#{rawJS onclick}();|]
return onclick
where
getPath t = fromMaybe t $ do
uri <- parseURI $ T.unpack t
return $ T.pack $ uriPath uri
-- | Generates a function to handle on-click events, and returns that function
-- name.
createOnClick :: BrowserIdSettings
-> (Route Auth -> Route master)
-> WidgetFor master Text
createOnClick bidSettings toMaster = createOnClickOverride bidSettings toMaster Nothing

View File

@ -327,7 +327,7 @@ class ( YesodAuth site
-- used. -- used.
-- --
-- @since 1.6.4 -- @since 1.6.4
emailPreviouslyRegisteredResponse :: MonadAuthHandler site m => Text -> Maybe (m TypedContent) emailPreviouslyRegisteredResponse :: Text -> Maybe (AuthHandler site TypedContent)
emailPreviouslyRegisteredResponse _ = Nothing emailPreviouslyRegisteredResponse _ = Nothing
-- | Additional normalization of email addresses, besides standard canonicalization. -- | Additional normalization of email addresses, besides standard canonicalization.
@ -376,8 +376,8 @@ class ( YesodAuth site
-- Default: 'defaultSetPasswordHandler'. -- Default: 'defaultSetPasswordHandler'.
-- --
-- @since: 1.2.6 -- @since: 1.2.6
setPasswordHandler :: setPasswordHandler
Bool :: Bool
-- ^ Whether the old password is needed. If @True@, a -- ^ Whether the old password is needed. If @True@, a
-- field for the old password should be presented. -- field for the old password should be presented.
-- Otherwise, just two fields for the new password are -- Otherwise, just two fields for the new password are
@ -571,12 +571,12 @@ registerHelper allowUsername forgotPassword dest = do
return $ Just (lid, False, key, identifier) return $ Just (lid, False, key, identifier)
case registerCreds of case registerCreds of
Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier) Nothing -> loginErrorMessageI dest (Msg.IdentifierNotFound identifier)
Just creds@(_, False, _, _) -> sendConfirmationEmail creds Just creds'@(_, False, _, _) -> sendConfirmationEmail creds'
Just creds@(_, True, _, _) -> do Just creds'@(_, True, _, _) -> do
if forgotPassword then sendConfirmationEmail creds if forgotPassword then sendConfirmationEmail creds'
else case emailPreviouslyRegisteredResponse identifier of else case emailPreviouslyRegisteredResponse identifier of
Just response -> response Just response -> response
Nothing -> sendConfirmationEmail creds Nothing -> sendConfirmationEmail creds'
where sendConfirmationEmail (lid, _, verKey, email) = do where sendConfirmationEmail (lid, _, verKey, email) = do
render <- getUrlRender render <- getUrlRender
tp <- getRouteToParent tp <- getRouteToParent
@ -928,9 +928,9 @@ loginLinkKey = "_AUTH_EMAIL_LOGIN_LINK"
-- --
-- @since 1.2.1 -- @since 1.2.1
--setLoginLinkKey :: (MonadHandler m) => AuthId site -> m () --setLoginLinkKey :: (MonadHandler m) => AuthId site -> m ()
setLoginLinkKey :: (MonadHandler m, YesodAuthEmail (HandlerSite m)) setLoginLinkKey :: (HasHandlerData env, YesodAuthEmail (HandlerSite env))
=> AuthId (HandlerSite m) => AuthId (HandlerSite env)
-> m () -> RIO env ()
setLoginLinkKey aid = do setLoginLinkKey aid = do
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
setSession loginLinkKey $ TS.pack $ show (toPathPiece aid, now) setSession loginLinkKey $ TS.pack $ show (toPathPiece aid, now)

View File

@ -1,598 +0,0 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
-- | Use an email address as an identifier via Google's login system.
--
-- Note that this is a replacement for "Yesod.Auth.GoogleEmail", which depends
-- on Google's now deprecated OpenID system. For more information, see
-- <https://developers.google.com/+/api/auth-migration>.
--
-- By using this plugin, you are trusting Google to validate an email address,
-- and requiring users to have a Google account. On the plus side, you get to
-- use email addresses as the identifier, many users have existing Google
-- accounts, the login system has been long tested (as opposed to BrowserID),
-- and it requires no credential managing or setup (as opposed to Email).
--
-- In order to use this plugin:
--
-- * Create an application on the Google Developer Console <https://console.developers.google.com/>
--
-- * Create OAuth credentials. The redirect URI will be <http://yourdomain/auth/page/googleemail2/complete>. (If you have your authentication subsite at a different root than \/auth\/, please adjust accordingly.)
--
-- * Enable the Google+ API.
--
-- @since 1.3.1
module Yesod.Auth.GoogleEmail2
{-# DEPRECATED "Google+ is being shut down, please migrate to Google Sign-in https://pbrisbin.com/posts/googleemail2_deprecation/" #-}
( -- * Authentication handlers
authGoogleEmail
, authGoogleEmailSaveToken
, forwardUrl
-- * User authentication token
, Token(..)
, getUserAccessToken
-- * Person
, getPerson
, Person(..)
, Name(..)
, Gender(..)
, PersonImage(..)
, resizePersonImage
, RelationshipStatus(..)
, PersonURI(..)
, PersonURIType(..)
, Organization(..)
, OrganizationType(..)
, Place(..)
, Email(..)
, EmailType(..)
-- * Other functions
, pid
) where
import Yesod.Auth (Auth, AuthPlugin (AuthPlugin),
AuthRoute, Creds (Creds),
Route (PluginR), YesodAuth,
runHttpRequest, setCredsRedirect,
logoutDest, AuthHandler)
import qualified Yesod.Auth.Message as Msg
import Yesod.Core (HandlerSite, MonadHandler,
TypedContent, getRouteToParent,
getUrlRender, invalidArgs,
liftIO, lookupGetParam,
lookupSession, notFound, redirect,
setSession, whamlet, (.:),
addMessage, getYesod,
toHtml, liftSubHandler)
import Blaze.ByteString.Builder (fromByteString, toByteString)
import Control.Applicative ((<$>), (<*>))
import Control.Arrow (second)
import Control.Monad (unless, when)
import Control.Monad.IO.Class (MonadIO)
import qualified Crypto.Nonce as Nonce
import Data.Aeson ((.:?))
import qualified Data.Aeson as A
#if MIN_VERSION_aeson(1,0,0)
import qualified Data.Aeson.Text as A
#else
import qualified Data.Aeson.Encode as A
#endif
import Data.Aeson.Parser (json')
import Data.Aeson.Types (FromJSON (parseJSON), parseEither,
parseMaybe, withObject, withText)
import Data.Conduit
import Data.Conduit.Attoparsec (sinkParser)
import qualified Data.HashMap.Strict as M
import Data.Maybe (fromMaybe)
import Data.Monoid (mappend)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TL
import Network.HTTP.Client (Manager, requestHeaders,
responseBody, urlEncodedBody)
import qualified Network.HTTP.Client as HTTP
import Network.HTTP.Client.Conduit (Request, bodyReaderSource)
import Network.HTTP.Conduit (http)
import Network.HTTP.Types (renderQueryText)
import System.IO.Unsafe (unsafePerformIO)
-- | Plugin identifier. This is used to identify the plugin used for
-- authentication. The 'credsPlugin' will contain this value when this
-- plugin is used for authentication.
-- @since 1.4.17
pid :: Text
pid = "googleemail2"
forwardUrl :: AuthRoute
forwardUrl = PluginR pid ["forward"]
csrfKey :: Text
csrfKey = "_GOOGLE_CSRF_TOKEN"
getCsrfToken :: MonadHandler m => m (Maybe Text)
getCsrfToken = lookupSession csrfKey
accessTokenKey :: Text
accessTokenKey = "_GOOGLE_ACCESS_TOKEN"
-- | Get user's access token from the session. Returns Nothing if it's not found
-- (probably because the user is not logged in via 'Yesod.Auth.GoogleEmail2'
-- or you are not using 'authGoogleEmailSaveToken')
getUserAccessToken :: MonadHandler m => m (Maybe Token)
getUserAccessToken = fmap (\t -> Token t "Bearer") <$> lookupSession accessTokenKey
getCreateCsrfToken :: MonadHandler m => m Text
getCreateCsrfToken = do
mtoken <- getCsrfToken
case mtoken of
Just token -> return token
Nothing -> do
token <- Nonce.nonce128urlT defaultNonceGen
setSession csrfKey token
return token
authGoogleEmail :: YesodAuth m
=> Text -- ^ client ID
-> Text -- ^ client secret
-> AuthPlugin m
authGoogleEmail = authPlugin False
-- | An alternative version which stores user access token in the session
-- variable. Use it if you want to request user's profile from your app.
--
-- @since 1.4.3
authGoogleEmailSaveToken :: YesodAuth m
=> Text -- ^ client ID
-> Text -- ^ client secret
-> AuthPlugin m
authGoogleEmailSaveToken = authPlugin True
authPlugin :: YesodAuth m
=> Bool -- ^ if the token should be stored
-> Text -- ^ client ID
-> Text -- ^ client secret
-> AuthPlugin m
authPlugin storeToken clientID clientSecret =
AuthPlugin pid dispatch login
where
complete = PluginR pid ["complete"]
getDest :: MonadHandler m
=> (Route Auth -> Route (HandlerSite m))
-> m Text
getDest tm = do
csrf <- getCreateCsrfToken
render <- getUrlRender
let qs = map (second Just)
[ ("scope", "email profile")
, ("state", csrf)
, ("redirect_uri", render $ tm complete)
, ("response_type", "code")
, ("client_id", clientID)
, ("access_type", "offline")
]
return $ decodeUtf8
$ toByteString
$ fromByteString "https://accounts.google.com/o/oauth2/auth"
`Data.Monoid.mappend` renderQueryText True qs
login tm = do
[whamlet|<a href=@{tm forwardUrl}>_{Msg.LoginGoogle}|]
dispatch :: YesodAuth site
=> Text
-> [Text]
-> AuthHandler site TypedContent
dispatch "GET" ["forward"] = do
tm <- getRouteToParent
getDest tm >>= redirect
dispatch "GET" ["complete"] = do
mstate <- lookupGetParam "state"
case mstate of
Nothing -> invalidArgs ["CSRF state from Google is missing"]
Just state -> do
mtoken <- getCsrfToken
unless (Just state == mtoken) $ invalidArgs ["Invalid CSRF token from Google"]
mcode <- lookupGetParam "code"
code <-
case mcode of
Nothing -> do
merr <- lookupGetParam "error"
case merr of
Nothing -> invalidArgs ["Missing code paramter"]
Just err -> do
master <- getYesod
let msg =
case err of
"access_denied" -> "Access denied"
_ -> "Unknown error occurred: " `T.append` err
addMessage "error" $ toHtml msg
redirect $ logoutDest master
Just c -> return c
render <- getUrlRender
tm <- getRouteToParent
req' <- liftIO $
HTTP.parseUrlThrow
"https://accounts.google.com/o/oauth2/token" -- FIXME don't hardcode, use: https://accounts.google.com/.well-known/openid-configuration
let req =
urlEncodedBody
[ ("code", encodeUtf8 code)
, ("client_id", encodeUtf8 clientID)
, ("client_secret", encodeUtf8 clientSecret)
, ("redirect_uri", encodeUtf8 $ render $ tm complete)
, ("grant_type", "authorization_code")
]
req'
{ requestHeaders = []
}
value <- makeHttpRequest req
token@(Token accessToken' tokenType') <-
case parseEither parseJSON value of
Left e -> error e
Right t -> return t
unless (tokenType' == "Bearer") $ error $ "Unknown token type: " ++ show tokenType'
-- User's access token is saved for further access to API
when storeToken $ setSession accessTokenKey accessToken'
personValue <- makeHttpRequest =<< personValueRequest token
person <- case parseEither parseJSON personValue of
Left e -> error e
Right x -> return x
email <-
case map emailValue $ filter (\e -> emailType e == EmailAccount) $ personEmails person of
[e] -> return e
[] -> error "No account email"
x -> error $ "Too many account emails: " ++ show x
setCredsRedirect $ Creds pid email $ allPersonInfo personValue
dispatch _ _ = notFound
makeHttpRequest :: Request -> AuthHandler site A.Value
makeHttpRequest req =
liftSubHandler $ runHttpRequest req $ \res ->
runConduit $ bodyReaderSource (responseBody res) .| sinkParser json'
-- | Allows to fetch information about a user from Google's API.
-- In case of parsing error returns 'Nothing'.
-- Will throw 'HttpException' in case of network problems or error response code.
--
-- @since 1.4.3
getPerson :: MonadHandler m => Manager -> Token -> m (Maybe Person)
getPerson manager token = liftSubHandler $ parseMaybe parseJSON <$> (do
req <- personValueRequest token
res <- http req manager
runConduit $ responseBody res .| sinkParser json'
)
personValueRequest :: MonadIO m => Token -> m Request
personValueRequest token = do
req2' <- liftIO
$ HTTP.parseUrlThrow "https://www.googleapis.com/plus/v1/people/me"
return req2'
{ requestHeaders =
[ ("Authorization", encodeUtf8 $ "Bearer " `mappend` accessToken token)
]
}
--------------------------------------------------------------------------------
-- | An authentication token which was acquired from OAuth callback.
-- The token gets saved into the session storage only if you use
-- 'authGoogleEmailSaveToken'.
-- You can acquire saved token with 'getUserAccessToken'.
--
-- @since 1.4.3
data Token = Token { accessToken :: Text
, tokenType :: Text
} deriving (Show, Eq)
instance FromJSON Token where
parseJSON = withObject "Tokens" $ \o -> Token
Control.Applicative.<$> o .: "access_token"
Control.Applicative.<*> o .: "token_type"
--------------------------------------------------------------------------------
-- | Gender of the person
--
-- @since 1.4.3
data Gender = Male | Female | OtherGender deriving (Show, Eq)
instance FromJSON Gender where
parseJSON = withText "Gender" $ \t -> return $ case t of
"male" -> Male
"female" -> Female
_ -> OtherGender
--------------------------------------------------------------------------------
-- | URIs specified in the person's profile
--
-- @since 1.4.3
data PersonURI =
PersonURI { uriLabel :: Maybe Text
, uriValue :: Maybe Text
, uriType :: Maybe PersonURIType
} deriving (Show, Eq)
instance FromJSON PersonURI where
parseJSON = withObject "PersonURI" $ \o -> PersonURI <$> o .:? "label"
<*> o .:? "value"
<*> o .:? "type"
--------------------------------------------------------------------------------
-- | The type of URI
--
-- @since 1.4.3
data PersonURIType = OtherProfile -- ^ URI for another profile
| Contributor -- ^ URI to a site for which this person is a contributor
| Website -- ^ URI for this Google+ Page's primary website
| OtherURI -- ^ Other URL
| PersonURIType Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON PersonURIType where
parseJSON = withText "PersonURIType" $ \t -> return $ case t of
"otherProfile" -> OtherProfile
"contributor" -> Contributor
"website" -> Website
"other" -> OtherURI
_ -> PersonURIType t
--------------------------------------------------------------------------------
-- | Current or past organizations with which this person is associated
--
-- @since 1.4.3
data Organization =
Organization { orgName :: Maybe Text
-- ^ The person's job title or role within the organization
, orgTitle :: Maybe Text
, orgType :: Maybe OrganizationType
-- ^ The date that the person joined this organization.
, orgStartDate :: Maybe Text
-- ^ The date that the person left this organization.
, orgEndDate :: Maybe Text
-- ^ If @True@, indicates this organization is the person's
-- ^ primary one, which is typically interpreted as the current one.
, orgPrimary :: Maybe Bool
} deriving (Show, Eq)
instance FromJSON Organization where
parseJSON = withObject "Organization" $ \o ->
Organization <$> o .:? "name"
<*> o .:? "title"
<*> o .:? "type"
<*> o .:? "startDate"
<*> o .:? "endDate"
<*> o .:? "primary"
--------------------------------------------------------------------------------
-- | The type of an organization
--
-- @since 1.4.3
data OrganizationType = Work
| School
| OrganizationType Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON OrganizationType where
parseJSON = withText "OrganizationType" $ \t -> return $ case t of
"work" -> Work
"school" -> School
_ -> OrganizationType t
--------------------------------------------------------------------------------
-- | A place where the person has lived or is living at the moment.
--
-- @since 1.4.3
data Place =
Place { -- | A place where this person has lived. For example: "Seattle, WA", "Near Toronto".
placeValue :: Maybe Text
-- | If @True@, this place of residence is this person's primary residence.
, placePrimary :: Maybe Bool
} deriving (Show, Eq)
instance FromJSON Place where
parseJSON = withObject "Place" $ \o -> Place <$> (o .:? "value") <*> (o .:? "primary")
--------------------------------------------------------------------------------
-- | Individual components of a name
--
-- @since 1.4.3
data Name =
Name { -- | The full name of this person, including middle names, suffixes, etc
nameFormatted :: Maybe Text
-- | The family name (last name) of this person
, nameFamily :: Maybe Text
-- | The given name (first name) of this person
, nameGiven :: Maybe Text
-- | The middle name of this person.
, nameMiddle :: Maybe Text
-- | The honorific prefixes (such as "Dr." or "Mrs.") for this person
, nameHonorificPrefix :: Maybe Text
-- | The honorific suffixes (such as "Jr.") for this person
, nameHonorificSuffix :: Maybe Text
} deriving (Show, Eq)
instance FromJSON Name where
parseJSON = withObject "Name" $ \o -> Name <$> o .:? "formatted"
<*> o .:? "familyName"
<*> o .:? "givenName"
<*> o .:? "middleName"
<*> o .:? "honorificPrefix"
<*> o .:? "honorificSuffix"
--------------------------------------------------------------------------------
-- | The person's relationship status.
--
-- @since 1.4.3
data RelationshipStatus = Single -- ^ Person is single
| InRelationship -- ^ Person is in a relationship
| Engaged -- ^ Person is engaged
| Married -- ^ Person is married
| Complicated -- ^ The relationship is complicated
| OpenRelationship -- ^ Person is in an open relationship
| Widowed -- ^ Person is widowed
| DomesticPartnership -- ^ Person is in a domestic partnership
| CivilUnion -- ^ Person is in a civil union
| RelationshipStatus Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON RelationshipStatus where
parseJSON = withText "RelationshipStatus" $ \t -> return $ case t of
"single" -> Single
"in_a_relationship" -> InRelationship
"engaged" -> Engaged
"married" -> Married
"its_complicated" -> Complicated
"open_relationship" -> OpenRelationship
"widowed" -> Widowed
"in_domestic_partnership" -> DomesticPartnership
"in_civil_union" -> CivilUnion
_ -> RelationshipStatus t
--------------------------------------------------------------------------------
-- | The URI of the person's profile photo.
--
-- @since 1.4.3
newtype PersonImage = PersonImage { imageUri :: Text } deriving (Show, Eq)
instance FromJSON PersonImage where
parseJSON = withObject "PersonImage" $ \o -> PersonImage <$> o .: "url"
-- | @resizePersonImage img 30@ would set query part to @?sz=30@ which would resize
-- the image under the URI. If for some reason you need to modify the query
-- part, you should do it after resizing.
--
-- @since 1.4.3
resizePersonImage :: PersonImage -> Int -> PersonImage
resizePersonImage (PersonImage uri) size =
PersonImage $ uri `mappend` "?sz=" `mappend` T.pack (show size)
--------------------------------------------------------------------------------
-- | Information about the user
-- Full description of the resource https://developers.google.com/+/api/latest/people
--
-- @since 1.4.3
data Person = Person
{ personId :: Text
-- | The name of this person, which is suitable for display
, personDisplayName :: Maybe Text
, personName :: Maybe Name
, personNickname :: Maybe Text
, personBirthday :: Maybe Text -- ^ Birthday formatted as YYYY-MM-DD
, personGender :: Maybe Gender
, personProfileUri :: Maybe Text -- ^ The URI of this person's profile
, personImage :: Maybe PersonImage
, personAboutMe :: Maybe Text -- ^ A short biography for this person
, personRelationshipStatus :: Maybe RelationshipStatus
, personUris :: [PersonURI]
, personOrganizations :: [Organization]
, personPlacesLived :: [Place]
-- | The brief description of this person
, personTagline :: Maybe Text
-- | Whether this user has signed up for Google+
, personIsPlusUser :: Maybe Bool
-- | The "bragging rights" line of this person
, personBraggingRights :: Maybe Text
-- | if a Google+ page, the number of people who have +1'd this page
, personPlusOneCount :: Maybe Int
-- | For followers who are visible, the number of people who have added
-- this person or page to a circle.
, personCircledByCount :: Maybe Int
-- | Whether the person or Google+ Page has been verified. This is used only
-- for pages with a higher risk of being impersonated or similar. This
-- flag will not be present on most profiles.
, personVerified :: Maybe Bool
-- | The user's preferred language for rendering.
, personLanguage :: Maybe Text
, personEmails :: [Email]
, personDomain :: Maybe Text
, personOccupation :: Maybe Text -- ^ The occupation of this person
, personSkills :: Maybe Text -- ^ The person's skills
} deriving (Show, Eq)
instance FromJSON Person where
parseJSON = withObject "Person" $ \o ->
Person <$> o .: "id"
<*> o .: "displayName"
<*> o .:? "name"
<*> o .:? "nickname"
<*> o .:? "birthday"
<*> o .:? "gender"
<*> (o .:? "url")
<*> o .:? "image"
<*> o .:? "aboutMe"
<*> o .:? "relationshipStatus"
<*> ((fromMaybe []) <$> (o .:? "urls"))
<*> ((fromMaybe []) <$> (o .:? "organizations"))
<*> ((fromMaybe []) <$> (o .:? "placesLived"))
<*> o .:? "tagline"
<*> o .:? "isPlusUser"
<*> o .:? "braggingRights"
<*> o .:? "plusOneCount"
<*> o .:? "circledByCount"
<*> o .:? "verified"
<*> o .:? "language"
<*> ((fromMaybe []) <$> (o .:? "emails"))
<*> o .:? "domain"
<*> o .:? "occupation"
<*> o .:? "skills"
--------------------------------------------------------------------------------
-- | Person's email
--
-- @since 1.4.3
data Email = Email
{ emailValue :: Text
, emailType :: EmailType
}
deriving (Show, Eq)
instance FromJSON Email where
parseJSON = withObject "Email" $ \o -> Email
<$> o .: "value"
<*> o .: "type"
--------------------------------------------------------------------------------
-- | Type of email
--
-- @since 1.4.3
data EmailType = EmailAccount -- ^ Google account email address
| EmailHome -- ^ Home email address
| EmailWork -- ^ Work email adress
| EmailOther -- ^ Other email address
| EmailType Text -- ^ Something else
deriving (Show, Eq)
instance FromJSON EmailType where
parseJSON = withText "EmailType" $ \t -> return $ case t of
"account" -> EmailAccount
"home" -> EmailHome
"work" -> EmailWork
"other" -> EmailOther
_ -> EmailType t
allPersonInfo :: A.Value -> [(Text, Text)]
allPersonInfo (A.Object o) = map enc $ M.toList o
where enc (key, A.String s) = (key, s)
enc (key, v) = (key, TL.toStrict $ TL.toLazyText $ A.encodeToTextBuilder v)
allPersonInfo _ = []
-- See https://github.com/yesodweb/yesod/issues/1245 for discussion on this
-- use of unsafePerformIO.
defaultNonceGen :: Nonce.Generator
defaultNonceGen = unsafePerformIO (Nonce.new)
{-# NOINLINE defaultNonceGen #-}

View File

@ -4,6 +4,7 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Yesod.Auth.OpenId module Yesod.Auth.OpenId
( authOpenId ( authOpenId
, forwardUrl , forwardUrl
@ -29,7 +30,7 @@ forwardUrl = PluginR "openid" ["forward"]
data IdentifierType = Claimed | OPLocal data IdentifierType = Claimed | OPLocal
authOpenId :: YesodAuth master authOpenId :: forall master. YesodAuth master
=> IdentifierType => IdentifierType
-> [(Text, Text)] -- ^ extension fields -> [(Text, Text)] -- ^ extension fields
-> AuthPlugin master -> AuthPlugin master
@ -41,16 +42,15 @@ authOpenId idType extensionFields =
name :: Text name :: Text
name = "openid_identifier" name = "openid_identifier"
login
:: (AuthRoute -> Route master)
-> WidgetFor master ()
login tm = do login tm = do
ident <- newIdent ident <- newIdent
-- FIXME this is a hack to get GHC 7.6's type checker to allow the toWidget [cassius|##{ident}
-- code, but it shouldn't be necessary
let y :: a -> [(Text, Text)] -> Text
y = undefined
toWidget (\x -> [cassius|##{ident}
background: #fff url(http://www.myopenid.com/static/openid-icon-small.gif) no-repeat scroll 0pt 50%; background: #fff url(http://www.myopenid.com/static/openid-icon-small.gif) no-repeat scroll 0pt 50%;
padding-left: 18px; padding-left: 18px;
|] $ x `asTypeOf` y) |]
[whamlet| [whamlet|
$newline never $newline never
<form method="get" action="@{tm forwardUrl}"> <form method="get" action="@{tm forwardUrl}">
@ -62,7 +62,10 @@ $newline never
<input type="submit" value="_{Msg.LoginOpenID}"> <input type="submit" value="_{Msg.LoginOpenID}">
|] |]
dispatch :: Text -> [Text] -> AuthHandler master TypedContent dispatch
:: Text
-> [Text]
-> SubHandlerFor Auth master TypedContent
dispatch "GET" ["forward"] = do dispatch "GET" ["forward"] = do
roid <- runInputGet $ iopt textField name roid <- runInputGet $ iopt textField name
case roid of case roid of
@ -86,7 +89,11 @@ $newline never
completeHelper idType posts completeHelper idType posts
dispatch _ _ = notFound dispatch _ _ = notFound
completeHelper :: IdentifierType -> [(Text, Text)] -> AuthHandler master TypedContent completeHelper
:: (HasHandlerData env, SubHandlerSite env ~ Auth, YesodAuth (HandlerSite env))
=> IdentifierType
-> [(Text, Text)]
-> RIO env TypedContent
completeHelper idType gets' = do completeHelper idType gets' = do
manager <- authHttpManager manager <- authHttpManager
eres <- tryAny $ OpenId.authenticateClaimed gets' manager eres <- tryAny $ OpenId.authenticateClaimed gets' manager

View File

@ -3,6 +3,7 @@
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Yesod.Auth.Rpxnow module Yesod.Auth.Rpxnow
( authRpxnow ( authRpxnow
) where ) where
@ -18,7 +19,7 @@ import Data.Text.Encoding.Error (lenientDecode)
import Control.Arrow ((***)) import Control.Arrow ((***))
import Network.HTTP.Types (renderQuery) import Network.HTTP.Types (renderQuery)
authRpxnow :: YesodAuth master authRpxnow :: forall master. YesodAuth master
=> String -- ^ app name => String -- ^ app name
-> String -- ^ key -> String -- ^ key
-> AuthPlugin master -> AuthPlugin master

View File

@ -45,6 +45,7 @@ library
, nonce >= 1.0.2 && < 1.1 , nonce >= 1.0.2 && < 1.1
, persistent >= 2.8 && < 2.10 , persistent >= 2.8 && < 2.10
, random >= 1.0.0.2 , random >= 1.0.0.2
, rio
, safe , safe
, shakespeare , shakespeare
, template-haskell , template-haskell
@ -63,13 +64,11 @@ library
build-depends: network-uri >= 2.6 build-depends: network-uri >= 2.6
exposed-modules: Yesod.Auth exposed-modules: Yesod.Auth
Yesod.Auth.BrowserId
Yesod.Auth.Dummy Yesod.Auth.Dummy
Yesod.Auth.Email Yesod.Auth.Email
Yesod.Auth.OpenId Yesod.Auth.OpenId
Yesod.Auth.Rpxnow Yesod.Auth.Rpxnow
Yesod.Auth.Message Yesod.Auth.Message
Yesod.Auth.GoogleEmail2
Yesod.Auth.Hardcoded Yesod.Auth.Hardcoded
Yesod.Auth.Util.PasswordStore Yesod.Auth.Util.PasswordStore
other-modules: Yesod.Auth.Routes other-modules: Yesod.Auth.Routes

View File

@ -1080,7 +1080,7 @@ setUrl url' = do
site <- fmap rbdSite getSIO site <- fmap rbdSite getSIO
eurl <- Yesod.Core.Unsafe.runFakeHandler eurl <- Yesod.Core.Unsafe.runFakeHandler
M.empty M.empty
(const $ error "Yesod.Test: No logger available") mempty
site site
(toTextUrl url') (toTextUrl url')
url <- either (error . show) return eurl url <- either (error . show) return eurl