Vastly simplified Web.Restful.Application

This commit is contained in:
Michael Snoyman 2009-08-05 13:32:01 +03:00
parent c4eb5e1ee7
commit cb963a6231
8 changed files with 393 additions and 304 deletions

93
Data/Object/Instances.hs Normal file
View File

@ -0,0 +1,93 @@
---------------------------------------------------------
--
-- Module : Data.Object.Instances
-- Copyright : Michael Snoyman
-- License : BSD3
--
-- Maintainer : Michael Snoyman <michael@snoyman.com>
-- Stability : Stable
-- Portability : portable
--
-- Instances for converting various types of data into Data.Object.Object.
--
---------------------------------------------------------
module Data.Object.Instances
( Json (..)
, Yaml (..)
, Html (..)
) where
import Data.Object
import qualified Data.ByteString as B
import Data.ByteString.Class
import Web.Encodings (encodeJson)
import qualified Text.Yaml as Y
newtype Json = Json B.ByteString
instance FromObject Json where
fromObject = return . Json . helper where
helper :: Object -> B.ByteString
helper (Scalar s) = B.concat
[ toStrictByteString "\""
, encodeJson $ fromStrictByteString s
, toStrictByteString "\""
]
helper (Sequence s) = B.concat
[ toStrictByteString "["
, B.intercalate (toStrictByteString ",") $ map helper s
, toStrictByteString "]"
]
helper (Mapping m) = B.concat
[ toStrictByteString "{"
, B.intercalate (toStrictByteString ",") $ map helper2 m
, toStrictByteString "}"
]
helper2 :: (B.ByteString, Object) -> B.ByteString
helper2 (k, v) = B.concat
[ toStrictByteString "\""
, encodeJson $ fromStrictByteString k
, toStrictByteString "\":"
, helper v
]
newtype Yaml = Yaml B.ByteString
instance FromObject Yaml where
fromObject = return . Yaml . Y.encode
-- | Represents as an entire HTML 5 document by using the following:
--
-- * A scalar is a paragraph.
-- * A sequence is an unordered list.
-- * A mapping is a definition list.
newtype Html = Html B.ByteString
instance FromObject Html where
fromObject o = return $ Html $ B.concat
[ toStrictByteString "<!DOCTYPE html>\n<html><body>"
, helper o
, toStrictByteString "</body></html>"
] where
helper :: Object -> B.ByteString
helper (Scalar s) = B.concat
[ toStrictByteString "<p>"
, s
, toStrictByteString "</p>"
]
helper (Sequence []) = toStrictByteString "<ul></ul>"
helper (Sequence s) = B.concat
[ toStrictByteString "<ul><li>"
, B.intercalate (toStrictByteString "</li><li>") $ map helper s
, toStrictByteString "</li></ul>"
]
helper (Mapping m) = B.concat $
toStrictByteString "<dl>" :
map helper2 m ++
[ toStrictByteString "</dl>" ]
helper2 :: (B.ByteString, Object) -> B.ByteString
helper2 (k, v) = B.concat $
[ toStrictByteString "<dt>"
, k
, toStrictByteString "</dt><dd>"
, helper v
, toStrictByteString "</dd>"
]

View File

@ -1,27 +0,0 @@
---------------------------------------------------------
--
-- Module : Web.Restful
-- Copyright : Michael Snoyman
-- License : BSD3
--
-- Maintainer : Michael Snoyman <michael@snoyman.com>
-- Stability : Stable
-- Portability : portable
--
-- Lightweight framework for designing RESTful APIs.
--
---------------------------------------------------------
module Web.Restful
(
module Data.Object
, module Web.Restful.Request
, module Web.Restful.Response
, module Web.Restful.Application
, module Web.Restful.Definitions
) where
import Data.Object
import Web.Restful.Request
import Web.Restful.Response
import Web.Restful.Application
import Web.Restful.Definitions

View File

