Major Auth refactoring

This commit is contained in:
Michael Snoyman 2010-05-10 20:29:51 +03:00
parent 9b03a86353
commit ad7a3330d5
2 changed files with 100 additions and 81 deletions

View File

@ -20,66 +20,100 @@
-- --
--------------------------------------------------------- ---------------------------------------------------------
module Yesod.Helpers.Auth module Yesod.Helpers.Auth
( maybeIdentifier ( redirectLogin
, authIdentifier
, displayName
, redirectLogin
, Auth (..) , Auth (..)
, AuthRoutes (..) , AuthRoutes (..)
, siteAuth , siteAuth
, YesodAuth (..) , YesodAuth (..)
, identKey , identKey
, displayNameKey , displayNameKey
, Creds (..)
, maybeCreds
, requireCreds
) where ) where
import Web.Encodings
import qualified Web.Authenticate.Rpxnow as Rpxnow import qualified Web.Authenticate.Rpxnow as Rpxnow
import qualified Web.Authenticate.OpenId as OpenId import qualified Web.Authenticate.OpenId as OpenId
import Yesod import Yesod
import Data.Convertible.Text
import Control.Monad.Attempt import Control.Monad.Attempt
import Data.Maybe import Data.Maybe
import Control.Applicative import Control.Monad
import Data.Typeable (Typeable)
import Control.Exception (Exception)
class Yesod master => YesodAuth master where class Yesod master => YesodAuth master where
-- | Default destination on successful login or logout, if no other
-- destination exists.
defaultDest :: master -> Routes master defaultDest :: master -> Routes master
-- | Default page to redirect user to for logging in.
defaultLoginRoute :: master -> Routes master defaultLoginRoute :: master -> Routes master
onRpxnowLogin :: Rpxnow.Identifier -> GHandler Auth master () -- | Callback for a successful login.
onRpxnowLogin _ = return () --
-- The second parameter can contain various information, depending on login
-- mechanism.
onLogin :: Creds -> [(String, String)] -> GHandler Auth master ()
onLogin _ _ = return ()
data Auth = Auth data Auth = Auth
{ authIsOpenIdEnabled :: Bool { authIsOpenIdEnabled :: Bool
, authRpxnowApiKey :: Maybe String , authRpxnowApiKey :: Maybe String
} }
$(mkYesodSub "Auth" [''YesodAuth] [$parseRoutes| data AuthType = AuthOpenId | AuthRpxnow
deriving (Show, Read, Eq)
-- | User credentials
data Creds = Creds
{ credsIdent :: String -- ^ Identifier. Exact meaning depends on 'credsAuthType'.
, credsAuthType :: AuthType -- ^ How the user was authenticated
, credsEmail :: Maybe String -- ^ Verified e-mail address.
, credsDisplayName :: Maybe String -- ^ Display name.
}
deriving (Show, Read, Eq)
credsKey :: String
credsKey = "_CREDS"
setCreds :: YesodAuth master
=> Creds -> [(String, String)] -> GHandler Auth master ()
setCreds creds extra = do
setSession credsKey $ show creds
onLogin creds extra
maybeCreds :: GHandler sub master (Maybe Creds)
maybeCreds = do
mcs <- lookupSession credsKey
return $ mcs >>= readMay
where
readMay x = case reads x of
(y, _):_ -> Just y
_ -> Nothing
mkYesodSub "Auth" [''YesodAuth] [$parseRoutes|
/check Check GET /check Check GET
/logout Logout GET /logout Logout GET
/openid OpenIdR GET /openid OpenIdR GET
/openid/forward OpenIdForward GET /openid/forward OpenIdForward GET
/openid/complete OpenIdComplete GET /openid/complete OpenIdComplete GET
/login/rpxnow RpxnowR /login/rpxnow RpxnowR
|]) |]
data ExpectedSingleParam = ExpectedSingleParam testOpenId :: GHandler Auth master ()
deriving (Show, Typeable) testOpenId = do
instance Exception ExpectedSingleParam a <- getYesodSub
unless (authIsOpenIdEnabled a) notFound
getOpenIdR :: Yesod master => GHandler Auth master RepHtml getOpenIdR :: Yesod master => GHandler Auth master RepHtml
getOpenIdR = do getOpenIdR = do
testOpenId
rr <- getRequest rr <- getRequest
case getParams rr "dest" of case getParams rr "dest" of
[] -> return () [] -> return ()
(x:_) -> setUltDestString x (x:_) -> setUltDestString x
rtom <- getRouteToMaster rtom <- getRouteToMaster
let message = cs <$> (listToMaybe $ getParams rr "message") message <- getMessage
applyLayout "Log in via OpenID" (return ()) [$hamlet| applyLayout "Log in via OpenID" (return ()) [$hamlet|
$maybe message msg $maybe message msg
%p.message $msg$ %p.message $msg$
@ -91,33 +125,35 @@ $maybe message msg
getOpenIdForward :: GHandler Auth master () getOpenIdForward :: GHandler Auth master ()
getOpenIdForward = do getOpenIdForward = do
testOpenId
rr <- getRequest rr <- getRequest
oid <- case getParams rr "openid" of oid <- case getParams rr "openid" of
[x] -> return x [x] -> return x
_ -> invalidArgs [("openid", show ExpectedSingleParam)] _ -> invalidArgs [("openid", "Expected single parameter")]
render <- getUrlRender render <- getUrlRender
toMaster <- getRouteToMaster toMaster <- getRouteToMaster
let complete = render $ toMaster OpenIdComplete let complete = render $ toMaster OpenIdComplete
res <- runAttemptT $ OpenId.getForwardUrl oid complete res <- runAttemptT $ OpenId.getForwardUrl oid complete
attempt attempt
(\err -> redirectParams RedirectTemporary (toMaster OpenIdR) (\err -> do
[("message", show err)]) setMessage $ cs $ show err
redirect RedirectTemporary $ toMaster OpenIdR)
(redirectString RedirectTemporary) (redirectString RedirectTemporary)
res res
getOpenIdComplete :: YesodAuth master => GHandler Auth master () getOpenIdComplete :: YesodAuth master => GHandler Auth master ()
getOpenIdComplete = do getOpenIdComplete = do
testOpenId
rr <- getRequest rr <- getRequest
let gets' = reqGetParams rr let gets' = reqGetParams rr
res <- runAttemptT $ OpenId.authenticate gets' res <- runAttemptT $ OpenId.authenticate gets'
renderm <- getUrlRender
toMaster <- getRouteToMaster toMaster <- getRouteToMaster
let render = renderm . toMaster let onFailure err = do
let errurl err = render OpenIdR ++ "?message=" ++ encodeUrl (show err) setMessage $ cs $ show err
let onFailure err = redirectString RedirectTemporary $ errurl err redirect RedirectTemporary $ toMaster OpenIdR
let onSuccess (OpenId.Identifier ident) = do let onSuccess (OpenId.Identifier ident) = do
y <- getYesod y <- getYesod
setSession identKey ident setCreds (Creds ident AuthOpenId Nothing Nothing) []
redirectUltDest RedirectTemporary $ defaultDest y redirectUltDest RedirectTemporary $ defaultDest y
attempt onFailure onSuccess res attempt onFailure onSuccess res
@ -131,12 +167,15 @@ handleRpxnowR = do
rr <- getRequest rr <- getRequest
pp <- postParams rr pp <- postParams rr
let token = case getParams rr "token" ++ pp "token" of let token = case getParams rr "token" ++ pp "token" of
[] -> failure MissingToken [] -> invalidArgs [("token", "Value not supplied")]
(x:_) -> x (x:_) -> x
ident <- liftIO $ Rpxnow.authenticate apiKey token Rpxnow.Identifier ident extra <- liftIO $ Rpxnow.authenticate apiKey token
onRpxnowLogin ident let creds = Creds
setSession identKey $ Rpxnow.identifier ident ident
setSession displayNameKey $ getDisplayName ident AuthRpxnow
(lookup "verifiedEmail" extra)
(getDisplayName extra)
setCreds creds extra
either (redirect RedirectTemporary) (redirectString RedirectTemporary) $ either (redirect RedirectTemporary) (redirectString RedirectTemporary) $
case pp "dest" of case pp "dest" of
(d:_) -> Right d (d:_) -> Right d
@ -146,61 +185,39 @@ handleRpxnowR = do
(('#':rest):_) -> Right rest (('#':rest):_) -> Right rest
(s:_) -> Right s (s:_) -> Right s
-- | Get some form of a display name.
data MissingToken = MissingToken getDisplayName :: [(String, String)] -> Maybe String
deriving (Show, Typeable) getDisplayName extra = helper choices where
instance Exception MissingToken
-- | Get some form of a display name, defaulting to the identifier.
getDisplayName :: Rpxnow.Identifier -> String
getDisplayName (Rpxnow.Identifier ident extra) = helper choices where
choices = ["verifiedEmail", "email", "displayName", "preferredUsername"] choices = ["verifiedEmail", "email", "displayName", "preferredUsername"]
helper [] = ident helper [] = Nothing
helper (x:xs) = fromMaybe (helper xs) $ lookup x extra helper (x:xs) = maybe (helper xs) Just $ lookup x extra
getCheck :: Yesod master => GHandler Auth master RepHtmlJson getCheck :: Yesod master => GHandler Auth master RepHtmlJson
getCheck = do getCheck = do
ident <- maybeIdentifier creds <- maybeCreds
dn <- displayName applyLayoutJson "Authentication Status"
let arg = (cs $ fromMaybe "" ident, cs $ fromMaybe "" dn) (return ()) (html creds) (json creds)
applyLayoutJson "Authentication Status" (return ()) arg html json
where where
html (x, y) = [$hamlet| html creds = [$hamlet|
%h1 Authentication Status %h1 Authentication Status
%dl $if isNothing.creds
%dt identifier %p Not logged in
%dd $x$ $maybe creds c
%dt displayName %p Logged in as $cs.credsIdent.c$
%dd $y$
|] |]
json (ident, dn) = json creds =
jsonMap [ ("ident", jsonScalar ident) jsonMap
, ("displayName", jsonScalar dn) [ ("ident", jsonScalar $ maybe (cs "") (cs . credsIdent) creds)
] , ("displayName", jsonScalar $ cs $ fromMaybe ""
$ creds >>= credsDisplayName)
]
getLogout :: YesodAuth master => GHandler Auth master () getLogout :: YesodAuth master => GHandler Auth master ()
getLogout = do getLogout = do
y <- getYesod y <- getYesod
clearSession identKey clearSession credsKey
redirectUltDest RedirectTemporary $ defaultDest y redirectUltDest RedirectTemporary $ defaultDest y
-- | Gets the identifier for a user if available.
maybeIdentifier :: RequestReader m => m (Maybe String)
maybeIdentifier = do
s <- session
return $ listToMaybe $ s identKey
-- | Gets the display name for a user if available.
displayName :: RequestReader m => m (Maybe String)
displayName = do
s <- session
return $ listToMaybe $ s displayNameKey
-- | Gets the identifier for a user. If user is not logged in, redirects them
-- to the login page.
authIdentifier :: YesodAuth master => GHandler sub master String
authIdentifier = maybeIdentifier >>= maybe redirectLogin return
-- | Redirect the user to the 'defaultLoginPath', setting the DEST cookie -- | Redirect the user to the 'defaultLoginPath', setting the DEST cookie
-- appropriately. -- appropriately.
redirectLogin :: YesodAuth master => GHandler sub master a redirectLogin :: YesodAuth master => GHandler sub master a
@ -209,6 +226,9 @@ redirectLogin = do
setUltDest' setUltDest'
redirect RedirectTemporary $ defaultLoginRoute y redirect RedirectTemporary $ defaultLoginRoute y
requireCreds :: YesodAuth master => GHandler sub master Creds
requireCreds = maybeCreds >>= maybe redirectLogin return
identKey :: String identKey :: String
identKey = "IDENTIFIER" identKey = "IDENTIFIER"

View File

@ -100,18 +100,17 @@ applyLayout t h b =
applyLayoutJson :: Yesod master applyLayoutJson :: Yesod master
=> String -- ^ title => String -- ^ title
-> Hamlet (Routes master) IO () -- ^ head -> Hamlet (Routes master) IO () -- ^ head
-> x -> Hamlet (Routes master) IO () -- ^ body
-> (x -> Hamlet (Routes master) IO ()) -> Json (Routes master) ()
-> (x -> Json (Routes master) ())
-> GHandler sub master RepHtmlJson -> GHandler sub master RepHtmlJson
applyLayoutJson t h x toH toJ = do applyLayoutJson t h html json = do
html <- defaultLayout PageContent html' <- defaultLayout PageContent
{ pageTitle = cs t { pageTitle = cs t
, pageHead = h , pageHead = h
, pageBody = toH x , pageBody = html
} }
json <- jsonToContent $ toJ x json' <- jsonToContent json
return $ RepHtmlJson html json return $ RepHtmlJson html' json'
applyLayout' :: Yesod master applyLayout' :: Yesod master
=> String -- ^ title => String -- ^ title