Converted auth helper to subsite

This commit is contained in:
Michael Snoyman 2010-04-16 14:28:59 -07:00
parent e9a8b43595
commit 3165b253ba
5 changed files with 131 additions and 118 deletions

View File

@ -45,6 +45,12 @@ hamletToRepHtml h = do
c <- hamletToContent h c <- hamletToContent h
return $ RepHtml c return $ RepHtml c
-- FIXME some type of JSON combined output...
--hamletToRepHtmlJson :: x
-- -> (x -> Hamlet (Routes y) IO ())
-- -> (x -> Json)
-- -> Handler y RepHtmlJson
instance Monad m => ConvertSuccess String (Hamlet url m ()) where instance Monad m => ConvertSuccess String (Hamlet url m ()) where
convertSuccess = outputHtml . Unencoded . cs convertSuccess = outputHtml . Unencoded . cs
instance Monad m instance Monad m

View File

@ -23,7 +23,9 @@ module Yesod.Handler
Handler Handler
, getYesod , getYesod
, getUrlRender , getUrlRender
, getRoute
, runHandler , runHandler
, runHandler'
, liftIO , liftIO
, YesodApp (..) , YesodApp (..)
, Routes , Routes
@ -59,7 +61,12 @@ import Data.Convertible.Text (cs)
type family Routes y type family Routes y
data HandlerData yesod = HandlerData Request yesod (Routes yesod -> String) data HandlerData yesod = HandlerData
{ handlerRequest :: Request
, handlerYesod :: yesod
, handlerRoute :: Maybe (Routes yesod)
, handlerRender :: (Routes yesod -> String)
}
newtype YesodApp = YesodApp newtype YesodApp = YesodApp
{ unYesodApp { unYesodApp
@ -100,22 +107,37 @@ instance MonadIO (Handler yesod) where
instance Failure ErrorResponse (Handler yesod) where instance Failure ErrorResponse (Handler yesod) where
failure e = Handler $ \_ -> return ([], HCError e) failure e = Handler $ \_ -> return ([], HCError e)
instance RequestReader (Handler yesod) where instance RequestReader (Handler yesod) where
getRequest = Handler $ \(HandlerData rr _ _) getRequest = Handler $ \r -> return ([], HCContent $ handlerRequest r)
-> return ([], HCContent rr)
getYesod :: Handler yesod yesod getYesod :: Handler yesod yesod
getYesod = Handler $ \(HandlerData _ yesod _) -> return ([], HCContent yesod) getYesod = Handler $ \r -> return ([], HCContent $ handlerYesod r)
getUrlRender :: Handler yesod (Routes yesod -> String) getUrlRender :: Handler yesod (Routes yesod -> String)
getUrlRender = Handler $ \(HandlerData _ _ r) -> return ([], HCContent r) getUrlRender = Handler $ \r -> return ([], HCContent $ handlerRender r)
runHandler :: HasReps c => Handler yesod c -> yesod -> (Routes yesod -> String) -> YesodApp getRoute :: Handler yesod (Maybe (Routes yesod))
runHandler handler y render = YesodApp $ \eh rr cts -> do getRoute = Handler $ \r -> return ([], HCContent $ handlerRoute r)
runHandler' :: HasReps c
=> Handler yesod c
-> yesod
-> Routes yesod
-> (Routes yesod -> String)
-> YesodApp
runHandler' handler y route render = runHandler handler y (Just route) render
runHandler :: HasReps c
=> Handler yesod c
-> yesod
-> Maybe (Routes yesod)
-> (Routes yesod -> String)
-> YesodApp
runHandler handler y route render = YesodApp $ \eh rr cts -> do
let toErrorHandler = let toErrorHandler =
InternalError InternalError
. (show :: Control.Exception.SomeException -> String) . (show :: Control.Exception.SomeException -> String)
(headers, contents) <- Control.Exception.catch (headers, contents) <- Control.Exception.catch
(unHandler handler $ HandlerData rr y render) (unHandler handler $ HandlerData rr y route render)
(\e -> return ([], HCError $ toErrorHandler e)) (\e -> return ([], HCError $ toErrorHandler e))
let handleError e = do let handleError e = do
Response _ hs ct c <- unYesodApp (eh e) safeEh rr cts Response _ hs ct c <- unYesodApp (eh e) safeEh rr cts

View File

@ -2,6 +2,9 @@
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ExistentialQuantification #-}
--------------------------------------------------------- ---------------------------------------------------------
-- --
-- Module : Yesod.Helpers.Auth -- Module : Yesod.Helpers.Auth
@ -16,16 +19,14 @@
-- --
--------------------------------------------------------- ---------------------------------------------------------
module Yesod.Helpers.Auth module Yesod.Helpers.Auth
( authHandler ( maybeIdentifier
, YesodAuth (..)
, maybeIdentifier
, authIdentifier , authIdentifier
, displayName , displayName
, redirectLogin , redirectLogin
, Auth (..)
, siteAuthRoutes
) where ) where
-- FIXME write as subsite
import Web.Encodings 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
@ -35,6 +36,7 @@ import Data.Convertible.Text
import Control.Monad.Attempt import Control.Monad.Attempt
import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Char8 as B8
import Data.Maybe
import qualified Network.Wai as W import qualified Network.Wai as W
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
@ -43,79 +45,49 @@ import Control.Applicative ((<$>))
-- FIXME check referer header to determine destination -- FIXME check referer header to determine destination
class Yesod a => YesodAuth a where data LoginType = OpenId | Rpxnow
-- | The following breaks DRY, but I cannot think of a better solution
-- right now.
--
-- The root relative to the application root. Should not begin with a slash
-- and should end with one.
authRoot :: a -> String
authRoot _ = "auth/"
-- | Absolute path to the default login path. data Auth = forall y. Yesod y => Auth
defaultLoginPath :: a -> String { defaultDest :: String
defaultLoginPath a = approot a ++ authRoot a ++ "openid/" , onRpxnowLogin :: Rpxnow.Identifier -> Handler Auth ()
, rpxnowApiKey :: Maybe String
, defaultLoginType :: LoginType
, parentYesod :: y
}
rpxnowApiKey :: a -> Maybe String $(mkYesod "Auth" [$parseRoutes|
rpxnowApiKey _ = Nothing /check Check GET
/logout Logout GET
onRpxnowLogin :: Rpxnow.Identifier -> Handler a () /openid OpenIdR GET
onRpxnowLogin _ = return () /openid/forward OpenIdForward GET
/openid/complete OpenIdComplete GET
getFullAuthRoot :: YesodAuth y => Handler y String /login/rpxnow RpxnowR
getFullAuthRoot = do |])
y <- getYesod
ar <- getApproot
return $ ar ++ authRoot y
data AuthResource =
Check
| Logout
| Openid
| OpenidForward
| OpenidComplete
| LoginRpxnow
deriving (Show, Eq, Enum, Bounded)
rc :: HasReps x => Handler y x -> Handler y ChooseRep
rc = fmap chooseRep
authHandler :: YesodAuth y => W.Method -> [String] -> Handler y ChooseRep
authHandler W.GET ["check"] = rc authCheck
authHandler W.GET ["logout"] = rc authLogout
authHandler W.GET ["openid"] = rc authOpenidForm
authHandler W.GET ["openid", "forward"] = rc authOpenidForward
authHandler W.GET ["openid", "complete"] = rc authOpenidComplete
-- two different versions of RPX protocol apparently, so just accepting all
-- verbs
authHandler _ ["login", "rpxnow"] = rc rpxnowLogin
authHandler _ _ = notFound
-- FIXME data OIDFormReq = OIDFormReq (Maybe String) (Maybe String)
{- FIXME
instance ConvertSuccess OIDFormReq Html where
convertSuccess (OIDFormReq Nothing _) = cs ""
convertSuccess (OIDFormReq (Just s) _) =
Tag "p" [("class", "message")] $ cs s
-}
data ExpectedSingleParam = ExpectedSingleParam data ExpectedSingleParam = ExpectedSingleParam
deriving (Show, Typeable) deriving (Show, Typeable)
instance Exception ExpectedSingleParam instance Exception ExpectedSingleParam
authOpenidForm :: Yesod y => Handler y ChooseRep getOpenIdR :: Handler Auth RepHtml
authOpenidForm = do getOpenIdR = do
rr <- getRequest rr <- getRequest
case getParams rr "dest" of case getParams rr "dest" of
[] -> return () [] -> return ()
(x:_) -> addCookie destCookieTimeout destCookieName x (x:_) -> addCookie destCookieTimeout destCookieName x
let html = template (getParams rr "message") (Auth _ _ _ _ y) <- getYesod
simpleApplyLayout "Log in via OpenID" html let html = template (getParams rr "message", id)
let pc = PageContent
{ pageTitle = cs "Log in via OpenID"
, pageHead = return ()
, pageBody = html
}
content <- hamletToContent $ applyLayout y pc rr
return $ RepHtml content
where where
urlForward _ = error "FIXME urlForward" urlForward (_, wrapper) = wrapper OpenIdForward
hasMessage = not . null hasMessage = not . null . fst
message [] = cs "" message ([], _) = cs ""
message (m:_) = cs m message (m:_, _) = cs m
template = [$hamlet| template = [$hamlet|
$if hasMessage $if hasMessage
%p.message $message$ %p.message $message$
@ -125,14 +97,14 @@ $if hasMessage
%input!type=submit!value=Login %input!type=submit!value=Login
|] |]
authOpenidForward :: YesodAuth y => Handler y () getOpenIdForward :: Handler Auth ()
authOpenidForward = do getOpenIdForward = do
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", show ExpectedSingleParam)]
authroot <- getFullAuthRoot render <- getUrlRender
let complete = authroot ++ "/openid/complete/" let complete = render OpenIdComplete
res <- runAttemptT $ OpenId.getForwardUrl oid complete res <- runAttemptT $ OpenId.getForwardUrl oid complete
attempt attempt
(\err -> redirect RedirectTemporary (\err -> redirect RedirectTemporary
@ -140,8 +112,8 @@ authOpenidForward = do
(redirect RedirectTemporary) (redirect RedirectTemporary)
res res
authOpenidComplete :: Yesod y => Handler y () getOpenIdComplete :: Handler Auth ()
authOpenidComplete = do getOpenIdComplete = do
rr <- getRequest rr <- getRequest
let gets' = reqGetParams rr let gets' = reqGetParams rr
res <- runAttemptT $ OpenId.authenticate gets' res <- runAttemptT $ OpenId.authenticate gets'
@ -149,15 +121,14 @@ authOpenidComplete = do
$ "/auth/openid/?message=" $ "/auth/openid/?message="
++ encodeUrl (show err) ++ encodeUrl (show err)
let onSuccess (OpenId.Identifier ident) = do let onSuccess (OpenId.Identifier ident) = do
ar <- getApproot y <- getYesod
header authCookieName ident header authCookieName ident
redirectToDest RedirectTemporary ar redirectToDest RedirectTemporary $ defaultDest y
attempt onFailure onSuccess res attempt onFailure onSuccess res
rpxnowLogin :: YesodAuth y => Handler y () handleRpxnowR :: Handler Auth ()
rpxnowLogin = do handleRpxnowR = do
ay <- getYesod ay <- getYesod
let ar = approot ay
apiKey <- case rpxnowApiKey ay of apiKey <- case rpxnowApiKey ay of
Just x -> return x Just x -> return x
Nothing -> notFound Nothing -> notFound
@ -168,13 +139,14 @@ rpxnowLogin = do
(x:_) -> x (x:_) -> x
let dest = case pp "dest" of let dest = case pp "dest" of
[] -> case getParams rr "dest" of [] -> case getParams rr "dest" of
[] -> ar [] -> defaultDest ay
("":_) -> ar ("":_) -> defaultDest ay
(('#':rest):_) -> rest (('#':rest):_) -> rest
(s:_) -> s (s:_) -> s
(d:_) -> d (d:_) -> d
ident <- liftIO $ Rpxnow.authenticate apiKey token ident <- liftIO $ Rpxnow.authenticate apiKey token
onRpxnowLogin ident auth <- getYesod
onRpxnowLogin auth ident
header authCookieName $ Rpxnow.identifier ident header authCookieName $ Rpxnow.identifier ident
header authDisplayName $ getDisplayName ident header authDisplayName $ getDisplayName ident
redirectToDest RedirectTemporary dest redirectToDest RedirectTemporary dest
@ -192,22 +164,25 @@ getDisplayName (Rpxnow.Identifier ident extra) = helper choices where
Nothing -> helper xs Nothing -> helper xs
Just y -> y Just y -> y
authCheck :: Yesod y => Handler y ChooseRep getCheck :: Handler Auth RepHtml
authCheck = do getCheck = do
_ident <- maybeIdentifier ident <- maybeIdentifier
_dn <- displayName dn <- displayName
error "FIXME applyLayoutJson" -- FIXME applyLayoutJson
{- hamletToRepHtml $ [$hamlet|
applyLayoutJson "Authentication Status" $ cs %h1 Authentication Status
[ ("identifier", fromMaybe "" ident) %dl
, ("displayName", fromMaybe "" dn) %dt identifier
] %dd $fst$
-} %dt displayName
%dd $snd$
|] (cs $ fromMaybe "" ident, cs $ fromMaybe "" dn)
authLogout :: YesodAuth y => Handler y () getLogout :: Handler Auth ()
authLogout = do getLogout = do
y <- getYesod
deleteCookie authCookieName deleteCookie authCookieName
getApproot >>= redirectToDest RedirectTemporary redirectToDest RedirectTemporary $ defaultDest y
-- | Gets the identifier for a user if available. -- | Gets the identifier for a user if available.
maybeIdentifier :: (Functor m, Monad m, RequestReader m) => m (Maybe String) maybeIdentifier :: (Functor m, Monad m, RequestReader m) => m (Maybe String)
@ -223,18 +198,22 @@ displayName = do
-- | Gets the identifier for a user. If user is not logged in, redirects them -- | Gets the identifier for a user. If user is not logged in, redirects them
-- to the login page. -- to the login page.
authIdentifier :: YesodAuth y => Handler y String authIdentifier :: Handler Auth String
authIdentifier = maybeIdentifier >>= maybe redirectLogin return 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 y => Handler y a redirectLogin :: Handler Auth a
redirectLogin = redirectLogin = do
defaultLoginPath `fmap` getYesod >>= redirectSetDest RedirectTemporary y <- getYesod
let r = case defaultLoginType y of
OpenId -> OpenIdR
Rpxnow -> RpxnowR -- FIXME this doesn't actually show a login page?
redirectSetDest RedirectTemporary r
-- | Determinge the path requested by the user (ie, the path info). This -- | Determinge the path requested by the user (ie, the path info). This
-- includes the query string. -- includes the query string.
requestPath :: (Functor m, Monad m, RequestReader m) => m String requestPath :: (Functor m, Monad m, RequestReader m) => m String --FIXME unused
requestPath = do requestPath = do
env <- waiRequest env <- waiRequest
let q = case B8.unpack $ W.queryString env of let q = case B8.unpack $ W.queryString env of
@ -248,13 +227,18 @@ requestPath = do
-- | Redirect to the given URL, and set a cookie with the current URL so the -- | Redirect to the given URL, and set a cookie with the current URL so the
-- user will ultimately be sent back here. -- user will ultimately be sent back here.
redirectSetDest :: Yesod y => RedirectType -> String -> Handler y a redirectSetDest :: RedirectType
-> Routes y -- ^ redirect page
-> Handler y a
redirectSetDest rt dest = do redirectSetDest rt dest = do
ar <- getApproot ur <- getUrlRender
rp <- requestPath curr <- getRoute
let curr = ar ++ rp let curr' = case curr of
addCookie destCookieTimeout destCookieName curr Just x -> ur x
redirect rt dest Nothing -> "/" -- should never happen anyway
dest' = ur dest
addCookie destCookieTimeout destCookieName curr'
redirect rt dest'
-- | Read the 'destCookieName' cookie and redirect to this destination. If the -- | Read the 'destCookieName' cookie and redirect to this destination. If the
-- cookie is missing, then use the default path provided. -- cookie is missing, then use the default path provided.

View File

@ -20,6 +20,6 @@ mkYesod name res = do
decs <- createRoutes (name ++ "Routes") decs <- createRoutes (name ++ "Routes")
''YesodApp ''YesodApp
name' name'
"runHandler" "runHandler'"
res res
return $ tySyn : yes : decs return $ tySyn : yes : decs

View File

@ -55,9 +55,9 @@ class YesodSite a => Yesod a where
-- | Applies some form of layout to <title> and <body> contents of a page. FIXME: use a Maybe here to allow subsites to simply inherit. -- | Applies some form of layout to <title> and <body> contents of a page. FIXME: use a Maybe here to allow subsites to simply inherit.
applyLayout :: a applyLayout :: a
-> PageContent (Routes a) -> PageContent url -- FIXME not so good, should be Routes y
-> Request -> Request
-> Hamlet (Routes a) IO () -> Hamlet url IO ()
applyLayout _ p _ = [$hamlet| applyLayout _ p _ = [$hamlet|
!!! !!!
%html %html
@ -159,10 +159,11 @@ toWaiApp' y resource session env = do
onRequest y rr onRequest y rr
print pathSegments print pathSegments
let ya = case eurl of let ya = case eurl of
Left _ -> runHandler (errorHandler y NotFound) y render Left _ -> runHandler (errorHandler y NotFound) y Nothing render
Right url -> handleSite site render url method Right url -> handleSite site render url method
(badMethod method) y (badMethod method) y
let eh er = runHandler (errorHandler y er) y render let url' = either (const Nothing) Just eurl
let eh er = runHandler (errorHandler y er) y url' render
unYesodApp ya eh rr types >>= responseToWaiResponse unYesodApp ya eh rr types >>= responseToWaiResponse
cleanupSegments :: [B.ByteString] -> [String] cleanupSegments :: [B.ByteString] -> [String]