Merge branch 'json-auth2' of https://github.com/yesodweb/yesod into yesod1.2

This commit is contained in:
Michael Snoyman 2013-04-03 09:29:13 +03:00
commit c8738103f4
5 changed files with 61 additions and 17 deletions

View File

@ -294,11 +294,11 @@ authorizationCheck = do
void $ permissionDenied "Authentication required" void $ permissionDenied "Authentication required"
Just url' -> do Just url' -> do
void $ selectRep $ do void $ selectRep $ do
provideRepType typeJson $ do
void $ permissionDenied "Authentication required"
provideRepType typeHtml $ do provideRepType typeHtml $ do
setUltDestCurrent setUltDestCurrent
void $ redirect url' void $ redirect url'
provideRepType typeJson $ do
void $ permissionDenied "Authentication required"
Unauthorized s' -> permissionDenied s' Unauthorized s' -> permissionDenied s'
-- | Convert a widget to a 'PageContent'. -- | Convert a widget to a 'PageContent'.

View File

@ -31,6 +31,7 @@ module Yesod.Core.Content
, typeOctet , typeOctet
-- * Utilities -- * Utilities
, simpleContentType , simpleContentType
, contentTypeTypes
-- * Evaluation strategy -- * Evaluation strategy
, DontFullyEvaluate (..) , DontFullyEvaluate (..)
-- * Representations -- * Representations
@ -209,6 +210,17 @@ typeOctet = "application/octet-stream"
simpleContentType :: ContentType -> ContentType simpleContentType :: ContentType -> ContentType
simpleContentType = fst . B.breakByte 59 -- 59 == ; simpleContentType = fst . B.breakByte 59 -- 59 == ;
-- Give just the media types as a pair.
-- For example, \"text/html; charset=utf-8\" returns ("text", "html")
contentTypeTypes :: ContentType -> (B.ByteString, B.ByteString)
contentTypeTypes ct = (main, fst $ B.breakByte semicolon (tailEmpty sub))
where
tailEmpty x = if B.null x then "" else B.tail x
(main, sub) = B.breakByte slash ct
slash = 47
semicolon = 59
instance HasContentType a => HasContentType (DontFullyEvaluate a) where instance HasContentType a => HasContentType (DontFullyEvaluate a) where
getContentType = getContentType . liftM unDontFullyEvaluate getContentType = getContentType . liftM unDontFullyEvaluate

View File

