WIP: Trap postgres errors in middleware

Fixes #39
This commit is contained in:
Joe Nelson
2014-10-08 14:58:38 -07:00
parent 68d0a38806
commit 527d51670c
5 changed files with 97 additions and 71 deletions
+1
View File
@@ -36,6 +36,7 @@ executable dbapi
, PgStructure , PgStructure
, PgQuery , PgQuery
, RangeQuery , RangeQuery
, Middleware
hs-source-dirs: src hs-source-dirs: src
Test-Suite spec Test-Suite spec
+3 -11
View File
@@ -5,7 +5,6 @@ module Dbapi where
import Types (SqlRow, getRow) import Types (SqlRow, getRow)
import Control.Exception (try)
import Control.Monad (join) import Control.Monad (join)
import Control.Exception.Base (bracket_) import Control.Exception.Base (bracket_)
import Control.Arrow ((***)) import Control.Arrow ((***))
@@ -35,7 +34,6 @@ import Data.String.Conversions (cs)
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import Database.HDBC.PostgreSQL (Connection) import Database.HDBC.PostgreSQL (Connection)
import Database.HDBC.Types (SqlError, seErrorMsg)
import PgStructure (printTables, printColumns, primaryKeyColumns, import PgStructure (printTables, printColumns, primaryKeyColumns,
columns, Column(colName)) columns, Column(colName))
@@ -94,7 +92,7 @@ app conn anonymous req respond = do
MalformedAuth -> MalformedAuth ->
respond $ responseLBS status400 [] "Malformed basic auth header" respond $ responseLBS status400 [] "Malformed basic auth header"
LoginFailed -> LoginFailed ->
respond $ responseLBS status403 [] "Invalid username or password" respond $ responseLBS status401 [] "Invalid username or password"
LoginSuccess role -> LoginSuccess role ->
bracket_ (pgSetRole conn role) (pgResetRole conn) $ appWithRole conn req respond bracket_ (pgSetRole conn role) (pgResetRole conn) $ appWithRole conn req respond
NoCredentials -> NoCredentials ->
@@ -102,9 +100,8 @@ app conn anonymous req respond = do
appWithRole :: Connection -> Application appWithRole :: Connection -> Application
appWithRole conn req respond = do appWithRole conn req respond =
r <- try $ respond =<< case (path, verb) of
case (path, verb) of
([], _) -> ([], _) ->
responseLBS status200 [jsonContentType] <$> printTables ver conn responseLBS status200 [jsonContentType] <$> printTables ver conn
@@ -168,8 +165,6 @@ appWithRole conn req respond = do
(_, _) -> (_, _) ->
return $ responseLBS status404 [] "" return $ responseLBS status404 [] ""
respond $ either sqlErrorHandler id r
where where
path = pathInfo req path = pathInfo req
verb = requestMethod req verb = requestMethod req
@@ -232,9 +227,6 @@ requestedVersion hdrs =
accept = cs <$> lookup hAccept hdrs :: Maybe String accept = cs <$> lookup hAccept hdrs :: Maybe String
verStr = (=~ verRegex) <$> accept :: Maybe [[String]] verStr = (=~ verRegex) <$> accept :: Maybe [[String]]
sqlErrorHandler :: SqlError -> Response
sqlErrorHandler e =
responseLBS status400 [] $ cs (seErrorMsg e)
addHeaders :: ResponseHeaders -> Response -> Response addHeaders :: ResponseHeaders -> Response -> Response
addHeaders hdrs (ResponseFile s headers fp m) = addHeaders hdrs (ResponseFile s headers fp m) =
+2 -1
View File
@@ -4,6 +4,7 @@
module Main where module Main where
import Dbapi import Dbapi
import Middleware (reportPgErrors)
import Network.Wai.Handler.Warp hiding (Connection) import Network.Wai.Handler.Warp hiding (Connection)
import Database.HDBC.PostgreSQL (connectPostgreSQL') import Database.HDBC.PostgreSQL (connectPostgreSQL')
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
@@ -40,7 +41,7 @@ main = do
Prelude.putStrLn $ "Listening on port " ++ (show $ configPort conf :: String) Prelude.putStrLn $ "Listening on port " ++ (show $ configPort conf :: String)
conn <- connectPostgreSQL' dburi conn <- connectPostgreSQL' dburi
runTLS tls settings $ gzip def $ cors corsPolicy $ app conn (cs $ configAnonRole conf) runTLS tls settings $ gzip def $ cors corsPolicy $ reportPgErrors $ app conn (cs $ configAnonRole conf)
where where
describe = progDesc "create a REST API to an existing Postgres database" describe = progDesc "create a REST API to an existing Postgres database"
+33
View File
@@ -0,0 +1,33 @@
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Middleware where
import Data.Aeson
import Network.HTTP.Types.Header (hContentType)
import Network.HTTP.Types.Status (status400)
import Database.HDBC.Types (SqlError(..))
import Control.Exception (catchJust)
import Network.Wai
instance ToJSON SqlError where
toJSON t = object [
"error" .= object [
"code" .= seNativeError t
, "message" .= seErrorMsg t
, "state" .= seState t
]
]
reportPgErrors :: Middleware
reportPgErrors app req respond =
catchJust isPgException (app req respond) (
respond . responseLBS status400 [(hContentType, "application/json")]
. encode
)
where
isPgException :: SqlError -> Maybe SqlError
isPgException = Just
+2 -3
View File
@@ -13,12 +13,11 @@ spec :: Spec
spec = around appWithFixture $ spec = around appWithFixture $
describe "authorization" $ do describe "authorization" $ do
it "hides tables that anonymous does not own" $ it "hides tables that anonymous does not own" $
-- TODO: should be 404 get "/authors_only" `shouldRespondWith` 400 -- TODO: should be 404
get "/authors_only" `shouldRespondWith` 400
it "indicates login failure" $ do it "indicates login failure" $ do
let auth = authHeader "dbapi_test_author_a" "fakefake" let auth = authHeader "dbapi_test_author_a" "fakefake"
request methodGet "/authors_only" [auth] "" request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 403 `shouldRespondWith` 401
it "allows users with permissions to see their tables" $ do it "allows users with permissions to see their tables" $ do
let auth = authHeader "dbapi_test_author_a" "" let auth = authHeader "dbapi_test_author_a" ""
request methodGet "/authors_only" [auth] "" request methodGet "/authors_only" [auth] ""