fix: Return 405 Method Not Allowed for unsupported verbs

Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
This commit is contained in:
Wolfgang Walther
2022-01-28 19:26:33 +01:00
committed by Wolfgang Walther
parent 8980b09419
commit 58f76f3d6d
9 changed files with 99 additions and 70 deletions
+14 -12
View File
@@ -77,7 +77,7 @@ import PostgREST.GucHeader (GucHeader,
import PostgREST.Request.ApiRequest (Action (..),
ApiRequest (..),
InvokeMethod (..),
Target (..))
Mutation (..), Target (..))
import PostgREST.Request.Preferences (PreferCount (..),
PreferParameters (..),
PreferRepresentation (..),
@@ -229,13 +229,13 @@ handleRequest context@(RequestContext _ _ ApiRequest{..} _) =
case (iAction, iTarget) of
(ActionRead headersOnly, TargetIdent identifier) ->
handleRead headersOnly identifier context
(ActionCreate, TargetIdent identifier) ->
(ActionMutate MutationCreate, TargetIdent identifier) ->
handleCreate identifier context
(ActionUpdate, TargetIdent identifier) ->
(ActionMutate MutationUpdate, TargetIdent identifier) ->
handleUpdate identifier context
(ActionSingleUpsert, TargetIdent identifier) ->
(ActionMutate MutationSingleUpsert, TargetIdent identifier) ->
handleSingleUpsert identifier context
(ActionDelete, TargetIdent identifier) ->
(ActionMutate MutationDelete, TargetIdent identifier) ->
handleDelete identifier context
(ActionInfo, TargetIdent identifier) ->
handleInfo identifier context
@@ -243,6 +243,8 @@ handleRequest context@(RequestContext _ _ ApiRequest{..} _) =
handleInvoke invMethod proc context
(ActionInspect headersOnly, TargetDefaultSpec tSchema) ->
handleOpenApi headersOnly tSchema context
(ActionUnknown verb, _) ->
throwError $ Error.UnsupportedVerb verb
_ ->
throwError Error.NotFound
@@ -313,7 +315,7 @@ handleCreate identifier@QualifiedIdentifier{..} context@RequestContext{..} = do
ApiRequest{..} = ctxApiRequest
pkCols = tablePKCols ctxDbStructure qiSchema qiName
WriteQueryResult{..} <- writeQuery identifier True pkCols context
WriteQueryResult{..} <- writeQuery MutationCreate identifier True pkCols context
let
response = gucResponse resGucStatus resGucHeaders
@@ -344,7 +346,7 @@ handleCreate identifier@QualifiedIdentifier{..} context@RequestContext{..} = do
handleUpdate :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
handleUpdate identifier context@(RequestContext _ _ ApiRequest{..} _) = do
WriteQueryResult{..} <- writeQuery identifier False mempty context
WriteQueryResult{..} <- writeQuery MutationUpdate identifier False mempty context
let
response = gucResponse resGucStatus resGucHeaders
@@ -369,7 +371,7 @@ handleSingleUpsert identifier context@(RequestContext _ _ ApiRequest{..} _) = do
when (iTopLevelRange /= RangeQuery.allRange) $
throwError Error.PutRangeNotAllowedError
WriteQueryResult{..} <- writeQuery identifier False mempty context
WriteQueryResult{..} <- writeQuery MutationSingleUpsert identifier False mempty context
let response = gucResponse resGucStatus resGucHeaders
@@ -390,7 +392,7 @@ handleSingleUpsert identifier context@(RequestContext _ _ ApiRequest{..} _) = do
handleDelete :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
handleDelete identifier context@(RequestContext _ _ ApiRequest{..} _) = do
WriteQueryResult{..} <- writeQuery identifier False mempty context
WriteQueryResult{..} <- writeQuery MutationDelete identifier False mempty context
let
response = gucResponse resGucStatus resGucHeaders
@@ -522,13 +524,13 @@ data WriteQueryResult = WriteQueryResult
, resGucHeaders :: [GucHeader]
}
writeQuery :: QualifiedIdentifier -> Bool -> [Text] -> RequestContext -> DbHandler WriteQueryResult
writeQuery identifier@QualifiedIdentifier{..} isInsert pkCols context@RequestContext{..} = do
writeQuery :: Mutation -> QualifiedIdentifier -> Bool -> [Text] -> RequestContext -> DbHandler WriteQueryResult
writeQuery mutation identifier@QualifiedIdentifier{..} isInsert pkCols context@RequestContext{..} = do
readReq <- readRequest identifier context
mutateReq <-
liftEither $
ReqBuilder.mutateRequest qiSchema qiName ctxApiRequest
ReqBuilder.mutateRequest mutation qiSchema qiName ctxApiRequest
(tablePKCols ctxDbStructure qiSchema qiName)
readReq
+4 -3
View File
@@ -68,7 +68,6 @@ instance PgrstError ApiRequestError where
status ParseRequestError{} = HTTP.status400
status QueryParamError{} = HTTP.status400
status UnacceptableSchema{} = HTTP.status406
status UnsupportedVerb = HTTP.status405
headers _ = [ContentType.toHeader CTApplicationJSON]
@@ -104,8 +103,6 @@ instance JSON.ToJSON ApiRequestError where
(_, True, CTApplicationJSON) -> prms <> " function or the " <> schema <> "." <> procName <>" function with a single unnamed json or jsonb parameter"
_ -> prms <> " function") <>
" in the schema cache")]
toJSON UnsupportedVerb = JSON.object [
"message" .= ("Unsupported HTTP verb" :: Text)]
toJSON InvalidFilters = JSON.object [
"message" .= ("Filters must include all and only primary key columns with 'eq' operators" :: Text)]
toJSON (UnacceptableSchema schemas) = JSON.object [
@@ -283,6 +280,7 @@ data Error
| PutMatchingPkError
| PutRangeNotAllowedError
| SingularityError Integer
| UnsupportedVerb Text
instance PgrstError Error where
status (ApiRequestError err) = status err
@@ -298,6 +296,7 @@ instance PgrstError Error where
status PutMatchingPkError = HTTP.status400
status PutRangeNotAllowedError = HTTP.status400
status SingularityError{} = HTTP.status406
status UnsupportedVerb{} = HTTP.status405
headers (ApiRequestError err) = headers err
headers (JwtTokenInvalid m) = [ContentType.toHeader CTApplicationJSON, invalidTokenHeader m]
@@ -332,6 +331,8 @@ instance JSON.ToJSON Error where
toJSON JwtTokenRequired = JSON.object [
"message" .= ("Anonymous access is disabled" :: Text)]
toJSON NotFound = JSON.object []
toJSON (UnsupportedVerb verb) = JSON.object [
"message" .= ("Unsupported HTTP verb: " <> verb)]
toJSON (PgErr err) = JSON.toJSON err
toJSON (ApiRequestError err) = JSON.toJSON err
+26 -26
View File
@@ -9,6 +9,7 @@ Description : PostgREST functions to translate HTTP request to a domain type cal
module PostgREST.Request.ApiRequest
( ApiRequest(..)
, InvokeMethod(..)
, Mutation(..)
, ContentType(..)
, Action(..)
, Target(..)
@@ -82,16 +83,16 @@ data Payload
| RawPay { payRaw :: LBS.ByteString }
data InvokeMethod = InvHead | InvGet | InvPost deriving Eq
data Mutation = MutationCreate | MutationDelete | MutationSingleUpsert | MutationUpdate deriving Eq
-- | Types of things a user wants to do to tables/views/procs
data Action
= ActionCreate
= ActionMutate Mutation
| ActionRead {isHead :: Bool}
| ActionUpdate
| ActionDelete
| ActionSingleUpsert
| ActionInvoke InvokeMethod
| ActionInfo
| ActionInspect {isHead :: Bool}
| ActionUnknown Text
deriving Eq
-- | The path info that will be mapped to a target (used to handle validations and errors before defining the Target)
data Path
@@ -220,10 +221,10 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
contentType = maybe CTApplicationJSON ContentType.decodeContentType $ lookupHeader "content-type"
columns = case action of
ActionCreate -> qsColumns
ActionUpdate -> qsColumns
ActionInvoke InvPost -> qsColumns
_ -> Nothing
ActionMutate MutationCreate -> qsColumns
ActionMutate MutationUpdate -> qsColumns
ActionInvoke InvPost -> qsColumns
_ -> Nothing
payloadColumns =
case (contentType, action) of
@@ -265,25 +266,24 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
| otherwise -> ActionRead{isHead=False}
"POST" -> if isTargetingProc
then ActionInvoke InvPost
else ActionCreate
"PATCH" -> ActionUpdate
"PUT" -> ActionSingleUpsert
"DELETE" -> ActionDelete
else ActionMutate MutationCreate
"PATCH" -> ActionMutate MutationUpdate
"PUT" -> ActionMutate MutationSingleUpsert
"DELETE" -> ActionMutate MutationDelete
"OPTIONS" -> ActionInfo
_ -> ActionInspect{isHead=False}
_ -> ActionUnknown $ T.decodeUtf8 method
defaultSchema = NonEmptyList.head configDbSchemas
profile
| length configDbSchemas <= 1 -- only enable content negotiation by profile when there are multiple schemas specified in the config
= Nothing
| otherwise = case action of
| otherwise = case method of
-- POST/PATCH/PUT/DELETE don't use the same header as per the spec
ActionCreate -> contentProfile
ActionUpdate -> contentProfile
ActionSingleUpsert -> contentProfile
ActionDelete -> contentProfile
ActionInvoke InvPost -> contentProfile
_ -> acceptProfile
"DELETE" -> contentProfile
"PATCH" -> contentProfile
"POST" -> contentProfile
"PUT" -> contentProfile
_ -> acceptProfile
where
contentProfile = Just $ maybe defaultSchema T.decodeUtf8 $ lookupHeader "Content-Profile"
acceptProfile = Just $ maybe defaultSchema T.decodeUtf8 $ lookupHeader "Accept-Profile"
@@ -302,12 +302,12 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
PathUnknown -> Right TargetUnknown
shouldParsePayload = case (action, contentType) of
(ActionCreate, _) -> True
(ActionInvoke InvPost, CTUrlEncoded) -> False
(ActionInvoke InvPost, _) -> True
(ActionSingleUpsert, _) -> True
(ActionUpdate, _) -> True
_ -> False
(ActionMutate MutationCreate, _) -> True
(ActionInvoke InvPost, CTUrlEncoded) -> False
(ActionInvoke InvPost, _) -> True
(ActionMutate MutationSingleUpsert, _) -> True
(ActionMutate MutationUpdate, _) -> True
_ -> False
relevantPayload = case (contentType, action) of
-- Though ActionInvoke GET/HEAD doesn't really have a payload, we use the payload variable as a way
-- to store the query string arguments to the function.
+8 -8
View File
@@ -46,6 +46,7 @@ import PostgREST.RangeQuery (NonnegRange, allRange,
import PostgREST.Request.ApiRequest (Action (..),
ApiRequest (..),
InvokeMethod (..),
Mutation (..),
Payload (..))
import PostgREST.Request.Preferences
@@ -313,13 +314,13 @@ updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
findNode :: Maybe ReadRequest
findNode = find (\(Node (_,(nodeName,_,alias,_,_, _)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest
mutateRequest :: Schema -> TableName -> ApiRequest -> [FieldName] -> ReadRequest -> Either Error MutateRequest
mutateRequest schema tName ApiRequest{..} pkCols readReq = mapLeft ApiRequestError $
case iAction of
ActionCreate ->
mutateRequest :: Mutation -> Schema -> TableName -> ApiRequest -> [FieldName] -> ReadRequest -> Either Error MutateRequest
mutateRequest mutation schema tName ApiRequest{..} pkCols readReq = mapLeft ApiRequestError $
case mutation of
MutationCreate ->
Right $ Insert qi iColumns body ((,) <$> iPreferResolution <*> Just confCols) [] returnings
ActionUpdate -> Right $ Update qi iColumns body combinedLogic returnings
ActionSingleUpsert ->
MutationUpdate -> Right $ Update qi iColumns body combinedLogic returnings
MutationSingleUpsert ->
if null qsLogic &&
qsFilterFields == S.fromList pkCols &&
not (null (S.fromList pkCols)) &&
@@ -329,8 +330,7 @@ mutateRequest schema tName ApiRequest{..} pkCols readReq = mapLeft ApiRequestErr
then Right $ Insert qi iColumns body (Just (MergeDuplicates, pkCols)) combinedLogic returnings
else
Left InvalidFilters
ActionDelete -> Right $ Delete qi combinedLogic returnings
_ -> Left UnsupportedVerb
MutationDelete -> Right $ Delete qi combinedLogic returnings
where
confCols = fromMaybe pkCols qsOnConflict
QueryParams.QueryParams{..} = iQueryParams
-1
View File
@@ -71,7 +71,6 @@ data ApiRequestError
| ParseRequestError Text Text
| QueryParamError QPError
| UnacceptableSchema [Text]
| UnsupportedVerb -- Unreachable?
data QPError = QPError Text Text