@ -172,7 +172,7 @@ import Data.Text (Text)
import qualified Network.Wai.Parse as NWP import qualified Network.Wai.Parse as NWP
import Text.Shakespeare.I18N (RenderMessage (..)) import Text.Shakespeare.I18N (RenderMessage (..))
import Web.Cookie (SetCookie (..)) import Web.Cookie (SetCookie (..))
import Yesod.Core.Content (ToTypedContent (..), simpleContentType, HasContentType (..), ToContent (..), ToFlushBuilder (..)) import Yesod.Core.Content (ToTypedContent (..), simpleContentType, contentTypeTypes, HasContentType (..), ToContent (..), ToFlushBuilder (..))
import Yesod.Core.Internal.Util (formatRFC1123) import Yesod.Core.Internal.Util (formatRFC1123)
import Text.Blaze.Html (preEscapedToMarkup, toHtml) import Text.Blaze.Html (preEscapedToMarkup, toHtml)
@ -186,6 +186,7 @@ import Yesod.Core.Types
import Yesod.Routes.Class (Route) import Yesod.Routes.Class (Route)
import Control.Failure (failure) import Control.Failure (failure)
import Blaze.ByteString.Builder (Builder) import Blaze.ByteString.Builder (Builder)
import Safe (headMay)
get :: MonadHandler m => m GHState get :: MonadHandler m => m GHState
get = liftHandlerT $ HandlerT $ I.readIORef . handlerState get = liftHandlerT $ HandlerT $ I.readIORef . handlerState
@ -849,27 +850,51 @@ selectRep :: MonadHandler m
=> Writer.Writer (Endo [ProvidedRep m]) () => Writer.Writer (Endo [ProvidedRep m]) ()
-> m TypedContent -> m TypedContent
selectRep w = do selectRep w = do
-- the content types are already sorted by q values
-- which have been stripped
cts <- liftM reqAccept getRequest cts <- liftM reqAccept getRequest
case mapMaybe tryAccept cts of case mapMaybe tryAccept cts of
[] -> [] ->
case reps of case reps of
[] -> return $ toTypedContent ("No reps provided to selectRep" :: Text) [] -> sendResponseStatus H.status500 ("No reps provided to selectRep" :: Text)
rep:_ -> returnRep rep rep:_ ->
if null cts
then returnRep rep
else sendResponseStatus H.status406 explainUnaccepted
rep:_ -> returnRep rep rep:_ -> returnRep rep
where where
returnRep (ProvidedRep ct mcontent) = do explainUnaccepted :: Text
content <- mcontent explainUnaccepted = "no match found for accept header"
return $ TypedContent ct content
returnRep (ProvidedRep ct mcontent) =
mcontent >>= return . TypedContent ct
reps = appEndo (Writer.execWriter w) [] reps = appEndo (Writer.execWriter w) []
repMap = Map.unions $ map (\v@(ProvidedRep k _) -> Map.fromList repMap = Map.unions $ map (\v@(ProvidedRep k _) -> Map.fromList
[ (k, v) [ (k, v)
, (noSpace k, v) , (noSpace k, v)
, (simpleContentType k, v) , (simpleContentType k, v)
]) reps ]) reps
tryAccept ct = Map.lookup ct repMap <|>
Map.lookup (noSpace ct) repMap <|> -- match on the type for sub-type wildcards.
Map.lookup (simpleContentType ct) repMap -- If the accept is text/* it should match a provided text/html
mainTypeMap = Map.fromList $ reverse $ map
(\v@(ProvidedRep ct _) -> (fst $ contentTypeTypes ct, v)) reps
tryAccept ct =
if subType == "*"
then if mainType == "*"
then headMay reps
else Map.lookup mainType mainTypeMap
else lookupAccept ct
where
(mainType, subType) = contentTypeTypes ct
lookupAccept ct = Map.lookup ct repMap <|>
Map.lookup (noSpace ct) repMap <|>
Map.lookup (simpleContentType ct) repMap
-- Mime types such as "text/html; charset=foo" get converted to -- Mime types such as "text/html; charset=foo" get converted to
-- "text/html;charset=foo" -- "text/html;charset=foo"

View File

@ -7,6 +7,7 @@ import Network.Wai.Test
import Network.Wai import Network.Wai
import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Char8 as S8
import qualified Data.Text as T import qualified Data.Text as T
import Data.List (isSuffixOf)
data App = App data App = App
@ -47,6 +48,9 @@ test method path f = it (method ++ " " ++ path) $ do
sres <- request defaultRequest sres <- request defaultRequest
{ requestMethod = S8.pack method { requestMethod = S8.pack method
, pathInfo = [T.pack path] , pathInfo = [T.pack path]
, requestHeaders =
if not $ isSuffixOf "json" path then [] else
[("Accept", S8.pack "application/json")]
} }
f sres f sres

View File

@ -40,21 +40,22 @@ getJsonR = selectRep $ do
rep typeHtml "HTML" rep typeHtml "HTML"
provideRep $ return $ object ["message" .= ("Invalid Login" :: Text)] provideRep $ return $ object ["message" .= ("Invalid Login" :: Text)]
testRequest :: Request testRequest :: Int -- ^ http status code
-> Request
-> ByteString -- ^ expected body -> ByteString -- ^ expected body
-> Spec -> Spec
testRequest req expected = it (S8.unpack $ fromJust $ lookup "Accept" $ requestHeaders req) $ do testRequest status req expected = it (S8.unpack $ fromJust $ lookup "Accept" $ requestHeaders req) $ do
app <- toWaiApp App app <- toWaiApp App
flip runSession app $ do flip runSession app $ do
sres <- request req sres <- request req
assertStatus status sres
assertBody expected sres assertBody expected sres
assertStatus 200 sres
test :: String -- ^ accept header test :: String -- ^ accept header
-> ByteString -- ^ expected body -> ByteString -- ^ expected body
-> Spec -> Spec
test accept expected = test accept expected =
testRequest (acceptRequest accept) expected testRequest 200 (acceptRequest accept) expected
acceptRequest :: String -> Request acceptRequest :: String -> Request
acceptRequest accept = defaultRequest acceptRequest accept = defaultRequest
@ -68,9 +69,11 @@ specs = describe "selectRep" $ do
test "text/xml" "XML" test "text/xml" "XML"
test (S8.unpack typeXml) "XML" test (S8.unpack typeXml) "XML"
test "text/xml,application/json" "XML" test "text/xml,application/json" "XML"
test "text/foo" "HTML"
test "text/xml;q=0.9,application/json;q=1.0" "JSON" test "text/xml;q=0.9,application/json;q=1.0" "JSON"
test (S8.unpack typeHtml) "HTML" test (S8.unpack typeHtml) "HTML"
test "text/html" "HTML" test "text/html" "HTML"
test specialHtml "HTMLSPECIAL" test specialHtml "HTMLSPECIAL"
testRequest (acceptRequest "application/json") { pathInfo = ["json"] } "{\"message\":\"Invalid Login\"}" testRequest 200 (acceptRequest "application/json") { pathInfo = ["json"] } "{\"message\":\"Invalid Login\"}"
testRequest 406 (acceptRequest "text/foo") "no match found for accept header"
test "text/*" "HTML"
test "*/*" "HTML"