Merge branch 'json-auth2' of https://github.com/yesodweb/yesod into yesod1.2
This commit is contained in:
commit
c8738103f4
@ -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'.
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user