@ -18,24 +18,14 @@ module Web.Restful.Application
(
-- * Defining an application
ApplicationMonad
-- ** Routing
, addResource
-- ** Settings
, setHandler
, setRpxnowApiKey
, setResourceParser
, setHtmlWrapper
-- ** Engage
, run
-- * FIXME
, Application (..)
, toHackApp
) where
-- hideously long import list
import qualified Hack
import qualified Hack.Handler.CGI
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.State hiding (gets)
import Web.Encodings
import Data.Maybe (isJust)
@ -43,8 +33,7 @@ import Data.ByteString.Class
import qualified Data.ByteString.Lazy as BS
import Data.Function.Predicate (equals)
import Data.Default
import qualified Web.Authenticate.Rpxnow as Rpxnow
import qualified Web.Authenticate.OpenId as OpenId
import Control.Applicative ( Applicative (..))
import Hack.Middleware.Gzip
import Hack.Middleware.CleanPath
@ -52,19 +41,15 @@ import Hack.Middleware.Jsonp
import Hack.Middleware.ClientSession
import Hack.Middleware.MethodOverride
import Control.Applicative ((<$>), Applicative (..))
import Control.Arrow (second)
import Web.Restful.Request
import Web.Restful.Response
import Web.Restful.Constants
import Web.Restful.Utils
import Web.Restful.Handler
import Web.Restful.Definitions
import Data.Object
import Web.Restful.Constants
-- | Contains settings and a list of resources.
type ApplicationMonad a = StateT (ApplicationSettings a) (Writer (HandlerMap a))
type ApplicationMonad a = State (ApplicationSettings a)
instance Applicative (ApplicationMonad a) where
pure = return
f <*> a = do
@ -72,20 +57,16 @@ instance Applicative (ApplicationMonad a) where
a' <- a
return $! f' a'
data ApplicationSettings rn = ApplicationSettings
{ hackHandler :: Hack.Application -> IO ()
, rpxnowApiKey :: Maybe String
, encryptKey :: Either FilePath Word256
, appResourceParser :: ResourceParser rn
{ encryptKey :: Either FilePath Word256
, hackMiddleware :: [Hack.Middleware]
, response404 :: Hack.Env -> IO Hack.Response
, htmlWrapper :: BS.ByteString -> BS.ByteString
}
instance ResourceName a => Default (ApplicationSettings a) where
instance (HasResourceParser a) =>
Default (ApplicationSettings a) where
def = ApplicationSettings
{ hackHandler = Hack.Handler.CGI.run
, rpxnowApiKey = Nothing
, encryptKey = Left defaultKeyFile
, appResourceParser = \s -> ParsedResource (toResourceName s) []
{ encryptKey = Left defaultKeyFile
, hackMiddleware =
[ gzip
, cleanPath
@ -105,250 +86,65 @@ default404 env = return $
-- FIXME document below here
addResource :: (Request req, Response res, ResourceName rn)
=> Verb
-> rn
-> (req -> IO res)
-> ApplicationMonad rn ()
addResource verb resourceName' f = do
let handler :: Handler
handler = Handler $ (fmap ResponseWrapper) . f
handlerDesc = HandlerDesc resourceName' verb handler
tell [handlerDesc]
setResourceParser :: ResourceName rn
=> ResourceParser rn
-> ApplicationMonad rn ()
setResourceParser newRP = do
s <- get
put $ s { appResourceParser = newRP }
setHtmlWrapper :: (BS.ByteString -> BS.ByteString) -> ApplicationMonad a ()
setHtmlWrapper f = do
s <- get
put $ s { htmlWrapper = f }
run :: ResourceName a => ApplicationMonad a () -> IO ()
run m = do
let (settings, resources') = runWriter $ execStateT m def
toHackApp :: (Eq a, HasResourceParser a, HasHandlers a b)
=> ApplicationMonad a ()
-> b
-> IO Hack.Application
toHackApp am model = do
let settings = execState am def
key <- case encryptKey settings of
Left f -> getKey f
Right k -> return k
let defApp = defaultResources settings
defResources = execWriter $ execStateT defApp def
resources = resources' ++ defResources -- FIXME rename HandlerDescs
app' :: Hack.Application
app' = toHackApplication $ Application resources settings
clientsession' :: Hack.Middleware
clientsession' = clientsession [authCookieName] key
app :: Hack.Application
let handlers = getHandler model
app' = toHackApplication handlers settings
clientsession' = clientsession [authCookieName] key -- FIXME gotta be a better way...
app = foldr ($) app' $ hackMiddleware settings ++ [clientsession']
hackHandler settings app
return app
setHandler :: (Hack.Application -> IO ()) -> ApplicationMonad a ()
setHandler h = do
settings <- get
put $ settings { hackHandler = h }
setRpxnowApiKey :: String -> ApplicationMonad a ()
setRpxnowApiKey k = do
settings <- get
put $ settings { rpxnowApiKey = Just k }
defaultResources :: ResourceName rn
=> ApplicationSettings rn
-> ApplicationMonad rn ()
defaultResources settings = do
addResource Get (toResourceName ["auth", "check"]) authCheck
addResource Get (toResourceName ["auth", "logout"]) authLogout
addResource Get (toResourceName ["auth", "openid"]) authOpenidForm
addResource Get (toResourceName ["auth", "openid", "forward"]) authOpenidForward
addResource Get (toResourceName ["auth", "openid", "complete"]) authOpenidComplete
case rpxnowApiKey settings of
Nothing -> return ()
Just key -> do
addResource Get (toResourceName ["auth", "login", "rpxnow"]) $
rpxnowLogin key
data OIDFormReq = OIDFormReq (Maybe String) (Maybe String)
instance Request OIDFormReq where
parseRequest = OIDFormReq <$> getParam "message" <*> getParam "dest"
instance Show OIDFormReq where
show (OIDFormReq Nothing _) = ""
show (OIDFormReq (Just s) _) = "<p class='message'>" ++ encodeHtml s ++
"</p>"
data OIDFormRes = OIDFormRes String (Maybe String)
instance Response OIDFormRes where
reps (OIDFormRes s dest) = [("text/html", response 200 heads s)]
where
heads =
case dest of
Nothing -> []
Just dest' ->
[("Set-Cookie", "DEST=" ++ dest' ++ "; path=/")]
authOpenidForm :: OIDFormReq -> IO OIDFormRes
authOpenidForm m@(OIDFormReq _ dest) =
let html =
show m ++
"<form method='get' action='forward/'>" ++
"OpenID: <input type='text' name='openid'>" ++
"<input type='submit' value='Login'>" ++
"</form>"
in return $! OIDFormRes html dest
data OIDFReq = OIDFReq String String
instance Request OIDFReq where
parseRequest = do
oid <- getParam "openid"
env <- parseEnv
let complete = "http://" ++ Hack.serverName env ++ ":" ++
show (Hack.serverPort env) ++
"/auth/openid/complete/"
return $! OIDFReq oid complete
authOpenidForward :: OIDFReq -> IO GenResponse
authOpenidForward (OIDFReq oid complete) = do
res <- OpenId.getForwardUrl oid complete :: IO (Either String String)
return $
case res of
Left err -> RedirectResponse $ "/auth/openid/?message=" ++
encodeUrl err
Right url -> RedirectResponse url
data OIDComp = OIDComp [(String, String)] (Maybe String)
instance Request OIDComp where
parseRequest = do
rr <- ask
let gets = rawGetParams rr
dest <- cookieParam "DEST"
return $! OIDComp gets dest
data OIDCompRes = OIDCompResErr String
| OIDCompResGood String (Maybe String)
instance Response OIDCompRes where
reps (OIDCompResErr err) =
reps $ RedirectResponse
$ "/auth/openid/?message=" ++
encodeUrl err
reps (OIDCompResGood ident Nothing) =
reps $ OIDCompResGood ident (Just "/")
reps (OIDCompResGood ident (Just dest)) =
[("text/plain", response 303 heads "")] where
heads =
[ (authCookieName, ident)
, resetCookie "DEST"
, ("Location", dest)
]
resetCookie :: String -> (String, String)
resetCookie name =
("Set-Cookie",
name ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
authOpenidComplete :: OIDComp -> IO OIDCompRes
authOpenidComplete (OIDComp gets' dest) = do
res <- OpenId.authenticate gets' :: IO (Either String OpenId.Identifier)
return $
case res of
Left err -> OIDCompResErr err
Right (OpenId.Identifier ident) -> OIDCompResGood ident dest
-- | token dest
data RpxnowRequest = RpxnowRequest String (Maybe String)
instance Request RpxnowRequest where
parseRequest = do
token <- getParam "token"
dest <- getParam "dest"
return $! RpxnowRequest token $ chopHash `fmap` dest
chopHash :: String -> String
chopHash ('#':rest) = rest
chopHash x = x
-- | dest identifier
data RpxnowResponse = RpxnowResponse String (Maybe String)
instance Response RpxnowResponse where
reps (RpxnowResponse dest Nothing) =
[("text/html", response 303 [("Location", dest)] "")]
reps (RpxnowResponse dest (Just ident)) =
[("text/html", response 303
[ ("Location", dest)
, (authCookieName, ident)
]
"")]
rpxnowLogin :: String -- ^ api key
-> RpxnowRequest
-> IO RpxnowResponse
rpxnowLogin apiKey (RpxnowRequest token dest') = do
let dest = case dest' of
Nothing -> "/"
Just "" -> "/"
Just s -> s
ident' <- Rpxnow.authenticate apiKey token
return $ RpxnowResponse dest (Rpxnow.identifier `fmap` ident')
data AuthRequest = AuthRequest (Maybe String)
instance Request AuthRequest where
parseRequest = AuthRequest `fmap` identifier
authCheck :: AuthRequest -> IO Object
authCheck (AuthRequest Nothing) =
return $ toObject [("status", "notloggedin")]
authCheck (AuthRequest (Just i)) =
return $ toObject
[ ("status", "loggedin")
, ("ident", i)
]
authLogout :: () -> IO LogoutResponse
authLogout _ = return LogoutResponse
data LogoutResponse = LogoutResponse
instance Response LogoutResponse where
reps _ = map (second addCookie) $ reps tree where
tree = toObject [("status", "loggedout")]
addCookie (Hack.Response s h c) =
Hack.Response s (h':h) c
h' = resetCookie authCookieName
toHackApplication :: Eq resourceName
=> Application resourceName
toHackApplication :: (HasResourceParser resourceName, Eq resourceName)
=> HandlerMap resourceName
-> ApplicationSettings resourceName
-> Hack.Application
toHackApplication (Application hm settings) env = do
toHackApplication hm settings env = do
let (Right resource) = splitPath $ Hack.pathInfo env
(ParsedResource rn urlParams') = (appResourceParser settings) resource
verb :: Verb
verb = toVerb $ Hack.requestMethod env
rr :: RawRequest
rr = envToRawRequest urlParams' env
matchingHandler (HandlerDesc resourceName' verb' _) =
rn == resourceName' &&
verb == verb'
case filter matchingHandler hm of
[HandlerDesc _ _ handler] -> do
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
ctypes' = parseHttpAccept rawHttpAccept
body <- runHandler handler rr
let reps' = reps body
ctypes = filter (\c -> isJust $ lookup c reps') ctypes'
let handlerPair =
case ctypes of
[] -> Just $ head reps'
(c:_) ->
case filter (fst `equals` c) reps' of
[pair] -> Just pair
[] -> Nothing
_ -> error "Overlapping reps"
case handlerPair of
Nothing -> response404 settings $ env
Just (ctype, Hack.Response status headers content) -> do
let wrapper =
case ctype of
"text/html" -> htmlWrapper settings
_ -> id
return $ Hack.Response status
(("Content-Type", ctype) : headers)
$ toLazyByteString $ wrapper content
[] -> response404 settings $ env
_ -> fail $ "Overlapping handlers for: " ++ show env
case resourceParser resource of
Nothing -> response404 settings $ env
(Just (ParsedResource rn urlParams')) -> do
let verb :: Verb
verb = toVerb $ Hack.requestMethod env
rr :: RawRequest
rr = envToRawRequest urlParams' env
case hm rn verb of
(Just handler) -> do
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
ctypes' = parseHttpAccept rawHttpAccept
body <- runHandler handler rr
let reps' = reps body
ctypes = filter (\c -> isJust $ lookup c reps') ctypes'
let handlerPair =
case ctypes of
[] -> Just $ head reps'
(c:_) ->
case filter (fst `equals` c) reps' of
[pair] -> Just pair
[] -> Nothing
_ -> error "Overlapping reps"
case handlerPair of
Nothing -> response404 settings $ env
Just (ctype, Hack.Response status headers content) -> do
let wrapper =
case ctype of
"text/html" -> htmlWrapper settings
_ -> id
return $ Hack.Response status
(("Content-Type", ctype) : headers)
$ toLazyByteString $ wrapper content
Nothing -> response404 settings $ env
envToRawRequest :: [(ParamName, ParamValue)] -> Hack.Env -> RawRequest
envToRawRequest urlParams' env =
@ -361,8 +157,3 @@ envToRawRequest urlParams' env =
rawCookie = tryLookup "" "Cookie" $ Hack.http env
cookies' = decodeCookies rawCookie :: [(String, String)]
in RawRequest rawPieces urlParams' gets' posts cookies' files env
data Application a = Application
{ handlerMap :: HandlerMap a
, applicationSettings :: ApplicationSettings a
}

View File

@ -1,4 +1,5 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
---------------------------------------------------------
--
-- Module : Web.Restful.Definitions
@ -18,7 +19,7 @@ module Web.Restful.Definitions
, Resource
, ParsedResource (..)
, ResourceParser
, ResourceName (..)
, HasResourceParser (..)
) where
import qualified Hack
@ -34,14 +35,14 @@ toVerb _ = Get
type Resource = [String]
class Eq a => ResourceName a where
toResourceName :: [String] -> a
instance ResourceName [String] where
toResourceName = id
data ParsedResource a = ParsedResource
{ resourceName :: a
, urlParameters :: [(String, String)]
}
type ResourceParser a = Resource -> ParsedResource a
type ResourceParser a = Resource -> Maybe (ParsedResource a)
class HasResourceParser a where
resourceParser :: ResourceParser a
simpleParse :: a -> Maybe (ParsedResource a)
simpleParse x = Just $ ParsedResource x []

View File

@ -1,4 +1,6 @@
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
---------------------------------------------------------
--
-- Module : Web.Restful.Handler
@ -16,7 +18,8 @@ module Web.Restful.Handler
( Handler (..)
, runHandler
, HandlerMap
, HandlerDesc (..)
, HasHandlers (..)
, liftHandler
) where
import Web.Restful.Definitions
@ -32,5 +35,12 @@ runHandler (Handler f) rreq = do
Left errors -> fail $ unlines errors -- FIXME
Right req -> f req
data HandlerDesc a = HandlerDesc a Verb Handler
type HandlerMap a = [HandlerDesc a]
type HandlerMap a = a -> Verb -> Maybe Handler
class HasHandlers a b | a -> b where
getHandler :: b -> a -> Verb -> Maybe Handler
liftHandler :: (Request req, Response res)
=> (req -> IO res)
-> Maybe Handler
liftHandler f = Just . Handler $ fmap ResponseWrapper . f

216
Web/Restful/Helpers/Auth.hs Normal file
View File

@ -0,0 +1,216 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
---------------------------------------------------------
--
-- Module : Web.Restful.Helpers.Auth
-- Copyright : Michael Snoyman
-- License : BSD3
--
-- Maintainer : Michael Snoyman <michael@snoyman.com>
-- Stability : Stable
-- Portability : portable
--
-- Authentication through the authentication package.
--
---------------------------------------------------------
module Web.Restful.Helpers.Auth
( AuthResource
, FromAuthResource (..)
, authResourceParser
) where
import qualified Hack
import Web.Encodings
import qualified Web.Authenticate.Rpxnow as Rpxnow
import qualified Web.Authenticate.OpenId as OpenId
import Web.Restful
import Web.Restful.Constants
import Control.Applicative ((<$>), Applicative (..))
import Control.Arrow (second)
import Control.Monad.Reader
import Data.Object
data AuthResource =
AuthCheck
| AuthLogout
| AuthOpenid
| AuthOpenidForward
| AuthOpenidComplete
| AuthLoginRpxnow
deriving Eq
class FromAuthResource a where
fromAuthResource :: AuthResource -> a
authResourceParser :: FromAuthResource far
=> Resource
-> Maybe (ParsedResource far)
authResourceParser ["check"] =
authResourceParser' AuthCheck
authResourceParser ["logout"] =
authResourceParser' AuthLogout
authResourceParser ["openid"] =
authResourceParser' AuthOpenid
authResourceParser ["openid", "forward"] =
authResourceParser' AuthOpenidForward
authResourceParser ["openid", "complete"] =
authResourceParser' AuthOpenidComplete
authResourceParser ["login", "rpxnow"] =
authResourceParser' AuthLoginRpxnow
authResourceParser _ = Nothing
authResourceParser' :: FromAuthResource far
=> AuthResource
-> Maybe (ParsedResource far)
authResourceParser' x = Just $ ParsedResource (fromAuthResource x) []
type RpxnowApiKey = String -- FIXME newtype
instance HasHandlers AuthResource (Maybe RpxnowApiKey) where
getHandler _ AuthCheck Get = liftHandler authCheck
getHandler _ AuthLogout Get = liftHandler authLogout
getHandler _ AuthOpenid Get = liftHandler authOpenidForm
getHandler _ AuthOpenidForward Get = liftHandler authOpenidForward
getHandler _ AuthOpenidComplete Get = liftHandler authOpenidComplete
getHandler (Just key) AuthLoginRpxnow Get = liftHandler $ rpxnowLogin key
getHandler _ _ _ = Nothing
data OIDFormReq = OIDFormReq (Maybe String) (Maybe String)
instance Request OIDFormReq where
parseRequest = OIDFormReq <$> getParam "message" <*> getParam "dest"
instance Show OIDFormReq where
show (OIDFormReq Nothing _) = ""
show (OIDFormReq (Just s) _) = "<p class='message'>" ++ encodeHtml s ++
"</p>"
data OIDFormRes = OIDFormRes String (Maybe String)
instance Response OIDFormRes where
reps (OIDFormRes s dest) = [("text/html", response 200 heads s)]
where
heads =
case dest of
Nothing -> []
Just dest' ->
[("Set-Cookie", "DEST=" ++ dest' ++ "; path=/")]
authOpenidForm :: OIDFormReq -> IO OIDFormRes
authOpenidForm m@(OIDFormReq _ dest) =
let html =
show m ++
"<form method='get' action='forward/'>" ++
"OpenID: <input type='text' name='openid'>" ++
"<input type='submit' value='Login'>" ++
"</form>"
in return $! OIDFormRes html dest
data OIDFReq = OIDFReq String String
instance Request OIDFReq where
parseRequest = do
oid <- getParam "openid"
env <- parseEnv
let complete = "http://" ++ Hack.serverName env ++ ":" ++
show (Hack.serverPort env) ++
"/auth/openid/complete/"
return $! OIDFReq oid complete
authOpenidForward :: OIDFReq -> IO GenResponse
authOpenidForward (OIDFReq oid complete) = do
res <- OpenId.getForwardUrl oid complete :: IO (Either String String)
return $
case res of
Left err -> RedirectResponse $ "/auth/openid/?message=" ++
encodeUrl err
Right url -> RedirectResponse url
data OIDComp = OIDComp [(String, String)] (Maybe String)
instance Request OIDComp where
parseRequest = do
rr <- ask
let gets = rawGetParams rr
dest <- cookieParam "DEST"
return $! OIDComp gets dest
data OIDCompRes = OIDCompResErr String
| OIDCompResGood String (Maybe String)
instance Response OIDCompRes where
reps (OIDCompResErr err) =
reps $ RedirectResponse
$ "/auth/openid/?message=" ++
encodeUrl err
reps (OIDCompResGood ident Nothing) =
reps $ OIDCompResGood ident (Just "/")
reps (OIDCompResGood ident (Just dest)) =
[("text/plain", response 303 heads "")] where
heads =
[ (authCookieName, ident)
, resetCookie "DEST"
, ("Location", dest)
]
resetCookie :: String -> (String, String)
resetCookie name =
("Set-Cookie",
name ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
authOpenidComplete :: OIDComp -> IO OIDCompRes
authOpenidComplete (OIDComp gets' dest) = do
res <- OpenId.authenticate gets' :: IO (Either String OpenId.Identifier)
return $
case res of
Left err -> OIDCompResErr err
Right (OpenId.Identifier ident) -> OIDCompResGood ident dest
-- | token dest
data RpxnowRequest = RpxnowRequest String (Maybe String)
instance Request RpxnowRequest where
parseRequest = do
token <- getParam "token"
dest <- getParam "dest"
return $! RpxnowRequest token $ chopHash `fmap` dest
chopHash :: String -> String
chopHash ('#':rest) = rest
chopHash x = x
-- | dest identifier
data RpxnowResponse = RpxnowResponse String (Maybe String)
instance Response RpxnowResponse where
reps (RpxnowResponse dest Nothing) =
[("text/html", response 303 [("Location", dest)] "")]
reps (RpxnowResponse dest (Just ident)) =
[("text/html", response 303
[ ("Location", dest)
, (authCookieName, ident)
]
"")]
rpxnowLogin :: String -- ^ api key
-> RpxnowRequest
-> IO RpxnowResponse
rpxnowLogin apiKey (RpxnowRequest token dest') = do
let dest = case dest' of
Nothing -> "/"
Just "" -> "/"
Just s -> s
ident' <- Rpxnow.authenticate apiKey token
return $ RpxnowResponse dest (Rpxnow.identifier `fmap` ident')
data AuthRequest = AuthRequest (Maybe String)
instance Request AuthRequest where
parseRequest = AuthRequest `fmap` identifier
authCheck :: AuthRequest -> IO Object
authCheck (AuthRequest Nothing) =
return $ toObject [("status", "notloggedin")]
authCheck (AuthRequest (Just i)) =
return $ toObject
[ ("status", "loggedin")
, ("ident", i)
]
authLogout :: () -> IO LogoutResponse
authLogout _ = return LogoutResponse
data LogoutResponse = LogoutResponse
instance Response LogoutResponse where
reps _ = map (second addCookie) $ reps tree where
tree = toObject [("status", "loggedout")]
addCookie (Hack.Response s h c) =
Hack.Response s (h':h) c
h' = resetCookie authCookieName

View File

@ -47,6 +47,7 @@ import System.Locale
import Web.Restful.Request -- FIXME ultimately remove
import Data.Object
import Data.List (intercalate)
import Data.Object.Instances
type ContentType = String

View File

@ -29,7 +29,8 @@ library
bytestring-class,
web-encodings,
mtl >= 1.1.0.2,
data-object
data-object,
yaml >= 0.0.1
exposed-modules: Web.Restful,
Web.Restful.Constants,
Web.Restful.Request,
@ -37,5 +38,8 @@ library
Web.Restful.Utils,
Web.Restful.Definitions,
Web.Restful.Handler,
Web.Restful.Application
Web.Restful.Application,
Data.Object.Instances,
Hack.Middleware.MethodOverride,
Web.Restful.Helpers.Auth
ghc-options: -Wall