Set role only once, and set it before other GUC vars (#560)

* Set role only once, and set it before other GUC vars

Fixes #559

* Unify role/claim logic in claimsToSQL

Suggested by @diogob
This commit is contained in:
Joe Nelson
2016-04-15 07:30:36 -07:00
parent c32d13c8f1
commit eae5857d0e
3 changed files with 21 additions and 27 deletions
+2
View File
@@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed ### Fixed
- Prevent role from being changed twice - @begriffs
## [0.3.1.1] - 2016-03-28 ## [0.3.1.1] - 2016-03-28
### Fixed ### Fixed
+14 -19
View File
@@ -12,8 +12,7 @@ In the test suite there is an example of simple login function that can be used
very simple authentication system inside the PostgreSQL database. very simple authentication system inside the PostgreSQL database.
-} -}
module PostgREST.Auth ( module PostgREST.Auth (
setRole claimsToSQL
, claimsToSQL
, jwtClaims , jwtClaims
, tokenJWT , tokenJWT
) where ) where
@@ -25,7 +24,7 @@ import Data.Aeson.Types (parseMaybe, emptyObject, emptyArray)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.Vector as V import qualified Data.Vector as V
import qualified Data.HashMap.Strict as M import qualified Data.HashMap.Strict as M
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe, maybeToList)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Data.Text (Text) import Data.Text (Text)
@@ -34,19 +33,20 @@ import PostgREST.QueryBuilder (pgFmtIdent, pgFmtLit, unquoted)
import qualified Web.JWT as JWT import qualified Web.JWT as JWT
{-| {-|
Receives a map of JWT claims and returns a list Receives a map of JWT claims and returns a list of PostgreSQL
of PostgreSQL statements to set the claims as user defined GUCs. statements to set the claims as user defined GUCs. Except if we
Except if we have a claim called role, have a claim called role, this one is mapped to a SET ROLE
this one is mapped to a SET ROLE statement. statement.
In case there is any problem decoding the JWT it returns Nothing.
-} -}
claimsToSQL :: M.HashMap Text Value -> [BS.ByteString] claimsToSQL :: M.HashMap Text Value -> [BS.ByteString]
claimsToSQL = map setVar . M.toList claimsToSQL claims = roleStmts <> varStmts
where where
setVar ("role", String val) = setRole val roleStmts = maybeToList $
setVar (k, val) = "set local " <> cs (pgFmtIdent $ "postgrest.claims." <> k) (\r -> "set local role " <> r <> ";") . cs . valueToVariable <$> M.lookup "role" claims
<> " = " <> cs (valueToVariable val) <> ";" varStmts = map setVar $ M.toList (M.delete "role" claims)
valueToVariable = pgFmtLit . unquoted setVar (k, val) = "set local " <> cs (pgFmtIdent $ "postgrest.claims." <> k)
<> " = " <> cs (valueToVariable val) <> ";"
valueToVariable = pgFmtLit . unquoted
{-| {-|
Receives the JWT secret (from config) and a JWT and Receives the JWT secret (from config) and a JWT and
@@ -70,11 +70,6 @@ jwtClaims secret input time =
value2map (Object o) = o value2map (Object o) = o
value2map _ = M.empty value2map _ = M.empty
{-| Receives the name of a role and returns a SET ROLE statement -}
setRole :: Text -> BS.ByteString
setRole r = "set local role " <> cs (pgFmtLit r) <> ";"
{-| {-|
Receives the JWT secret (from config) and a JWT and a JSON value Receives the JWT secret (from config) and a JWT and a JSON value
and returns a signed JWT. and returns a signed JWT.
+5 -8
View File
@@ -3,8 +3,7 @@
module PostgREST.Middleware where module PostgREST.Middleware where
import Control.Monad (unless) import Data.Aeson (Value (..))
import qualified Data.ByteString as BS
import qualified Data.HashMap.Strict as M import qualified Data.HashMap.Strict as M
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
@@ -21,7 +20,7 @@ import Network.Wai.Middleware.Gzip (def, gzip)
import Network.Wai.Middleware.Static (only, staticPolicy) import Network.Wai.Middleware.Static (only, staticPolicy)
import PostgREST.ApiRequest (pickContentType) import PostgREST.ApiRequest (pickContentType)
import PostgREST.Auth (setRole, jwtClaims, claimsToSQL) import PostgREST.Auth (jwtClaims, claimsToSQL)
import PostgREST.Config (AppConfig (..), corsPolicy) import PostgREST.Config (AppConfig (..), corsPolicy)
import PostgREST.Error (errResponse) import PostgREST.Error (errResponse)
@@ -31,7 +30,6 @@ runWithClaims :: AppConfig -> NominalDiffTime ->
(Request -> H.Transaction Response) -> (Request -> H.Transaction Response) ->
Request -> H.Transaction Response Request -> H.Transaction Response
runWithClaims conf time app req = do runWithClaims conf time app req = do
H.sql setAnon
let tokenStr = case split (== ' ') (cs auth) of let tokenStr = case split (== ' ') (cs auth) of
("Bearer" : t : _) -> t ("Bearer" : t : _) -> t
_ -> "" _ -> ""
@@ -42,15 +40,14 @@ runWithClaims conf time app req = do
if M.null claims && not (null tokenStr) if M.null claims && not (null tokenStr)
then clientErr "Invalid JWT" then clientErr "Invalid JWT"
else do else do
let cmdBatch = mconcat $ claimsToSQL claims -- role claim defaults to anon if not specified in jwt
unless (BS.null cmdBatch) (H.sql cmdBatch) H.sql . mconcat . claimsToSQL $ M.union claims (M.singleton "role" anon)
app req app req
where where
hdrs = requestHeaders req hdrs = requestHeaders req
jwtSecret = configJwtSecret conf jwtSecret = configJwtSecret conf
auth = fromMaybe "" $ lookup hAuthorization hdrs auth = fromMaybe "" $ lookup hAuthorization hdrs
anon = cs $ configAnonRole conf anon = String . cs $ configAnonRole conf
setAnon = setRole anon
clientErr = return . errResponse status400 clientErr = return . errResponse status400
unsupportedAccept :: Application -> Application unsupportedAccept :: Application -> Application