User_id support (via user_vars)

This commit is contained in:
Federico Rampazzo
2015-05-24 03:26:42 +01:00
parent 1cc53245c5
commit 83f48dcd15
6 changed files with 63 additions and 14 deletions
+2 -2
View File
@@ -120,9 +120,9 @@ app conf reqBody req =
login <- signInRole (cs $ userId u) login <- signInRole (cs $ userId u)
(cs $ userPass u) (cs $ userPass u)
case login of case login of
LoginSuccess role -> LoginSuccess role uid ->
return $ responseLBS status201 [ jsonH ] $ return $ responseLBS status201 [ jsonH ] $
encode . object $ [("token", String $ tokenJWT jwtSecret (cs $ userId u) role)] encode . object $ [("token", String $ tokenJWT jwtSecret uid role)]
_ -> return $ responseLBS status401 [jsonH] $ _ -> return $ responseLBS status401 [jsonH] $
encode . object $ [("message", String "Failed authentication.")] encode . object $ [("message", String "Failed authentication.")]
+18 -5
View File
@@ -40,12 +40,13 @@ instance ToJSON AuthUser where
, "role" .= userRole u ] , "role" .= userRole u ]
type DbRole = Text type DbRole = Text
type UserId = Text
data LoginAttempt = data LoginAttempt =
NoCredentials NoCredentials
| MalformedAuth | MalformedAuth
| LoginFailed | LoginFailed
| LoginSuccess DbRole | LoginSuccess DbRole UserId
deriving (Eq, Show) deriving (Eq, Show)
checkPass :: Text -> Text -> Bool checkPass :: Text -> Text -> Bool
@@ -57,6 +58,15 @@ setRole role = H.unitEx $ B.Stmt ("set role " <> cs (pgFmtLit role)) V.empty Tru
resetRole :: H.Tx P.Postgres s () resetRole :: H.Tx P.Postgres s ()
resetRole = H.unitEx [H.stmt|reset role|] resetRole = H.unitEx [H.stmt|reset role|]
setUserId :: Text -> H.Tx P.Postgres s ()
setUserId uid = if uid /= "" then
H.unitEx $ B.Stmt ("set user_vars.user_id = " <> cs (pgFmtLit uid)) V.empty True
else
resetUserId
resetUserId :: H.Tx P.Postgres s ()
resetUserId = H.unitEx [H.stmt|reset user_vars.user_id|]
addUser :: Text -> Text -> Text -> H.Tx P.Postgres s () addUser :: Text -> Text -> Text -> H.Tx P.Postgres s ()
addUser identity pass role = do addUser identity pass role = do
let Just hashed = unsafePerformIO $ hashPasswordUsingPolicy fastBcryptHashingPolicy (cs pass) let Just hashed = unsafePerformIO $ hashPasswordUsingPolicy fastBcryptHashingPolicy (cs pass)
@@ -66,20 +76,23 @@ addUser identity pass role = do
signInRole :: Text -> Text -> H.Tx P.Postgres s LoginAttempt signInRole :: Text -> Text -> H.Tx P.Postgres s LoginAttempt
signInRole user pass = do signInRole user pass = do
u <- H.maybeEx $ [H.stmt|select pass, rolname from postgrest.auth where id = ?|] user u <- H.maybeEx $ [H.stmt|select id, pass, rolname from postgrest.auth where id = ?|] user
return $ maybe LoginFailed (\r -> return $ maybe LoginFailed (\r ->
let (hashed, role) = r in let (uid, hashed, role) = r in
if checkPass hashed pass if checkPass hashed pass
then LoginSuccess role then LoginSuccess role uid
else LoginFailed else LoginFailed
) u ) u
signInWithJWT :: Text -> Text -> LoginAttempt signInWithJWT :: Text -> Text -> LoginAttempt
signInWithJWT secret input = case maybeRole of signInWithJWT secret input = case maybeRole of
Just (Just (String role)) -> LoginSuccess $ cs role Just (Just (String role)) -> case maybeUserId of
Just (Just (String uid)) -> LoginSuccess (cs role) (cs uid)
_ -> LoginFailed
_ -> LoginFailed _ -> LoginFailed
where where
maybeRole = (Data.Map.lookup "role" <$> claims) ::Maybe (Maybe Value) maybeRole = (Data.Map.lookup "role" <$> claims) ::Maybe (Maybe Value)
maybeUserId = (Data.Map.lookup "id" <$> claims) ::Maybe (Maybe Value)
claims = JWT.unregisteredClaims <$> JWT.claims <$> decoded claims = JWT.unregisteredClaims <$> JWT.claims <$> decoded
decoded = JWT.decodeAndVerifySignature (JWT.secret secret) input decoded = JWT.decodeAndVerifySignature (JWT.secret secret) input
+7 -5
View File
@@ -20,7 +20,7 @@ import Network.Wai (Application, requestHeaders, responseLBS, rawPathInfo,
import Network.URI (URI(..), parseURI) import Network.URI (URI(..), parseURI)
import PostgREST.Config (AppConfig(..)) import PostgREST.Config (AppConfig(..))
import PostgREST.Auth (LoginAttempt(..), signInRole, signInWithJWT, setRole, resetRole) import PostgREST.Auth (LoginAttempt(..), signInRole, signInWithJWT, setRole, resetRole, setUserId, resetUserId)
import Codec.Binary.Base64.String (decode) import Codec.Binary.Base64.String (decode)
import Prelude import Prelude
@@ -35,8 +35,8 @@ authenticated conf app req = do
return $ responseLBS status400 [] "Malformed basic auth header" return $ responseLBS status400 [] "Malformed basic auth header"
LoginFailed -> LoginFailed ->
return $ responseLBS status401 [] "Invalid username or password" return $ responseLBS status401 [] "Invalid username or password"
LoginSuccess role -> if role /= currentRole then runInRole role else app req LoginSuccess role uid -> if role /= currentRole then runInRole role uid else app req
NoCredentials -> if anon /= currentRole then runInRole anon else app req NoCredentials -> if anon /= currentRole then runInRole anon "" else app req
where where
jwtSecret = cs $ configJwtSecret conf jwtSecret = cs $ configJwtSecret conf
@@ -54,11 +54,13 @@ authenticated conf app req = do
return $ signInWithJWT jwtSecret jwt return $ signInWithJWT jwtSecret jwt
_ -> return NoCredentials _ -> return NoCredentials
runInRole :: Text -> H.Tx P.Postgres s Response runInRole :: Text -> Text -> H.Tx P.Postgres s Response
runInRole r = do runInRole r uid = do
setUserId uid
setRole r setRole r
res <- app req res <- app req
resetRole resetRole
resetUserId
return res return res
+20
View File
@@ -274,3 +274,23 @@ spec = afterAll_ resetDb $ around withApp $ do
[("Prefer", "return=representation")] [("Prefer", "return=representation")]
[json| { id: 99 } |] [json| { id: 99 } |]
`shouldRespondWith` [json| [{id:99}] |] `shouldRespondWith` [json| [{id:99}] |]
describe "Row level permission" $
it "set user_id when inserting rows" $ do
_ <- post "/postgrest/users" [json| { "id":"jdoe", "pass": "1234", "role": "postgrest_test_author" } |]
_ <- post "/postgrest/users" [json| { "id":"jroe", "pass": "1234", "role": "postgrest_test_author" } |]
p1 <- request methodPost "/authors_only"
[ authHeaderBasic "jdoe" "1234", ("Prefer", "return=representation") ]
[json| { "secret": "nyancat" } |]
liftIO $ do
simpleBody p1 `shouldBe` [json| { "owner":"jdoe", "secret":"nyancat" } |]
simpleStatus p1 `shouldBe` created201
p2 <- request methodPost "/authors_only"
-- jwt token for jroe
[ authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqcm9lIn0.YuF_VfmyIxWyuceT7crnNKEprIYXsJAyXid3rjPjIow", ("Prefer", "return=representation") ]
[json| { "secret": "lolcat", "owner": "hacker" } |]
liftIO $ do
simpleBody p2 `shouldBe` [json| { "owner":"jroe", "secret":"lolcat" } |]
simpleStatus p2 `shouldBe` created201
+1 -1
View File
@@ -79,7 +79,7 @@ spec = around dbWithSchema $ do
addUser user pass role conn addUser user pass role conn
return conn) $ do return conn) $ do
it "accepts correct credentials and return the role" $ \conn -> it "accepts correct credentials and return the role" $ \conn ->
signInRole user pass conn `shouldReturn` LoginSuccess role signInRole user pass conn `shouldReturn` LoginSuccess role user
it "returns nothing with bad creds" $ \conn -> do it "returns nothing with bad creds" $ \conn -> do
signInRole "not-a-user" pass conn `shouldReturn` LoginFailed signInRole "not-a-user" pass conn `shouldReturn` LoginFailed
+14
View File
@@ -72,6 +72,18 @@ $$;
ALTER FUNCTION postgrest.update_owner() OWNER TO postgrest_test; ALTER FUNCTION postgrest.update_owner() OWNER TO postgrest_test;
CREATE FUNCTION set_authors_only_owner() RETURNS trigger
LANGUAGE plpgsql
AS $$
begin
NEW.owner = current_setting('user_vars.user_id');
RETURN NEW;
end
$$;
ALTER FUNCTION postgrest.set_authors_only_owner() OWNER TO postgrest_test;
SET search_path = "1", pg_catalog; SET search_path = "1", pg_catalog;
SET default_tablespace = ''; SET default_tablespace = '';
@@ -80,6 +92,7 @@ SET default_with_oids = false;
CREATE TABLE authors_only ( CREATE TABLE authors_only (
owner character varying NOT NULL,
secret character varying NOT NULL secret character varying NOT NULL
); );
@@ -328,6 +341,7 @@ SET search_path = "1", pg_catalog;
ALTER TABLE ONLY authors_only ALTER TABLE ONLY authors_only
ADD CONSTRAINT authors_only_pkey PRIMARY KEY (secret); ADD CONSTRAINT authors_only_pkey PRIMARY KEY (secret);
CREATE TRIGGER secrets_owner_track BEFORE INSERT OR UPDATE ON authors_only FOR EACH ROW EXECUTE PROCEDURE postgrest.set_authors_only_owner();
ALTER TABLE ONLY auto_incrementing_pk ALTER TABLE ONLY auto_incrementing_pk